veodl
v1.11.0
Published
A clean, simple video downloader powered by yt-dlp.
Maintainers
Readme
veo
A small, clean video-downloading CLI powered by yt-dlp. No banners, just progress and the saved file path.
Automating with an agent? Start with the agent guide for JSON output, run inspection and audio verification.
Only download content you own or are authorized or legally permitted to download. Respect copyright, website terms, and access restrictions. veo does not bypass DRM, private-content access controls, or paywalls.
Install and run
Requires Node.js 22+ and npm. Internet access is needed for installation and first-use backend setup.
The npm package is veodl; the installed commands are veo and veodl.
npx veodl "https://example.com/video.mp4"Or install globally:
npm install -g veodl
veo "https://example.com/video.mp4"
# or: veodl "https://example.com/video.mp4"The first download automatically prepares missing yt-dlp, FFmpeg and FFprobe.
On Android/Termux this includes installing the native packages and yt-dlp's
JavaScript support with pkg install -y. On desktop systems, missing media tools
are downloaded into veo's own cache. Run veo doctor fix to prepare everything
before downloading. Plain veo doctor, help and version do not install tools.
From this checkout, without publishing:
npm install
node bin/veo.js --help
npm link
veo "https://example.com/video.mp4"package.json maps "veo": "./bin/veo.js"; npm creates the executable/shim automatically. No custom command prefix is needed. Use only trusted URLs, and quote URLs to protect query strings from your shell.
If something does not work, run veo doctor first: it inspects the whole local setup and prints one line per check.
Run veo without arguments in an interactive terminal for a guided download: choose a
profile, enter a link, select video or audio, choose available resolution and output
directory, and start. The wizard checks the link and asks about playlist mode only when
it finds a collection; configured playlist and entry selection values are preselected.
Playlist mode also lists entries for selection. The wizard enables
resume and offers skipping previously downloaded files. With redirected input, use the
regular command arguments; veo never starts a prompt in a script.
Usage
veo <url> [<url>...] [options]
Options:
-q, --quality <quality> best, 2160p, 1440p, 1080p, 720p, 480p, 360p
-o, --output <path> Output directory (default: current working directory)
-r, --rename <name> Filename without extension; * inserts the original title
--closest-quality Nearest available resolution instead of an upper bound
--open Open the saved file with your default app
--audio Audio only (MP3 by default)
--format <format> Video: mp4, mkv, webm, mov
Audio: mp3, m4a, aac, opus, flac, wav
--recode Explicit video conversion (requires --format)
--playlist-concurrency <n> Parallel playlist entries, 1-4 (default: 2)
--playlist Download every entry of a playlist or channel URL
-N, --concurrent-fragments <n> Parallel fragments, 1-64 (default: 8; 17-64 experimental)
--experimental-fragments Explicitly accept experimental values above 16
--subs Download subtitles (default languages: en)
--auto-subs Include automatically generated subtitles
--sub-langs <langs> Subtitle languages, e.g. "de,en" (manual unless --auto-subs)
--sub-format <format> Prefer subtitle formats, e.g. srt/vtt/best
--list-subs Show manual and automatic subtitle availability
--embed-subs Embed subtitles into the video file
--embed-metadata Embed title, date and other metadata
--embed-thumbnail Embed the thumbnail
--sponsorblock-remove <categories> e.g. "sponsor,selfpromo"
--section <range> Only a time range, e.g. "*10:00-12:00"
--cookies <file> Netscape cookie file, for content you may access
--cookies-from-browser <browser[:profile]>
--resume Keep partial data and continue an interrupted download
--incognito Save media without run ID, history, stats or retry data
--neutral-filename Optional random filename instead of title and templates
--list-qualities Show available video resolutions and exit
--list-formats Show available formats and exit
--dry-run Show what would be downloaded and exit
--verify Probe saved media files with FFprobe (default: off)
--json One JSON object per URL instead of prose
--profile <name> Use a named profile from the config file
--batch-file <file> Read one URL per line (blank lines and # comments ignored)
--retry-failed <id|file> Retry failed/unfinished downloads by run ID or job file
--playlist-items <list> Select entries, e.g. 1,3-5 (implies --playlist)
--skip-existing Skip matching downloads that are still on disk
--no-<boolean-option> Disable a default, e.g. --no-open or --no-audio
-v, --version Show installed version (also: veo version)
-h, --help Show helpveo "https://youtube.com/watch?v=VIDEO_ID" -q 1080p
veo "https://x.com/USER/status/STATUS_ID" -q best
veo "https://example.com/video.mp4" --audio
veo "https://example.com/video.mp4" --audio --format flac
veo "https://example.com/video.mp4" --format webm --recode -o ./videos
veo "https://example.com/video.mp4" -r "My Video" --format mp4 --open
veo "https://example.com/video.mp4" --audio -r "My Music"
veo <url1> <url2> <url3> --embed-metadata --subs
veo "https://youtube.com/playlist?list=LIST_ID" --playlist -q 720p
veo "https://example.com/video.mp4" --resume
npx veodl "https://example.com/video.mp4" --output ./downloadsSeveral URLs and playlists
Playlist downloads use two concurrent entries by default; use --playlist-concurrency 1 for sequential downloads. Each entry uses up to eight concurrent DASH/HLS fragments by default (-N 1 disables fragment parallelism). Fragment counts between 1 and 16 need no confirmation. Values from 17 to 64 are experimental and prompt for y/N confirmation in terminals, or require --experimental-fragments (or "experimentalFragments": true in configuration/profiles) in non-interactive environments and JSON mode. Values above 64 are rejected. More connections help only when the source and connection have spare capacity, and excessive connections may trigger server limits. Video format changes preserve the encoded streams unless --recode is explicitly enabled; audio extraction and precise section cuts retain their existing conversion behavior.
- Pass any number of URLs; up to two run concurrently by default. Use
--concurrent-downloads 1for sequential processing. A failure is reported with its URL and does not stop the remaining URLs; the exit status is1if anything failed. - Playlists, channels and other collections are refused by default with a hint. Add
--playlistto download every entry. Each entry keeps its own title;--renameapplies a name prefix per entry (My Name - 001), or substitutes each title withmovie_*. - Use
--playlist-items 1,3-5to select entries by their original, one-based index. Playlist entries run with bounded parallelism: a failed entry does not discard successful files or stop the remaining entries. Before downloading, veo displays the selected count and a size estimate when the source supplies sizes. Unknown sizes are labeled explicitly; metadata estimates do not predict conversion size. --batch-file links.txtaccepts a UTF-8 URL list, optionally alongside URLs on the command line. A final summary counts saved, skipped and failed videos. Successful files are opened with--openeven if another URL or playlist entry fails.- Failed or cancelled jobs print a ready-to-use
veo --retry-failed <runId>command. Saved job file paths are also accepted. - Use
veo retry --lastto retry the newest failed or unfinished job without copying its path. Active runs are ignored; useveo runsto inspect them. Jobs are stored under the per-user veo cache'sjobsdirectory. Retries retain resolved output directories and settings, even from a different working directory, and explicit flags can override them. Completed playlist jobs retry only failed indices; interrupted playlists revisit their selection and skip completed entries through resume history. Playlist indices refer to the playlist's current order, so changes to that order can change what an index selects. Cookie paths and browser-session settings are not saved in retry jobs; pass those flags again if needed. Job files contain URLs, settings and local file paths and can be deleted when no longer needed.
veo --batch-file links.txt --profile archive
veo "https://example.com/playlist" --playlist-items 1,3-5 --resume
veo "https://example.com/playlist" --playlist --skip-existing
veo --retry-failed "C:\path\to\job.json"
veo --retry-failed abc123
veo retry --lastQuality
- Numeric qualities are an upper bound:
-q 720pdownloads the best resolution at or below 720p, so a metered connection can never accidentally receive a 2160p file. If a source offers nothing at or below the request, veo stops before downloading and lists the resolutions that do exist. --closest-qualityrestores the former "nearest available resolution" rule, which may pick a resolution above the request. It needs a numeric--quality.- Sources that report no resolution metadata fall back to the best available stream. Collections resolve and enforce the quality limit separately for each entry.
- Other positive numeric resolutions, such as
-q 540p, are also accepted. The interactive wizard offers the resolutions reported by the source; without resolution metadata it offersbestrather than inventing available streams. --qualitydoes not apply to audio.- MP4-compatible codecs are preferred at the selected resolution; merged video prefers MP4
with MKV fallback. A single-file source may retain its original container. Use
--format mp4to require MP4 through lossless remuxing. Incompatible codecs cause an error. --formaton video remuxes without re-encoding. Add--recodeto explicitly allow conversion, which may be slow or lossy. Audio formats require--audio.
Playback compatibility
H.264/AAC sources are preferred at the selected quality, including explicit MP4 output. Original codecs are retained by default; a playback note identifies media needing extra player codecs. MP4 is a container, not a promise of a particular codec.
Use veo URL --compatible to ensure MP4 with H.264 8-bit 4:2:0 and AAC.
Already compatible streams are copied; only incompatible streams are encoded (H.264 CRF 18,
AAC 192 kbit/s), which takes time and may lose quality. HDR requiring video conversion
is refused rather than silently losing correct colors. Embedded subtitles/thumbnails are
not supported in this mode; separate subtitle files remain supported.
New configs include a kompatibel profile; existing configs receive a commented profile
example through veo config edit. Enable it with veo URL --profile kompatibel.
Download controls and diagnostics
- URL lists and batch files use
--concurrent-downloads 2by default (range 1–4). Each playlist independently uses--playlist-concurrency 2(range 1–4). The limits multiply when several playlists run at once. JSON emits one complete object per URL in completion order; theurlfield identifies each result. - Adaptive concurrency is on by default. After the backend's own retries are exhausted,
HTTP 429/5xx and temporary connection failures trigger at most two further attempts,
after 2 and 4 seconds. Fragment parallelism and future URL/playlist workers are reduced
for the rest of the run. Active downloads finish normally; format and quality stay fixed.
Partials are continued on retry. Use
--no-adaptive-concurrencyto disable this. --check-spacechecks cache and destination free space before media transfer. Known sizes include conservative room for merge output and a destination copy, plus space reserved by concurrent downloads in the same process. Unknown sizes are reported as unknown; conversion sizes and other processes' disk usage cannot be predicted exactly. Use--no-check-spaceto disable this conservative check.--timingsshows phase times at the end of the run. Single downloads show timing without a summary; multiple downloads show timing below the summary. The phases cover setup, metadata, download/backend, processing, saving and retry waits. Download/backend includes the backend's own startup and extraction overhead; these are summed per-download times, not a diagnosis of network speed. JSON results includetimingsin milliseconds, orentryTimingsfor playlists. Use--no-timingsto hide/omit them.- Terminal details are gray, titles bold, saves green and errors red. The same styling
applies to stats, history, runs/stop, doctor, flush, updates, config prose, help and
interactive prompts. For example,
veo stats --no-colordisables it for one command. Redirected output and JSON have no color escapes.--no-color,"color": false,NO_COLOR, andTERM=dumbdisable colors. Terminal reports shorten long URLs, paths and detail values to the current window width. Titles can use up to three lines before an ellipsis is added. This also applies with--no-color; JSON and redirected output retain complete values. Downloads stay in the normal terminal flow. Only the current progress block is updated in place; narrow windows put the statistics below the progress bar. Existing logs remain in scrollback and are not replayed or rewritten on resize. This can be set independently per profile, for example"profiles": { "default": { "color": true }, "plain": { "color": false } }. Useveo stats --profile plainorveo URL --profile plain;--coloror--no-coloroverrides the profile for one invocation (exceptNO_COLOR). External npm/editor output and Node runtime warnings keep their own formatting.
Filename templates and folders
--filename-template controls the name without extension; --folder-template
creates relative subfolders under --output. Both support {title}, {id},
{channel}, {year}, {playlist} and {index} (three-digit playlist index).
Missing metadata uses readable fallbacks. {year} is the upload year when available.
Subtitles follow the media name. Existing files are never overwritten. Templates cannot
escape the output folder, and symlink/junction subfolders are refused. Use forward slashes
for folders. --rename and --filename-template are mutually exclusive.
veo "VIDEO-URL" --folder-template "{channel}/{year}" --filename-template "{title} - {id}"
veo "PLAYLIST-URL" --playlist --folder-template "{playlist}" --filename-template "{index} - {title}"
veo config check
veo config check --profile fast
veo config show --profile fastConfig checks validate every effective profile (or the selected one), including unknown
settings, ranges and conflicting options. Show prints merged built-in, global and profile
settings as JSON, with credential paths/browser profiles redacted. Neither command contacts
video sites. All new options are also documented in veo config guide.
Files, names and resume
- Original titles are preserved by default. Use
-r "My Video"or--rename "My Video"for video or audio. Supply the name without an extension; the actual media extension is appended automatically. Use-ofor the directory. Invalid filename characters are sanitized and overly long names shortened. Existing files are never intentionally overwritten: duplicates get(1),(2), etc. - Use
-r "movie_*"or"rename": "movie_*"in your config/profile to insert the original title:My Filmbecomesmovie_My Film.mp4. Every*is substituted. Quote the pattern in your shell. Patterns also work with URL batches and playlists. - Subtitles and thumbnails are saved beside the media file under the same base name
(
My Video.mp4→My Video.en.vtt). - Downloads and media processing first finish in the local per-user veo cache, under
downloads(%LOCALAPPDATA%\veo\downloadson Windows). The destination is only written after the media is ready. Saving uses a hard link when supported or an exclusive copy across drives/cloud mounts; copied file sizes are checked. Existing files are never overwritten. The local original is removed only after files and history are saved. Local disk space is therefore needed for the complete download and processing files. - If saving fails or is cancelled after media processing completes, the local original and
sidecars are kept for 15 minutes, even without
--resume. Repeat the same command or use the printed--retry-failedcommand to retry the transfer. Individual completed downloads can be recovered without contacting their source again. The same source, media settings and destination identify a cached transfer; changing those starts a new download. Expired copies are removed on the next veo invocation, not by a background timer while veo is closed. Active transfers are never expired. Another failed transfer starts a fresh 15-minute retention period. --resumealso keeps unfinished downloads in local.veo-part-<request-hash>folders until resumed; unfinished data has no automatic expiry. Without--resume, unfinished downloads are discarded on failure or cancellation. The hash separates source URLs, playlist entries, destinations, quality, media type, format, sections and subtitle/metadata options. A manifest records backend-confirmed completion and saved files, so an unprocessed file is not mistaken for a finished video. Each playlist entry has its own state. Completed entries survive later failures and are skipped when resuming the same selection. Partial downloads are never resumed without--resume. Legacy partial folders in the output directory are left untouched; the new local cache does not automatically migrate them.- A lock prevents two resume processes from using the same partial folder. Normal failures
and Ctrl+C release it. After a force kill, remove the named
.lockfile only after making sure no veo process still uses that folder. - Successful downloads record their source, output settings and saved paths in
.veo-historyinside the output directory.--skip-existinguses these records and checks that the files still exist. A different quality/format or deleted output is downloaded again. Files downloaded before this history existed are not recognized automatically. Deleting history removes duplicate detection, not downloaded media. Records whose files no longer exist are removed on the next download into that folder, and.veo-historyitself is removed with its last record;veo doctorreports stale records.
Metadata and subtitles
veo subs <URL>lists manual and automatically generated subtitles separately, including their languages and source formats.--jsonreturns one object without subtitle URLs.--subswrites manual subtitle files.--auto-subsadds generated subtitles, or selects only generated subtitles when used alone.--sub-langs de,enselects languages;--sub-format srt/vtt/bestprefers available source formats in that order. A format preference does not convert subtitles.--embed-subsembeds selected subtitles into the video container.--embed-metadataand--embed-thumbnailuse FFmpeg to write metadata and cover art.--sponsorblock-remove sponsor,selfpromocuts sponsor segments (YouTube) and requires FFmpeg; the cut re-encodes the affected parts.--section "*10:00-12:00"downloads only a time range.-N 8downloads several fragments in parallel, which is noticeably faster on HLS/DASH sources and heavier on the network.
Optional post-download verification
Use veo <URL> --verify or set "verify": true in a config profile to check
saved media with FFprobe before recording a successful download. It checks that
the container can be probed and contains a video track (or an audio track for
--audio). Verification is off by default; --no-verify overrides a profile.
This quick check does not decode the whole file or prove that the audio is
audible. Use veo inspect <file> --check-audio for a full audio signal check.
If verification fails, veo reports failure and keeps the saved file for review.
Access and credentials
veo downloads publicly accessible content. It does not bypass access controls, and it does not expose options that defeat them. If you need your own session for content you are authorized to view:
--cookies ./cookies.txtpasses a Netscape-format cookie file. The file is validated before any network work, and on Unix a file readable by other users produces a warning.--cookies-from-browser firefox:Workreads cookies from an installed browser:brave,chrome,chromium,edge,firefox,opera,safari,vivaldi,whale, optionally with+gnomekeyring/+kwallet/+basicand a profile or container.
Credentials are used for that single invocation only and are never stored by veo. When a download fails because a login is required, the error message points at these flags.
Inspection and scripting
veo <page-url> --list-sourcesopens the page in a temporary headless Chrome, Edge or Chromium profile, repeatedly checks for visible Play controls (including controls added later or inside frames), and watches media requests after interaction. Use--jsonfor numbered source metadata. Each result includes its title, host, HLS/DASH/direct type, known resolutions, estimated MiB size and Mbit/s bitrate when the source provides them. Signed media URLs are not printed. SetVEO_BROWSER_PATHif the browser is not found.- Add
--deep-scanto verify every media candidate observed during the source search. The normal search verifies at most 30 candidates.--timeout 30ssets a deadline for browser observation and candidate verification; plain numbers mean seconds, andmmeans minutes (5 seconds to 10 minutes). Defaults are 45 seconds, or 2 minutes with--deep-scan. If the deadline is reached, verified sources found so far are shown with a timeout notice. A deep scan covers observed requests; it cannot discover sources that the page never loads. The timeout does not stop a subsequent download. - For an unsupported single page URL, veo searches for media automatically and
uses the only verified source when there is one. If several sources are found,
the terminal asks which one to use; scripts receive a numbered source list and
a nonzero exit.
veo <page-url> --source 2 --dry-run --jsonpreviews a chosen source, andveo <page-url> --source 2 --jsondownloads it. Scripts never receive an interactive prompt. Retry jobs retain the page URL and selected number and discover a fresh media URL, because player links may expire. Browser discovery uses a temporary profile without copying browser cookies, and cannot unlock DRM content. Misspelled flags and commands show a nearby valid spelling, for example--deepscansuggests--deep-scan. veo <url> --list-qualitieslists available video resolutions, highest first, without downloading media.--list-qualitysis an alias. Add--jsonfor aqualitiesarray (e.g.["1080p", "720p"]); use--quality 720pto select an upper bound. Unknown resolutions are reported without guessing. Audio-only, storyboard and DRM formats are excluded. Supports--source <number>for embedded players.--list-formatsprints the backend's own format table for one URL and exits.--dry-runprints the title, the resolved quality and the destination path that would be used — without creating the output directory or downloading anything.--jsonprints one JSON object per line on stdout:{"url":…,"status":"saved","title":…,"files":[…]}or{"url":…,"status":"failed","error":…}. Progress and status still go to stderr. Without--json, every saved file is printed asSaved: <path>on stdout. Duplicate detection can returnstatus: "skipped"; cancellation returns"cancelled". Playlist results also includesaved,skippedandfailures(with original indices). A failed or cancelled playlist can still report files saved before the failure.veo runs [id] --jsonreports active run metadata and per-item progress. Finished attempts remain available throughveo history --json.veo inspect <file> --jsonreports local container and stream metadata. Add--check-audioto decode all audio tracks and measure whether any peak exceeds -60 dBFS. A track can exist without a detectable signal. The check reads the full media file and uses only local FFmpeg/FFprobe tools.- Download results and history retain a
runId.veo inspect run <id> --jsonreads a finished run and checks every saved file before probing it. Renamed media is found by file identity, size and a sampled SHA-256 fingerprint within the original output tree; add--search <directory>after moving it elsewhere. Ambiguous or missing files are reported without probing another file. Old runs lack this persistent record. Finished run records live in the current device's veo cache. On Windows, macOS, Linux and Termux,inspectuses bundled media tools where supported and otherwise uses localffmpegandffprobe(orVEO_FFMPEG_PATH); it does not install them. Copying only the media to another device does not copy its run ID record.
Config file
Defaults can be stored in a config file, so a long list of flags is not needed for every
call. veo doctor prints the exact path; VEO_CONFIG overrides it.
The file accepts JSON with // line comments and /* ... */ block comments. Strings
(including URLs and Windows paths) keep their normal JSON escaping rules. Trailing commas
are not allowed.
- Windows:
%APPDATA%\veo\config.json - macOS:
~/Library/Application Support/veo/config.json - Linux:
$XDG_CONFIG_HOME/veo/config.json(or~/.config/veo/config.json)
{
"output": "D:\\Videos",
"quality": "1080p",
"embedMetadata": true,
"subLangs": "de,en",
"concurrentFragments": 4
}Supported keys: output, quality, format, rename, audio, open, resume, incognito, neutralFilename,
closestQuality, cookies, cookiesFromBrowser, playlist, concurrentFragments,
experimentalFragments, playlistConcurrency, recode, compatible, concurrentDownloads, adaptiveConcurrency,
filenameTemplate, folderTemplate, checkSpace, timings, color,
subs, autoSubs, subLangs, subFormat, embedSubs, embedMetadata, embedThumbnail, sponsorblockRemove, verify,
section, json. An explicit command-line flag always wins over a stored default. An
unknown key produces a warning; invalid JSON or a wrong value type is an error, because
silently ignoring a typo would be worse.
Source discovery defaults are deepScan (boolean), timeout (string such as
"30s" or "2m"), listSources (boolean) and autoListSources (boolean).
listSources: true makes a single URL list sources and exit; use
--no-list-sources to download for one invocation. autoListSources defaults
to true and searches when ordinary extraction finds no downloadable video.
Set it to false or pass --no-auto-list-sources to disable that fallback.
A single verified source is selected automatically; multiple sources require
a choice. deepScan and
timeout apply to either kind of source search.
Additional defaults are skipExisting and playlistItems. Boolean defaults can be disabled
with --no-open, --no-audio, --no-resume, --no-embed-metadata, etc. --no-subs also
disables stored subtitle languages and subtitle embedding for that invocation.
Incognito downloads
Use veo <url> --incognito or set "incognito": true in a named profile. The
downloaded media is saved normally. veo does not assign a run ID or write a
retry job, run archive, download history, output .veo-history record, or
statistics for that attempt. It uses a private temporary staging directory and
removes it after success, failure, or cancellation. It also disables yt-dlp's
filesystem cache. --no-incognito overrides
a profile for one request. Incognito cannot be combined with --resume,
--skip-existing, or --retry-failed; there is no veo retry for an incognito
attempt. A force-killed process may leave temporary files that the OS can
clean later. The destination file, shell history, network service, and source
website can still reveal the download.
Example profile: "profiles": { "private": { "incognito": true } }.
Add "neutralFilename": true to that profile, or pass --neutral-filename,
to save files as names such as video-a1b2c3d4e5f6.mp4. This is optional:
incognito keeps the normal title by default. Neutral naming overrides --rename,
--filename-template, and --folder-template for that request. The selected
output directory, media contents, and embedded metadata are unaffected.
Named profiles
If no profile is selected, profiles.default is applied automatically. veo profile music
selects another named profile for future commands and stores it as activeProfile in the
config; veo profile shows the current choice, and veo profile list marks it in
the list. veo profile reset removes the stored choice and returns to default.
You can also use veo profile default to select it explicitly.
The names list and reset are reserved for these commands and cannot be used as profile names.
--profile NAME overrides this choice for one command. Named profiles use global defaults
rather than inheriting default. The wizard preselects the active profile.
New configurations include an empty default profile and the example profiles. Existing configurations are
left unchanged when opened. An empty profile does not change download behavior.
{
"output": "D:\\Videos",
"profiles": {
"default": { "quality": "1080p", "resume": true },
"music": { "audio": true, "format": "mp3", "output": "D:\\Music" },
"archive": { "quality": "1080p", "embedMetadata": true, "subLangs": "de,en" }
}
}veo <url> --profile music merges global defaults, then the selected profile, then explicit
CLI flags. veo config profiles lists profile names; veo config path prints the file path.
The download status shows Profile: NAME (or global (no profile) when none is configured).
veo config edit creates a small, valid configuration for new or empty files. Opening an
existing configuration does not add or update comments. The current option guide is kept in
the program; veo config guide prints it without changing the file. In the built-in editor,
F3 offers A to add or update the guide at the top and R to remove a previously
generated guide. Ctrl+S saves that choice. This can also clean up the long duplicate block
inserted by older versions. Personal settings and notes below a generated block are preserved.
In an interactive terminal, it opens the built-in editor
unless VISUAL or EDITOR is configured (an executable path, without shell arguments).
The editor provides syntax colors, line numbers and live validation. Syntax errors mark
the affected line; unknown properties and invalid values are highlighted directly, with
spelling suggestions, expected types and allowed values in the status area. Validation uses
the same rules as the CLI, including profile overrides and audio/video formats.
Press F2 on a property or value to open suggestions, use Up/Down to choose, Enter to apply
or Esc to cancel. Suggestions require valid JSON syntax; free-text values and custom numeric
resolutions such as 900p remain supported. Ctrl+S validates and saves;
Ctrl+Z undoes edits, Ctrl+Y redoes them, and Ctrl+A selects the whole file.
Esc or Ctrl+Q exits, asking before discarding changes. Use arrows, Home/End and PageUp/PageDown
to navigate. Invalid configurations cannot be saved. --no-color disables syntax colors.
In terminals supporting SGR mouse reporting, left-click positions the cursor and dragging
selects text, including across lines. Holding the pointer at the top or bottom while dragging
scrolls the file and extends the selection. Backspace/Delete removes the selection; typing replaces
it. The mouse wheel scrolls without changing the editing position; Ctrl+Up/Down also scrolls.
Ctrl+C copies selected text to the system clipboard, and Ctrl+V pastes clipboard text at the
cursor or replaces the selection. Ctrl+Q or Esc exits the editor. The editor disables mouse
reporting again when it exits. Keyboard editing remains available in terminals without mouse
support.
Clipboard access uses the system clipboard on Windows, pbcopy/pbpaste on macOS, and
wl-clipboard (Wayland), xclip or xsel (X11) on Linux. Linux requires one of those tools
and access to a graphical session. The editor reports when no clipboard is available; terminal
paste shortcuts can still insert text directly when supported by the terminal.
On Termux, editor clipboard shortcuts use termux-clipboard-set/termux-clipboard-get
when the Termux:API app and termux-api package are installed.
Use veo config edit --external to use VISUAL, then EDITOR, then Notepad on Windows
or vi elsewhere. veo config edit --terminal explicitly selects the built-in editor.
Set VEO_CONFIG_EDITOR=external to disable the built-in editor by default (auto restores
automatic selection, terminal forces it). For a persistent Windows preference:
[Environment]::SetEnvironmentVariable('VEO_CONFIG_EDITOR', 'external', 'User')
$env:VEO_CONFIG_EDITOR = 'external'Non-interactive sessions use the external editor unless --terminal is explicitly selected,
in which case an interactive-terminal error is reported. The wizard also offers configured profiles.
Generated guides and app messages are in English. Existing comments, profile names and
settings are preserved when opening the editor.
Terminal output
- During a download, the terminal/tab title shows
veo | 50% | My Video(original title or your-rname), plus setup/processing status. It ends withDone,Failed, orCancelled; the shell may replace it at the next prompt. This uses the native console title on Windows (including PowerShell/Windows Terminal) and OSC title sequences on compatible Linux/macOS terminals. Titles are not changed when stderr is redirected orTERM=dumb. Terminal settings that enforce a fixed tab title can override this feature. - Progress shows percentage, speed, downloaded/total size and ETA on stderr; unknown values
appear as
?. Separate audio/video streams each have their own progress. Non-interactive output is throttled. - Progress labels include the current item (
[3/12]), source title, video/audio/media stream, postprocessing (merging, conversion, subtitles or metadata) and saving. Stream percentages describe that stream, not the entire multi-step job. Sources without codec metadata use the neutralMedialabel. --openlaunches the completed file in your default app, including renamed files and audio. With several URLs, the primary file of each URL is opened. Usesexplorer.exeon Windows,openon macOS, andxdg-openon Linux (requires a graphical desktop, xdg-utils and a file association). The CLI does not wait for the player to close. If the opener cannot be launched, a warning is printed and the successful download still exits with0; later errors inside the detached opener/player are not monitored.- After each successful command, veo checks the npm registry at most once per day for a
newer version and prints a one-line notice on stderr (
Update available: veo X (you have Y). Run: veo up). Help, version and the update commands themselves stay quiet. Disable it withVEO_NO_UPDATE_CHECK=1;VEO_REGISTRY/npm_config_registryare respected. - Exit status is
0on success,1on errors, and130on cancellation. With several URLs,1means at least one URL failed.
Commands
veo version # installed version (also: veo --version or veo -v)
veo # interactive download wizard (terminal only)
veo config edit # create/open config; F3 offers the optional guide
veo config guide # print the current option guide
veo config reset # confirm with y; back up config and restore the current template
veo config profiles # list available profiles
veo profile # show the currently selected profile
veo profile list # list all profiles and mark the active one
veo profile music # use music as the default for future commands
veo profile reset # return to default (or global settings if absent)
veo config path # show the config file location
veo doctor # diagnose the local setup; exit 1 if a check fails
veo doctor fix # restore missing or damaged managed tools
veo doctor --offline # skip the network checks
veo stats # persistent download totals; --json for scripting
veo history # the last 5 downloads; --json for scripting
veo history --failed --limit 20 # recent failed/cancelled attempts
veo retry --last # retry the newest failed or unfinished job
veo runs # active runs with their id; veo runs <id> for details
veo runs --json # machine-readable active run metadata
veo inspect FILE --check-audio --json # track metadata and audio signal
veo subs URL --json # available manual and automatic subtitles
veo inspect run ID --check-audio --json # finished run and saved media
veo stop [id] # stop one run, or every active run
veo flush # stop runs, clear temporary downloads and retry jobs
veo flush --stats # the same, and reset the statistics
veo update # install the latest veo with npm
veo update --check # only check for a newer veo
veo up # short alias for veo update (also: veo up --check)
veo upgrade # alias for veo update
veo check update # alias for veo update --check
veo backend update # install the newest yt-dlp release
veo backend reset # back to the release pinned in this veo version
veo alias list # shipped (veo, veodl) plus custom wrappers
veo alias add veo-dl # create veo-dl as a wrapper that calls veo
veo alias remove veo-dl # delete a custom wrapper
veo uninstall -p veodl # delete one alias (veo itself stays installed)
veo uninstall # plan the full removal (aliases, package, cache, config)
veo uninstall --yes # remove everythingveo update runs npm install -g veodl@latest and then removes yt-dlp backend
caches from older pinned releases, keeping an explicitly installed backend release. It
never uses a shell on Linux/macOS, passes fixed arguments only, and prints the manual npm
command on any failure. The registry can be overridden with VEO_REGISTRY (or npm's
npm_config_registry) for mirrors and proxies.
veo alias and veo uninstall
The npm package is veodl; a global install ships the commands veo
and veodl. veo alias manages extra command names without reinstalling:
veo alias list
veo alias add veo-dl
veo alias remove veo-dl
veo uninstall -p veodladd creates a small wrapper next to the veo command found on PATH that
forwards to veo, so it survives veo update. Names use 2-31 lowercase
letters, digits or hyphens. remove (also veo alias rm) deletes the wrapper
files; veo itself can never be removed this way. Removing a shipped name such
as veodl only deletes that shortcut — the next veo update recreates it.
veo uninstall -p <name> is a shortcut for veo alias remove <name>. Bare
veo uninstall removes everything: without flags it only prints the plan
(package veodl with commands veo, veodl, plus custom wrappers, cache
and config paths); veo uninstall --yes deletes all of it — custom wrappers,
the package with npm uninstall -g veodl, veo's cache directory (downloads,
jobs, backend tools, history, statistics) and the config file. Use
--keep-aliases, --keep-cache or --keep-config to preserve one part.
The plan shows the active config path, including a VEO_CONFIG override.
Add --json for scripting.
veo flush
Run veo flush to stop active veo runs started with this version, then remove
temporary local downloads, including the 15-minute retained files and unfinished
resume data, and cached retry job JSON files. Those jobs can no longer be retried.
Saved media, output history, config/profiles, the veo history list, finished-run
records and backend binaries are kept.
Cleanup waits for cancellation; if a run cannot stop, it fails without deleting
download or job files. Locked folders from older or interrupted processes are
skipped and reported. Only veo's own per-user cache is cleaned. Statistics are
preserved unless --stats is given: veo flush --stats resets them too.
To end runs without removing their downloads and jobs, use veo stop [id] instead.
veo stats
Shows saved videos/audio, failed attempts, skips, cancellations and total time spent
on download requests (including preparation, processing and saving). Parallel run
times are added together. Playlist entries count individually; retries are new
attempts. Active requests are recorded when they finish. Tracking starts with this
version; previous downloads are not imported. veo stats --json returns the totals
as JSON. Statistics contain counters and timestamps, not URLs or filenames.
veo history
Shows the last 5 download attempts by default, newest first, with title, status, media type, date, duration, URL and the saved files. Saved, skipped, failed and cancelled items are recorded, including the reason a failure was reported; playlist entries and retried attempts count individually. Active downloads appear once they finish. Long file lists are summarized in the text view.
Use veo history --limit N to show 1-1000 attempts, or veo history --failed to
show only failed and cancelled attempts; the flags can be combined. Failed attempts
include their exact retry-job command when one was recorded. New attempts also show
their persistent run ID for veo inspect run <id>. veo history --json
prints {"count":N,"entries":[…]} for scripting. Each entry has
at, url, title, status, media (video/audio), quality, format, the
complete files list, error, elapsedMs and an optional job path. Titles and paths are stored without
terminal control characters.
History is one small JSON file per attempt in the per-user veo cache's history
directory, so parallel runs cannot overwrite each other and each record stays small. The
directory keeps one file per attempt and contains URLs, titles and local file paths;
delete it to remove those records. veo flush keeps the list (only veo flush --stats
resets statistics, not history).
veo runs and veo stop
Every run registers itself while it works, under a 6-character id, and removes that record when it ends:
veo runs # active runs: id, PID, state, start time, progress, output directory
veo runs k3f9qa # one run in detail: URLs, settings, job file, per-item state
veo stop k3f9qa # ask that run to stop and wait until it exits
veo stop # stop every active runveo runs lists the runs of this user, including those started in another terminal, and a
run disappears from the list as soon as it finishes. Progress such as
1/3 done, 1 running is read from the run's job file, which is created before the first
download starts, so a run that is still preparing the backend shows starting. veo stop
<id> writes a stop request that the run itself polls, so it needs no signals or PIDs and
works the same on every platform. A stopped run exits like Ctrl+C (130) and keeps its
partial data and retry job, so the printed veo --retry-failed command still works —
unlike veo flush, which also removes that data. If a run does not stop within 15 seconds,
veo stop reports it and exits with 1.
A run that crashed without cleaning up leaves its record behind: veo runs marks it
stale and veo stop removes it, with or without its id. Records contain the id, process
id, start time, URLs, output directory and job path — never cookie files, browser sessions
or other credentials. Unknown or damaged record files are ignored instead of breaking veo,
because a single stray file in the cache must never stop later runs.
veo doctor
Prints one line per check: ok, warn or fail. It inspects Node.js, the platform, the
output directory, the backend cache, yt-dlp (including the SHA-256 of the cached binary and
the version it reports), FFmpeg/FFprobe, a system FFmpeg fallback, the config file, leftover
partial downloads, stale duplicate-detection records, the npm registry and the yt-dlp release host. It downloads no backend and
only creates its own probe files plus the backend cache directory. Exit status is 1 when
at least one check fails.
Run veo doctor fix to restore missing or damaged managed tools and check again.
It stages bundled FFmpeg/FFprobe and downloads verified yt-dlp when needed. Missing
desktop media tools are installed in veo's cache using npm and the pinned media
packages. On Termux it installs missing native tools with the package manager.
With --offline, it only uses local binaries; missing tools are reported for an online retry.
Use -o PATH to create and check an output directory. Config errors and invalid overrides
are reported for manual correction; PATH and config values are never rewritten.
A missing system FFmpeg is not a warning when the selected media tools work.
Supported sites and backend
YouTube, X/Twitter, TikTok, Vimeo, Reddit, Instagram, and many other yt-dlp sites are supported when publicly accessible and technically available. Support changes with websites, regions, rate limits, and backend versions; it is not a guarantee that every URL will work.
Official standalone yt-dlp is acquired on first download and cached outside the package directory; help, version and plain doctor never download the backend. The release is pinned and SHA-256 verified against hashes shipped with this package. FFmpeg and FFprobe are normally supplied by the optional ffmpeg-static and ffprobe-static dependencies during npm installation. If these are absent and no local pair is available, veo installs [email protected] and a platform-specific @ffprobe-installer package in a temporary cache project, checks that both programs run, and saves them in its backend cache. Media packages use npm/upstream HTTPS distribution, not the yt-dlp pinned-hash guarantee. These binaries have their own licenses; see their upstream packages. No Python installation is needed on supported standalone platforms.
The existing Node executable is explicitly enabled as yt-dlp's JavaScript runtime for YouTube. Local yt-dlp configuration and plugins are disabled for predictable execution. Arguments are passed without a shell.
ffmpeg-static and ffprobe-static publish no Windows ARM64 binaries. veo no longer refuses
that platform: it uses an FFmpeg/FFprobe pair found on PATH, including the usual
WinGet, Chocolatey and C:\ffmpeg\bin locations, or automatically downloads x64
media tools for Windows 11's x64 emulation.
Android / Termux
With Node.js 22+ and npm already installed in Termux:
npm install -g veodl
veo "URL"On Android, veo automatically finds yt-dlp, ffmpeg and ffprobe on PATH
(also checking $PREFIX/bin). If tools or the yt-dlp-ejs Python module are
missing, it runs pkg install -y python-yt-dlp yt-dlp-ejs ffmpeg automatically,
then checks the installed programs before continuing the download. Python is
installed as a dependency. Termux with the pacman package manager uses
--noconfirm instead. This needs a working Termux repository and internet access.
No root access is needed. Later runs reuse the installed tools.
Static FFmpeg dependencies are optional; --omit=optional can be added to the
npm install command to skip attempting those desktop packages entirely.
The system yt-dlp is maintained by Termux and is
not pinned or hash-verified by veo. Explicit VEO_YT_DLP_PATH and
VEO_FFMPEG_PATH overrides still take precedence.
veo doctor fix also performs this automatic setup; veo doctor fix --offline
never invokes a package manager. Update Termux packages with pkg upgrade;
veo backend update prints this platform's update instructions.
To save files in Android's shared Downloads folder, run termux-setup-storage,
grant the storage permission, then use veo "URL" -o ~/storage/downloads.
Keep veo and its tools in Termux's private storage.
For existing published veo 1.6.1 installations (before this automatic setup), run:
pkg install python-yt-dlp yt-dlp-ejs ffmpeg
export VEO_YT_DLP_PATH="$(command -v yt-dlp)"
export VEO_FFMPEG_PATH="$PREFIX/bin"
veo doctorThese exports apply to the current shell; add them to your shell startup file if you need them on subsequent launches of 1.6.1.
Advanced overrides
VEO_YT_DLP_PATH: absolute path to a trusted, current yt-dlp executable. It always takes precedence over the managed backend, including one installed byveo backend update.VEO_FFMPEG_PATH: directory containing bothffmpegandffprobeexecutables (with.exeon Windows).VEO_CONFIG: path to the config file.
Automatic setup covers Windows x64/ia32/ARM64, macOS x64/ARM64, Linux x64/ARM64,
and Android through Termux. Windows ARM64 media tools require x64 emulation.
--open uses termux-open in Termux; another Android app must be available to view the file.
Other systems should supply trusted native binaries. Node.js 22+, npm, internet
access, executable private storage and working OS libraries are prerequisites;
network blocks or unavailable repositories can still prevent setup. Android's
shared-storage permission must be granted by the user. Maintainers should update
the pinned yt-dlp release and hashes as websites change,
or users can install a newer release themselves.
Installing a newer backend
veo backend update --check # is a newer yt-dlp release available?
veo backend update # install it
veo backend reset # forget it, use the pinned release againThe pinned backend is hash-verified against values compiled into this veo version. That
guarantee cannot cover a release that did not exist when this version was built, so
veo backend update verifies the download against the SHA2-256SUMS file published with
that release over HTTPS: the trust anchor becomes HTTPS and GitHub instead of the npm
package. It is opt-in, it says so on stderr when it runs, and veo backend reset (plus the
next veo update) returns to the pinned release. An installed release is used only while it
is newer than the pinned one and its bytes still match the recorded hash; anything else
silently falls back to the pinned release.
Develop and publish
npm install
npm test # unit tests, no network
npm run test:smoke # real downloads: local HTTP server, real yt-dlp, real FFmpeg
npm run test:open # real download plus a controlled desktop-opener replacement
npm pack
npm run check:package # inspect the packed tarball
npm link
veo --version
# Then, authenticated as the account that owns the name:
npm publishThe smoke test generates a two-second synthetic video with FFmpeg, serves it on loopback,
and runs real yt-dlp downloads. It checks default output, the quality cap, duplicate names,
audio extraction, conversion, dry-run, format listing, JSON output, metadata embedding,
resume, batch behaviour, playlist selection and nonzero failures, and verifies that a
finished download whose destination was unavailable is retried from the local cache
without contacting the source again. It also confirms that real runs leave veo history
entries, that finished runs clean up their record, and that veo runs/veo stop report
nothing left behind. It needs network access once for yt-dlp acquisition; it downloads no
third-party video. Unit tests require no network.
The published tarball only includes bin/, src/, package metadata, README and LICENSE.
The lockfile is kept for reproducible development. There is no build step. npm makes the
shebang-bearing bin executable on installation (on Unix, chmod +x bin/veo.js also enables
direct checkout execution).
Continuous integration runs the unit tests on Windows, macOS and Linux for Node 22 and 24, runs the end-to-end smoke test on Node 22, and verifies the packed tarball.
Modules separate argument parsing (src/cli.js), configuration (src/config.js,
src/config-errors.js, src/config-template.js), backend setup (src/backend.js),
backend updates (src/backend-update.js), download orchestration (src/downloader.js,
src/download-cache.js), retry jobs (src/jobs.js), playlists (src/playlist.js),
statistics, download history, the run registry and cleanup (src/stats.js,
src/history.js, src/runs.js, src/flush.js), diagnostics (src/doctor.js),
progress (src/progress.js), and helpers (src/utils.js, src/paths.js,
src/state.js, src/version.js) so new options and providers can be added without
replacing the CLI.
License
MIT for this CLI. yt-dlp and FFmpeg/FFprobe retain their respective upstream licenses.
Clickable paths
In supported terminals (Windows Terminal, iTerm2, WezTerm, Kitty, VS Code and recent VTE terminals), the Saved: path, veo config path and the config editor's header link to the containing folder. Use the terminal's link gesture, usually Ctrl+click or Cmd+click. The terminal and operating system decide whether file links open the file manager; unsupported terminals and redirected output show plain paths. Missing paths are not linked. JSON output remains plain.
