<# .SYNOPSIS Download YouTube video(s) at highest quality as merged MP4. .DESCRIPTION Wraps yt-dlp with the settings needed for modern YouTube: - JS runtime (bun preferred; deno auto-downloaded to .\bin if missing) - yt-dlp (auto-downloaded to .\bin if missing) - ffmpeg (must be on PATH, or set -FfmpegDir) - cookies.txt next to this script is used automatically if present .EXAMPLE .\yt-download.ps1 "https://www.youtube.com/watch?v=xxxx" .EXAMPLE .\yt-download.ps1 URL1 URL2 URL3 -OutDir "D:\Videos" #> [CmdletBinding()] param( [Parameter(Mandatory = $true, Position = 0, ValueFromRemainingArguments = $true)] [string[]] $Urls, [string] $OutDir = (Join-Path $env:USERPROFILE 'Downloads'), # Override format selection if you want (e.g. H.264 only, or <=1080p) [string] $Format = 'bestvideo+bestaudio/best', # Path to ffmpeg's bin dir. Auto-detected from PATH if omitted. [string] $FfmpegDir = '', # Path to a Netscape cookies.txt. Defaults to cookies.txt beside this script. [string] $Cookies = '' ) $ErrorActionPreference = 'Stop' $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path $BinDir = Join-Path $ScriptDir 'bin' New-Item -ItemType Directory -Force $BinDir | Out-Null function Get-Tool($name, $url, $outFile) { $local = Join-Path $BinDir $outFile if (Test-Path $local) { return $local } $onPath = (Get-Command $name -ErrorAction SilentlyContinue) if ($onPath) { return $onPath.Source } Write-Host "[setup] downloading $name ..." -ForegroundColor Cyan Invoke-WebRequest -Uri $url -OutFile $local return $local } # --- yt-dlp --- $ytdlp = Get-Tool 'yt-dlp' 'https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp.exe' 'yt-dlp.exe' # --- JS runtime (bun preferred; deno fallback; required by YouTube) --- $jsRuntime = '' $bun = Get-Command 'bun' -ErrorAction SilentlyContinue if ($bun) { $jsRuntime = "bun:$($bun.Source)" } else { $deno = Join-Path $BinDir 'deno.exe' if (-not (Test-Path $deno)) { $onPath = Get-Command 'deno' -ErrorAction SilentlyContinue if ($onPath) { $deno = $onPath.Source } else { Write-Host "[setup] downloading deno ..." -ForegroundColor Cyan $zip = Join-Path $BinDir 'deno.zip' Invoke-WebRequest -Uri 'https://github.com/denoland/deno/releases/latest/download/deno-x86_64-pc-windows-msvc.zip' -OutFile $zip Expand-Archive $zip -DestinationPath $BinDir -Force Remove-Item $zip -Force } } $jsRuntime = "deno:$deno" } # --- ffmpeg --- if (-not $FfmpegDir) { $ff = Get-Command 'ffmpeg' -ErrorAction SilentlyContinue if ($ff) { $FfmpegDir = Split-Path -Parent $ff.Source } else { Write-Warning "ffmpeg not found on PATH. Install it (winget install Gyan.FFmpeg) or pass -FfmpegDir. Merging video+audio will fail without it." } } # --- cookies --- if (-not $Cookies) { $defaultCookies = Join-Path $ScriptDir 'cookies.txt' if (Test-Path $defaultCookies) { $Cookies = $defaultCookies } } New-Item -ItemType Directory -Force $OutDir | Out-Null $common = @( '--no-js-runtimes', '--js-runtimes', $jsRuntime, '-f', $Format, '--merge-output-format', 'mp4', '-o', (Join-Path $OutDir '%(title)s [%(height)sp].%(ext)s'), '--no-playlist' ) if ($FfmpegDir) { $common += @('--ffmpeg-location', $FfmpegDir) } if ($Cookies) { $common += @('--cookies', $Cookies); Write-Host "[info] using cookies: $Cookies" -ForegroundColor DarkGray } else { Write-Host "[info] no cookies.txt -- public videos only. See SKILL.md if you hit 'Sign in to confirm you're not a bot'." -ForegroundColor DarkGray } $fail = 0 foreach ($u in $Urls) { Write-Host "`n==== downloading: $u ====" -ForegroundColor Green & $ytdlp @common $u if ($LASTEXITCODE -ne 0) { $fail++; Write-Warning "failed: $u" } } if ($fail) { Write-Warning "$fail download(s) failed."; exit 1 } Write-Host "`nAll done. Saved to: $OutDir" -ForegroundColor Green