veodl 1.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,725 @@
1
+ # veo
2
+
3
+ A small, clean video-downloading CLI powered by [yt-dlp](https://github.com/yt-dlp/yt-dlp). No banners, just progress and the saved file path.
4
+
5
+ Automating with an agent? Start with the [agent guide](docs/AGENT_GUIDE.md) for JSON output, run inspection and audio verification.
6
+
7
+ **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.
8
+
9
+ ## Install and run
10
+
11
+ Requires **Node.js 22+** and npm. Internet access is needed for installation and first-use backend setup.
12
+
13
+ The npm package is `veodl`; the installed commands are `veo`, `veod`, and `veodl`.
14
+
15
+ ```bash
16
+ npx veodl "https://example.com/video.mp4"
17
+ ```
18
+
19
+ Or install globally:
20
+
21
+ ```bash
22
+ npm install -g veodl
23
+ veo "https://example.com/video.mp4"
24
+ # or: veod / veodl "https://example.com/video.mp4"
25
+ ```
26
+
27
+ The first download automatically prepares missing yt-dlp, FFmpeg and FFprobe.
28
+ On Android/Termux this includes installing the native packages and yt-dlp's
29
+ JavaScript support with `pkg install -y`. On desktop systems, missing media tools
30
+ are downloaded into veo's own cache. Run `veo doctor fix` to prepare everything
31
+ before downloading. Plain `veo doctor`, help and version do not install tools.
32
+
33
+ From this checkout, without publishing:
34
+
35
+ ```bash
36
+ npm install
37
+ node bin/veo.js --help
38
+ npm link
39
+ veo "https://example.com/video.mp4"
40
+ ```
41
+
42
+ `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.
43
+
44
+ If something does not work, run `veo doctor` first: it inspects the whole local setup and prints one line per check.
45
+
46
+ Run `veo` without arguments in an interactive terminal for a guided download: choose a
47
+ profile, enter a link, select video or audio, choose available resolution and output
48
+ directory, and start. The wizard checks the link and asks about playlist mode only when
49
+ it finds a collection; configured playlist and entry selection values are preselected.
50
+ Playlist mode also lists entries for selection. The wizard enables
51
+ resume and offers skipping previously downloaded files. With redirected input, use the
52
+ regular command arguments; veo never starts a prompt in a script.
53
+
54
+ ## Usage
55
+
56
+ ```text
57
+ veo <url> [<url>...] [options]
58
+
59
+ Options:
60
+ -q, --quality <quality> best, 2160p, 1440p, 1080p, 720p, 480p, 360p
61
+ -o, --output <path> Output directory (default: current working directory)
62
+ -r, --rename <name> Filename without extension; * inserts the original title
63
+ --closest-quality Nearest available resolution instead of an upper bound
64
+ --open Open the saved file with your default app
65
+ --audio Audio only (MP3 by default)
66
+ --format <format> Video: mp4, mkv, webm, mov
67
+ Audio: mp3, m4a, aac, opus, flac, wav
68
+ --recode Explicit video conversion (requires --format)
69
+ --playlist-concurrency <n> Parallel playlist entries, 1-4 (default: 2)
70
+ --playlist Download every entry of a playlist or channel URL
71
+ -N, --concurrent-fragments <n> Parallel fragments, 1-16 (default: 8)
72
+ --subs Download subtitles (default languages: en)
73
+ --sub-langs <langs> Subtitle languages, e.g. "de,en" (implies --subs)
74
+ --embed-subs Embed subtitles into the video file
75
+ --embed-metadata Embed title, date and other metadata
76
+ --embed-thumbnail Embed the thumbnail
77
+ --sponsorblock-remove <categories> e.g. "sponsor,selfpromo"
78
+ --section <range> Only a time range, e.g. "*10:00-12:00"
79
+ --cookies <file> Netscape cookie file, for content you may access
80
+ --cookies-from-browser <browser[:profile]>
81
+ --resume Keep partial data and continue an interrupted download
82
+ --list-formats Show available formats and exit
83
+ --dry-run Show what would be downloaded and exit
84
+ --json One JSON object per URL instead of prose
85
+ --profile <name> Use a named profile from the config file
86
+ --batch-file <file> Read one URL per line (blank lines and # comments ignored)
87
+ --retry-failed <file> Retry failed/unfinished downloads from a saved job
88
+ --playlist-items <list> Select entries, e.g. 1,3-5 (implies --playlist)
89
+ --skip-existing Skip matching downloads that are still on disk
90
+ --no-<boolean-option> Disable a default, e.g. --no-open or --no-audio
91
+ -v, --version Show installed version (also: veo version)
92
+ -h, --help Show help
93
+ ```
94
+
95
+ ```bash
96
+ veo "https://youtube.com/watch?v=VIDEO_ID" -q 1080p
97
+ veo "https://x.com/USER/status/STATUS_ID" -q best
98
+ veo "https://example.com/video.mp4" --audio
99
+ veo "https://example.com/video.mp4" --audio --format flac
100
+ veo "https://example.com/video.mp4" --format webm --recode -o ./videos
101
+ veo "https://example.com/video.mp4" -r "My Video" --format mp4 --open
102
+ veo "https://example.com/video.mp4" --audio -r "My Music"
103
+ veo <url1> <url2> <url3> --embed-metadata --subs
104
+ veo "https://youtube.com/playlist?list=LIST_ID" --playlist -q 720p
105
+ veo "https://example.com/video.mp4" --resume
106
+ npx veodl "https://example.com/video.mp4" --output ./downloads
107
+ ```
108
+
109
+ ### Several URLs and playlists
110
+
111
+ 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). More connections help only when the source and connection have spare capacity. Video format changes preserve the encoded streams unless `--recode` is explicitly enabled; audio extraction and precise section cuts retain their existing conversion behavior.
112
+
113
+ - Pass any number of URLs; up to two run concurrently by default. Use
114
+ `--concurrent-downloads 1` for sequential processing. A failure is reported
115
+ with its URL and does not stop the remaining URLs; the exit status is `1` if anything
116
+ failed.
117
+ - Playlists, channels and other collections are refused by default with a hint. Add
118
+ `--playlist` to download every entry. Each entry keeps its own title; `--rename` applies
119
+ a name prefix per entry (`My Name - 001`), or substitutes each title with `movie_*`.
120
+ - Use `--playlist-items 1,3-5` to select entries by their original, one-based index.
121
+ Playlist entries run with bounded parallelism: a failed entry does not discard successful
122
+ files or stop the remaining entries. Before downloading, veo displays the selected count
123
+ and a size estimate when the source supplies sizes. Unknown sizes are labeled explicitly;
124
+ metadata estimates do not predict conversion size.
125
+ - `--batch-file links.txt` accepts a UTF-8 URL list, optionally alongside URLs on the command
126
+ line. A final summary counts saved, skipped and failed videos. Successful files are opened
127
+ with `--open` even if another URL or playlist entry fails.
128
+ - Failed or cancelled jobs print a ready-to-use `veo --retry-failed "<job.json>"` command.
129
+ - Use `veo retry --last` to retry the newest failed or unfinished job without copying its path. Active runs are ignored; use `veo runs` to inspect them.
130
+ Jobs are stored under the per-user veo cache's `jobs` directory. Retries retain resolved
131
+ output directories and settings, even from a different working directory, and explicit
132
+ flags can override them. Completed playlist jobs retry only failed indices; interrupted
133
+ playlists revisit their selection and skip completed entries through resume history.
134
+ Playlist indices refer to the playlist's current order, so changes to that order can
135
+ change what an index selects. Cookie paths and browser-session settings are not saved in
136
+ retry jobs; pass those flags again if needed. Job files contain URLs, settings and local
137
+ file paths and can be deleted when no longer needed.
138
+
139
+ ```bash
140
+ veo --batch-file links.txt --profile archive
141
+ veo "https://example.com/playlist" --playlist-items 1,3-5 --resume
142
+ veo "https://example.com/playlist" --playlist --skip-existing
143
+ veo --retry-failed "C:\path\to\job.json"
144
+ veo retry --last
145
+ ```
146
+
147
+ ### Quality
148
+
149
+ - Numeric qualities are an **upper bound**: `-q 720p` downloads the best resolution at or
150
+ below 720p, so a metered connection can never accidentally receive a 2160p file. If a
151
+ source offers nothing at or below the request, veo stops before downloading and lists
152
+ the resolutions that do exist.
153
+ - `--closest-quality` restores the former "nearest available resolution" rule, which may
154
+ pick a resolution *above* the request. It needs a numeric `--quality`.
155
+ - Sources that report no resolution metadata fall back to the best available stream.
156
+ Collections resolve and enforce the quality limit separately for each entry.
157
+ - Other positive numeric resolutions, such as `-q 540p`, are also accepted. The interactive
158
+ wizard offers the resolutions reported by the source; without resolution metadata it
159
+ offers `best` rather than inventing available streams.
160
+ - `--quality` does not apply to audio.
161
+ - MP4-compatible codecs are preferred at the selected resolution; merged video prefers MP4
162
+ with MKV fallback. A single-file source may retain its original container. Use
163
+ `--format mp4` to require MP4 through lossless remuxing. Incompatible codecs cause an error.
164
+ - `--format` on video remuxes without re-encoding. Add `--recode` to explicitly allow conversion, which may be slow or lossy. Audio formats require `--audio`.
165
+
166
+ ### Playback compatibility
167
+
168
+ H.264/AAC sources are preferred at the selected quality, including explicit MP4 output.
169
+ Original codecs are retained by default; a playback note identifies media needing extra
170
+ player codecs. MP4 is a container, not a promise of a particular codec.
171
+
172
+ Use `veo URL --compatible` to ensure MP4 with H.264 8-bit 4:2:0 and AAC.
173
+ Already compatible streams are copied; only incompatible streams are encoded (H.264 CRF 18,
174
+ AAC 192 kbit/s), which takes time and may lose quality. HDR requiring video conversion
175
+ is refused rather than silently losing correct colors. Embedded subtitles/thumbnails are
176
+ not supported in this mode; separate subtitle files remain supported.
177
+ New configs include a `kompatibel` profile; existing configs receive a commented profile
178
+ example through `veo config edit`. Enable it with `veo URL --profile kompatibel`.
179
+
180
+ ### Download controls and diagnostics
181
+
182
+ - URL lists and batch files use `--concurrent-downloads 2` by default (range 1–4).
183
+ Each playlist independently uses `--playlist-concurrency 2` (range 1–4).
184
+ The limits multiply when several playlists run at once. JSON emits one complete
185
+ object per URL in completion order; the `url` field identifies each result.
186
+ - Adaptive concurrency is on by default. After the backend's own retries are exhausted,
187
+ HTTP 429/5xx and temporary connection failures trigger at most two further attempts,
188
+ after 2 and 4 seconds. Fragment parallelism and future URL/playlist workers are reduced
189
+ for the rest of the run. Active downloads finish normally; format and quality stay fixed.
190
+ Partials are continued on retry. Use `--no-adaptive-concurrency` to disable this.
191
+ - `--check-space` checks cache and destination free space before media transfer. Known
192
+ sizes include conservative room for merge output and a destination copy, plus space
193
+ reserved by concurrent downloads in the same process. Unknown sizes are reported as
194
+ unknown; conversion sizes and other processes' disk usage cannot be predicted exactly.
195
+ Use `--no-check-space` to disable this conservative check.
196
+ - `--timings` shows elapsed time for setup, metadata, download/backend, processing,
197
+ saving and retry waits. Download/backend includes the backend's own startup and
198
+ extraction overhead; these are elapsed timings, not a diagnosis of network speed.
199
+ JSON results include `timings` in milliseconds, or `entryTimings` for playlists.
200
+ Use `--no-timings` to hide/omit them.
201
+ - Terminal details are gray, titles bold, saves green and errors red. The same styling
202
+ applies to stats, history, runs/stop, doctor, flush, updates, config prose, help and
203
+ interactive prompts. For example, `veo stats --no-color` disables it for one command. Redirected output
204
+ and JSON have no color escapes. `--no-color`, `"color": false`, `NO_COLOR`,
205
+ and `TERM=dumb` disable colors.
206
+ This can be set independently per profile, for example
207
+ `"profiles": { "default": { "color": true }, "plain": { "color": false } }`.
208
+ Use `veo stats --profile plain` or `veo URL --profile plain`; `--color` or
209
+ `--no-color` overrides the profile for one invocation (except `NO_COLOR`).
210
+ External npm/editor output and Node runtime warnings keep their own formatting.
211
+
212
+ ### Filename templates and folders
213
+
214
+ `--filename-template` controls the name without extension; `--folder-template`
215
+ creates relative subfolders under `--output`. Both support `{title}`, `{id}`,
216
+ `{channel}`, `{year}`, `{playlist}` and `{index}` (three-digit playlist index).
217
+ Missing metadata uses readable fallbacks. `{year}` is the upload year when available.
218
+ Subtitles follow the media name. Existing files are never overwritten. Templates cannot
219
+ escape the output folder, and symlink/junction subfolders are refused. Use forward slashes
220
+ for folders. `--rename` and `--filename-template` are mutually exclusive.
221
+
222
+ ```powershell
223
+ veo "VIDEO-URL" --folder-template "{channel}/{year}" --filename-template "{title} - {id}"
224
+ veo "PLAYLIST-URL" --playlist --folder-template "{playlist}" --filename-template "{index} - {title}"
225
+ veo config check
226
+ veo config check --profile fast
227
+ veo config show --profile fast
228
+ ```
229
+
230
+ Config checks validate every effective profile (or the selected one), including unknown
231
+ settings, ranges and conflicting options. Show prints merged built-in, global and profile
232
+ settings as JSON, with credential paths/browser profiles redacted. Neither command contacts
233
+ video sites. All new options are also documented as commented examples in `veo config edit`.
234
+
235
+ ### Files, names and resume
236
+
237
+ - Original titles are preserved by default. Use `-r "My Video"` or `--rename "My Video"`
238
+ for video or audio. Supply the name **without an extension**; the actual media extension
239
+ is appended automatically. Use `-o` for the directory. Invalid filename characters are
240
+ sanitized and overly long names shortened. Existing files are never intentionally
241
+ overwritten: duplicates get ` (1)`, ` (2)`, etc.
242
+ - Use `-r "movie_*"` or `"rename": "movie_*"` in your config/profile to insert the
243
+ original title: `My Film` becomes `movie_My Film.mp4`. Every `*` is substituted.
244
+ Quote the pattern in your shell. Patterns also work with URL batches and playlists.
245
+ - Subtitles and thumbnails are saved beside the media file under the same base name
246
+ (`My Video.mp4` → `My Video.en.vtt`).
247
+ - Downloads and media processing first finish in the local per-user veo cache, under
248
+ `downloads` (`%LOCALAPPDATA%\veo\downloads` on Windows). The destination is only written
249
+ after the media is ready. Saving uses a hard link when supported or an exclusive copy
250
+ across drives/cloud mounts; copied file sizes are checked. Existing files are never
251
+ overwritten. The local original is removed only after files and history are saved.
252
+ Local disk space is therefore needed for the complete download and processing files.
253
+ - If saving fails or is cancelled after media processing completes, the local original and
254
+ sidecars are kept for **15 minutes**, even without `--resume`. Repeat the same command or
255
+ use the printed `--retry-failed` command to retry the transfer. Individual completed
256
+ downloads can be recovered without contacting their source again. The same source,
257
+ media settings and destination identify a cached transfer; changing those starts a new
258
+ download. Expired copies are removed on the next veo invocation, not by a background
259
+ timer while veo is closed. Active transfers are never expired. Another failed transfer
260
+ starts a fresh 15-minute retention period.
261
+ - `--resume` also keeps unfinished downloads in local `.veo-part-<request-hash>` folders
262
+ until resumed; unfinished data has no automatic expiry. Without `--resume`, unfinished
263
+ downloads are discarded on failure or cancellation. The hash separates source URLs,
264
+ playlist entries, destinations, quality, media type, format, sections and subtitle/metadata
265
+ options. A manifest records backend-confirmed
266
+ completion and saved files, so an unprocessed file is not mistaken for a finished video.
267
+ Each playlist entry has its own state. Completed entries survive later failures and are
268
+ skipped when resuming the same selection. Partial downloads are never resumed without
269
+ `--resume`. Legacy partial folders in the output directory are left untouched; the new
270
+ local cache does not automatically migrate them.
271
+ - A lock prevents two resume processes from using the same partial folder. Normal failures
272
+ and Ctrl+C release it. After a force kill, remove the named `.lock` file only after making
273
+ sure no veo process still uses that folder.
274
+ - Successful downloads record their source, output settings and saved paths in
275
+ `.veo-history` inside the output directory. `--skip-existing` uses these records and checks
276
+ that the files still exist. A different quality/format or deleted output is downloaded
277
+ again. Files downloaded before this history existed are not recognized automatically.
278
+ Deleting history removes duplicate detection, not downloaded media. Records whose files
279
+ no longer exist are removed on the next download into that folder, and `.veo-history`
280
+ itself is removed with its last record; `veo doctor` reports stale records.
281
+
282
+ ### Metadata and subtitles
283
+
284
+ - `--subs` writes subtitle files, `--sub-langs de,en` selects languages, and `--embed-subs`
285
+ embeds them into the video container instead.
286
+ - `--embed-metadata` and `--embed-thumbnail` use FFmpeg to write metadata and cover art.
287
+ - `--sponsorblock-remove sponsor,selfpromo` cuts sponsor segments (YouTube) and requires
288
+ FFmpeg; the cut re-encodes the affected parts.
289
+ - `--section "*10:00-12:00"` downloads only a time range.
290
+ - `-N 8` downloads several fragments in parallel, which is noticeably faster on HLS/DASH
291
+ sources and heavier on the network.
292
+
293
+ ### Access and credentials
294
+
295
+ veo downloads publicly accessible content. It does not bypass access controls, and it does
296
+ not expose options that defeat them. If you need your own session for content you are
297
+ authorized to view:
298
+
299
+ - `--cookies ./cookies.txt` passes a Netscape-format cookie file. The file is validated
300
+ before any network work, and on Unix a file readable by other users produces a warning.
301
+ - `--cookies-from-browser firefox:Work` reads cookies from an installed browser: `brave`,
302
+ `chrome`, `chromium`, `edge`, `firefox`, `opera`, `safari`, `vivaldi`, `whale`, optionally
303
+ with `+gnomekeyring`/`+kwallet`/`+basic` and a profile or container.
304
+
305
+ Credentials are used for that single invocation only and are never stored by veo. When a
306
+ download fails because a login is required, the error message points at these flags.
307
+
308
+ ### Inspection and scripting
309
+
310
+ - `--list-formats` prints the backend's own format table for one URL and exits.
311
+ - `--dry-run` prints the title, the resolved quality and the destination path that would be
312
+ used — without creating the output directory or downloading anything.
313
+ - `--json` prints one JSON object per line on stdout:
314
+ `{"url":…,"status":"saved","title":…,"files":[…]}` or
315
+ `{"url":…,"status":"failed","error":…}`. Progress and status still go to stderr. Without
316
+ `--json`, every saved file is printed as `Saved: <path>` on stdout.
317
+ Duplicate detection can return `status: "skipped"`; cancellation returns `"cancelled"`.
318
+ Playlist results also include `saved`, `skipped` and `failures` (with original indices).
319
+ A failed or cancelled playlist can still report files saved before the failure.
320
+ - `veo runs [id] --json` reports active run metadata and per-item progress. Finished attempts
321
+ remain available through `veo history --json`.
322
+ - `veo inspect <file> --json` reports local container and stream metadata. Add
323
+ `--check-audio` to decode all audio tracks and measure whether any peak exceeds
324
+ -60 dBFS. A track can exist without a detectable signal. The check reads the full
325
+ media file and uses only local FFmpeg/FFprobe tools.
326
+ - Download results and history retain a `runId`. `veo inspect run <id> --json` reads
327
+ a finished run and checks every saved file before probing it. Renamed media is
328
+ found by file identity, size and a sampled SHA-256 fingerprint within the original output tree;
329
+ add `--search <directory>` after moving it elsewhere. Ambiguous or missing files
330
+ are reported without probing another file. Old runs lack this persistent record.
331
+ Finished run records live in the current device's veo cache. On Windows, macOS,
332
+ Linux and Termux, `inspect` uses bundled media tools where supported and otherwise
333
+ uses local `ffmpeg` and `ffprobe` (or `VEO_FFMPEG_PATH`); it does not install them.
334
+ Copying only the media to another device does not copy its run ID record.
335
+
336
+ ### Config file
337
+
338
+ Defaults can be stored in a config file, so a long list of flags is not needed for every
339
+ call. `veo doctor` prints the exact path; `VEO_CONFIG` overrides it.
340
+ The file accepts JSON with `//` line comments and `/* ... */` block comments. Strings
341
+ (including URLs and Windows paths) keep their normal JSON escaping rules. Trailing commas
342
+ are not allowed.
343
+
344
+ - Windows: `%APPDATA%\veo\config.json`
345
+ - macOS: `~/Library/Application Support/veo/config.json`
346
+ - Linux: `$XDG_CONFIG_HOME/veo/config.json` (or `~/.config/veo/config.json`)
347
+
348
+ ```json
349
+ {
350
+ "output": "D:\\Videos",
351
+ "quality": "1080p",
352
+ "embedMetadata": true,
353
+ "subLangs": "de,en",
354
+ "concurrentFragments": 4
355
+ }
356
+ ```
357
+
358
+ Supported keys: `output`, `quality`, `format`, `rename`, `audio`, `open`, `resume`,
359
+ `closestQuality`, `cookies`, `cookiesFromBrowser`, `playlist`, `concurrentFragments`, `playlistConcurrency`, `recode`, `compatible`, `concurrentDownloads`, `adaptiveConcurrency`,
360
+ `filenameTemplate`, `folderTemplate`, `checkSpace`, `timings`, `color`,
361
+ `subs`, `subLangs`, `embedSubs`, `embedMetadata`, `embedThumbnail`, `sponsorblockRemove`,
362
+ `section`, `json`. An explicit command-line flag always wins over a stored default. An
363
+ unknown key produces a warning; invalid JSON or a wrong value type is an error, because
364
+ silently ignoring a typo would be worse.
365
+
366
+ Additional defaults are `skipExisting` and `playlistItems`. Boolean defaults can be disabled
367
+ with `--no-open`, `--no-audio`, `--no-resume`, `--no-embed-metadata`, etc. `--no-subs` also
368
+ disables stored subtitle languages and subtitle embedding for that invocation.
369
+
370
+ ### Named profiles
371
+
372
+ If no profile is selected, `profiles.default` is applied automatically. You can also select
373
+ it explicitly with `--profile default`. Other named profiles use global defaults rather
374
+ than inheriting `default`. The wizard preselects `default` when it exists.
375
+ `veo config edit` adds an empty `default` profile to existing configurations if missing,
376
+ preserving existing settings. An empty profile does not change download behavior.
377
+
378
+ ```json
379
+ {
380
+ "output": "D:\\Videos",
381
+ "profiles": {
382
+ "default": { "quality": "1080p", "resume": true },
383
+ "music": { "audio": true, "format": "mp3", "output": "D:\\Music" },
384
+ "archive": { "quality": "1080p", "embedMetadata": true, "subLangs": "de,en" }
385
+ }
386
+ }
387
+ ```
388
+
389
+ `veo <url> --profile music` merges global defaults, then the selected profile, then explicit
390
+ CLI flags. `veo config profiles` lists profile names; `veo config path` prints the file path.
391
+ `veo config edit` fills new or empty files with a commented template explaining common
392
+ options and example profiles. Existing files receive a commented reference guide once;
393
+ their settings remain intact. In an interactive terminal, it opens the built-in editor
394
+ unless `VISUAL` or `EDITOR` is configured (an executable path, without shell arguments).
395
+ The editor provides syntax colors, line numbers and live validation. Syntax errors mark
396
+ the affected line; unknown properties and invalid values are highlighted directly, with
397
+ spelling suggestions, expected types and allowed values in the status area. Validation uses
398
+ the same rules as the CLI, including profile overrides and audio/video formats.
399
+ Press F2 on a property or value to open suggestions, use Up/Down to choose, Enter to apply
400
+ or Esc to cancel. Suggestions require valid JSON syntax; free-text values and custom numeric
401
+ resolutions such as `900p` remain supported. Ctrl+S validates and saves;
402
+ Esc or Ctrl+Q exits, asking before discarding changes. Use arrows, Home/End and PageUp/PageDown
403
+ to navigate. Invalid configurations cannot be saved. `--no-color` disables syntax colors.
404
+ In terminals supporting SGR mouse reporting, left-click positions the cursor and dragging
405
+ selects text, including across lines. Backspace/Delete removes the selection; typing replaces
406
+ it. The mouse wheel scrolls without changing the editing position; Ctrl+Up/Down also scrolls.
407
+ Ctrl+C copies selected text to the system clipboard, and Ctrl+V pastes clipboard text at the
408
+ cursor or replaces the selection. Ctrl+Q or Esc exits the editor. The editor disables mouse
409
+ reporting again when it exits. Keyboard editing remains available in terminals without mouse
410
+ support.
411
+ Clipboard access uses the system clipboard on Windows, `pbcopy`/`pbpaste` on macOS, and
412
+ `wl-clipboard` (Wayland), `xclip` or `xsel` (X11) on Linux. Linux requires one of those tools
413
+ and access to a graphical session. The editor reports when no clipboard is available; terminal
414
+ paste shortcuts can still insert text directly when supported by the terminal.
415
+ On Termux, editor clipboard shortcuts use `termux-clipboard-set`/`termux-clipboard-get`
416
+ when the Termux:API app and `termux-api` package are installed.
417
+
418
+ Use `veo config edit --external` to use `VISUAL`, then `EDITOR`, then Notepad on Windows
419
+ or `vi` elsewhere. `veo config edit --terminal` explicitly selects the built-in editor.
420
+ Set `VEO_CONFIG_EDITOR=external` to disable the built-in editor by default (`auto` restores
421
+ automatic selection, `terminal` forces it). For a persistent Windows preference:
422
+
423
+ ```powershell
424
+ [Environment]::SetEnvironmentVariable('VEO_CONFIG_EDITOR', 'external', 'User')
425
+ $env:VEO_CONFIG_EDITOR = 'external'
426
+ ```
427
+
428
+ Non-interactive sessions use the external editor unless `--terminal` is explicitly selected,
429
+ in which case an interactive-terminal error is reported. The wizard also offers configured profiles.
430
+ Generated templates and app messages are in English. Previously generated German template
431
+ comments are translated the next time you run `veo config edit`; existing profile names,
432
+ paths and custom comments are preserved.
433
+
434
+ ### Terminal output
435
+
436
+ - During a download, the terminal/tab title shows `veo | 50% | My Video` (original title or
437
+ your `-r` name), plus setup/processing status. It ends with `Done`, `Failed`, or
438
+ `Cancelled`; the shell may replace it at the next prompt. This uses the native console
439
+ title on Windows (including PowerShell/Windows Terminal) and OSC title sequences on
440
+ compatible Linux/macOS terminals. Titles are not changed when stderr is redirected or
441
+ `TERM=dumb`. Terminal settings that enforce a fixed tab title can override this feature.
442
+ - Progress shows percentage, speed, downloaded/total size and ETA on stderr; unknown values
443
+ appear as `?`. Separate audio/video streams each have their own progress. Non-interactive
444
+ output is throttled.
445
+ - Progress labels include the current item (`[3/12]`), source title, video/audio/media stream,
446
+ postprocessing (merging, conversion, subtitles or metadata) and saving. Stream percentages
447
+ describe that stream, not the entire multi-step job. Sources without codec metadata use
448
+ the neutral `Media` label.
449
+ - `--open` launches the completed file in your default app, including renamed files and
450
+ audio. With several URLs, the primary file of each URL is opened. Uses `explorer.exe` on
451
+ Windows, `open` on macOS, and `xdg-open` on Linux (requires a graphical desktop,
452
+ xdg-utils and a file association). The CLI does not wait for the player to close. If the
453
+ opener cannot be launched, a warning is printed and the successful download still exits
454
+ with `0`; later errors inside the detached opener/player are not monitored.
455
+ - After each successful download, veo checks the npm registry at most once per day for a
456
+ newer version and prints a one-line notice on stderr (never on failure). Disable it with
457
+ `VEO_NO_UPDATE_CHECK=1`; `VEO_REGISTRY`/`npm_config_registry` are respected.
458
+ - Exit status is `0` on success, `1` on errors, and `130` on cancellation. With several
459
+ URLs, `1` means at least one URL failed.
460
+
461
+ ## Commands
462
+
463
+ ```bash
464
+ veo version # installed version (also: veo --version or veo -v)
465
+ veo # interactive download wizard (terminal only)
466
+ veo config edit # create/open config, including example profiles
467
+ veo config reset # confirm with y; back up config and restore the current template
468
+ veo config profiles # list available profiles
469
+ veo config path # show the config file location
470
+ veo doctor # diagnose the local setup; exit 1 if a check fails
471
+ veo doctor fix # restore missing or damaged managed tools
472
+ veo doctor --offline # skip the network checks
473
+ veo stats # persistent download totals; --json for scripting
474
+ veo history # the last 5 downloads; --json for scripting
475
+ veo history --failed --limit 20 # recent failed/cancelled attempts
476
+ veo retry --last # retry the newest failed or unfinished job
477
+ veo runs # active runs with their id; veo runs <id> for details
478
+ veo runs --json # machine-readable active run metadata
479
+ veo inspect FILE --check-audio --json # track metadata and audio signal
480
+ veo inspect run ID --check-audio --json # finished run and saved media
481
+ veo stop [id] # stop one run, or every active run
482
+ veo flush # stop runs, clear temporary downloads and retry jobs
483
+ veo flush --stats # the same, and reset the statistics
484
+ veo update # install the latest veo with npm
485
+ veo update --check # only check for a newer veo
486
+ veo upgrade # alias for veo update
487
+ veo check update # alias for veo update --check
488
+ veo backend update # install the newest yt-dlp release
489
+ veo backend reset # back to the release pinned in this veo version
490
+ ```
491
+
492
+ `veo update` runs `npm install -g veodl@latest` and then removes yt-dlp backend
493
+ caches from older pinned releases, keeping an explicitly installed backend release. It
494
+ never uses a shell on Linux/macOS, passes fixed arguments only, and prints the manual npm
495
+ command on any failure. The registry can be overridden with `VEO_REGISTRY` (or npm's
496
+ `npm_config_registry`) for mirrors and proxies.
497
+
498
+ ### `veo flush`
499
+
500
+ Run `veo flush` to stop active veo runs started with this version, then remove
501
+ temporary local downloads, including the 15-minute retained files and unfinished
502
+ resume data, and cached retry job JSON files. Those jobs can no longer be retried.
503
+ Saved media, output history, config/profiles, the `veo history` list, finished-run
504
+ records and backend binaries are kept.
505
+ Cleanup waits for cancellation; if a run cannot stop, it fails without deleting
506
+ download or job files. Locked folders from older or interrupted processes are
507
+ skipped and reported. Only veo's own per-user cache is cleaned. Statistics are
508
+ preserved unless `--stats` is given: `veo flush --stats` resets them too.
509
+ To end runs without removing their downloads and jobs, use `veo stop [id]` instead.
510
+
511
+ ### `veo stats`
512
+
513
+ Shows saved videos/audio, failed attempts, skips, cancellations and total time spent
514
+ on download requests (including preparation, processing and saving). Parallel run
515
+ times are added together. Playlist entries count individually; retries are new
516
+ attempts. Active requests are recorded when they finish. Tracking starts with this
517
+ version; previous downloads are not imported. `veo stats --json` returns the totals
518
+ as JSON. Statistics contain counters and timestamps, not URLs or filenames.
519
+
520
+ ### `veo history`
521
+
522
+ Shows the **last 5 download attempts** by default, newest first, with title, status, media type,
523
+ date, duration, URL and the saved files. Saved, skipped, failed and cancelled items are
524
+ recorded, including the reason a failure was reported; playlist entries and retried
525
+ attempts count individually. Active downloads appear once they finish. Long file lists
526
+ are summarized in the text view.
527
+
528
+ Use `veo history --limit N` to show 1-1000 attempts, or `veo history --failed` to
529
+ show only failed and cancelled attempts; the flags can be combined. Failed attempts
530
+ include their exact retry-job command when one was recorded. New attempts also show
531
+ their persistent run ID for `veo inspect run <id>`. `veo history --json`
532
+ prints `{"count":N,"entries":[…]}` for scripting. Each entry has
533
+ `at`, `url`, `title`, `status`, `media` (`video`/`audio`), `quality`, `format`, the
534
+ complete `files` list, `error`, `elapsedMs` and an optional `job` path. Titles and paths are stored without
535
+ terminal control characters.
536
+
537
+ History is one small JSON file per attempt in the per-user veo cache's `history`
538
+ directory, so parallel runs cannot overwrite each other and each record stays small. The
539
+ directory keeps one file per attempt and contains URLs, titles and local file paths;
540
+ delete it to remove those records. `veo flush` keeps the list (only `veo flush --stats`
541
+ resets statistics, not history).
542
+
543
+ ### `veo runs` and `veo stop`
544
+
545
+ Every run registers itself while it works, under a **6-character id**, and removes that
546
+ record when it ends:
547
+
548
+ ```bash
549
+ veo runs # active runs: id, PID, state, start time, progress, output directory
550
+ veo runs k3f9qa # one run in detail: URLs, settings, job file, per-item state
551
+ veo stop k3f9qa # ask that run to stop and wait until it exits
552
+ veo stop # stop every active run
553
+ ```
554
+
555
+ `veo runs` lists the runs of this user, including those started in another terminal, and a
556
+ run disappears from the list as soon as it finishes. Progress such as
557
+ `1/3 done, 1 running` is read from the run's job file, which is created before the first
558
+ download starts, so a run that is still preparing the backend shows `starting`. `veo stop
559
+ <id>` writes a stop request that the run itself polls, so it needs no signals or PIDs and
560
+ works the same on every platform. A stopped run exits like Ctrl+C (`130`) and keeps its
561
+ partial data and retry job, so the printed `veo --retry-failed` command still works —
562
+ unlike `veo flush`, which also removes that data. If a run does not stop within 15 seconds,
563
+ `veo stop` reports it and exits with `1`.
564
+
565
+ A run that crashed without cleaning up leaves its record behind: `veo runs` marks it
566
+ `stale` and `veo stop` removes it, with or without its id. Records contain the id, process
567
+ id, start time, URLs, output directory and job path — never cookie files, browser sessions
568
+ or other credentials. Unknown or damaged record files are ignored instead of breaking veo,
569
+ because a single stray file in the cache must never stop later runs.
570
+
571
+ ### `veo doctor`
572
+
573
+ Prints one line per check: `ok`, `warn` or `fail`. It inspects Node.js, the platform, the
574
+ output directory, the backend cache, yt-dlp (including the SHA-256 of the cached binary and
575
+ the version it reports), FFmpeg/FFprobe, a system FFmpeg fallback, the config file, leftover
576
+ partial downloads, stale duplicate-detection records, the npm registry and the yt-dlp release host. It downloads no backend and
577
+ only creates its own probe files plus the backend cache directory. Exit status is `1` when
578
+ at least one check fails.
579
+
580
+ Run `veo doctor fix` to restore missing or damaged managed tools and check again.
581
+ It stages bundled FFmpeg/FFprobe and downloads verified yt-dlp when needed. Missing
582
+ desktop media tools are installed in veo's cache using npm and the pinned media
583
+ packages. On Termux it installs missing native tools with the package manager.
584
+ With `--offline`, it only uses local binaries; missing tools are reported for an online retry.
585
+ Use `-o PATH` to create and check an output directory. Config errors and invalid overrides
586
+ are reported for manual correction; PATH and config values are never rewritten.
587
+ A missing system FFmpeg is not a warning when the selected media tools work.
588
+
589
+ ## Supported sites and backend
590
+
591
+ YouTube, X/Twitter, TikTok, Vimeo, Reddit, Instagram, and [many other yt-dlp sites](https://github.com/yt-dlp/yt-dlp/blob/master/supportedsites.md) 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.
592
+
593
+ 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 `ffmpeg-static@5.3.0` 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.
594
+
595
+ 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.
596
+
597
+ `ffmpeg-static` and `ffprobe-static` publish no Windows ARM64 binaries. veo no longer refuses
598
+ that platform: it uses an FFmpeg/FFprobe pair found on `PATH`, including the usual
599
+ WinGet, Chocolatey and `C:\ffmpeg\bin` locations, or automatically downloads x64
600
+ media tools for Windows 11's x64 emulation.
601
+
602
+ ### Android / Termux
603
+
604
+ With Node.js 22+ and npm already installed in Termux:
605
+
606
+ ```sh
607
+ npm install -g veodl
608
+ veo "URL"
609
+ ```
610
+
611
+ On Android, veo automatically finds `yt-dlp`, `ffmpeg` and `ffprobe` on PATH
612
+ (also checking `$PREFIX/bin`). If tools or the `yt-dlp-ejs` Python module are
613
+ missing, it runs `pkg install -y python-yt-dlp yt-dlp-ejs ffmpeg` automatically,
614
+ then checks the installed programs before continuing the download. Python is
615
+ installed as a dependency. Termux with the pacman package manager uses
616
+ `--noconfirm` instead. This needs a working Termux repository and internet access.
617
+ No root access is needed. Later runs reuse the installed tools.
618
+
619
+ Static FFmpeg dependencies are optional; `--omit=optional` can be added to the
620
+ npm install command to skip attempting those desktop packages entirely.
621
+ The system yt-dlp is maintained by Termux and is
622
+ not pinned or hash-verified by veo. Explicit `VEO_YT_DLP_PATH` and
623
+ `VEO_FFMPEG_PATH` overrides still take precedence.
624
+
625
+ `veo doctor fix` also performs this automatic setup; `veo doctor fix --offline`
626
+ never invokes a package manager. Update Termux packages with `pkg upgrade`;
627
+ `veo backend update` prints this platform's update instructions.
628
+
629
+ To save files in Android's shared Downloads folder, run `termux-setup-storage`,
630
+ grant the storage permission, then use `veo "URL" -o ~/storage/downloads`.
631
+ Keep veo and its tools in Termux's private storage.
632
+
633
+ For existing published veo 1.6.1 installations (before this automatic setup), run:
634
+
635
+ ```sh
636
+ pkg install python-yt-dlp yt-dlp-ejs ffmpeg
637
+ export VEO_YT_DLP_PATH="$(command -v yt-dlp)"
638
+ export VEO_FFMPEG_PATH="$PREFIX/bin"
639
+ veo doctor
640
+ ```
641
+
642
+ These exports apply to the current shell; add them to your shell startup file
643
+ if you need them on subsequent launches of 1.6.1.
644
+
645
+ ### Advanced overrides
646
+
647
+ - `VEO_YT_DLP_PATH`: absolute path to a trusted, current yt-dlp executable. It always takes
648
+ precedence over the managed backend, including one installed by `veo backend update`.
649
+ - `VEO_FFMPEG_PATH`: directory containing both `ffmpeg` and `ffprobe` executables (with
650
+ `.exe` on Windows).
651
+ - `VEO_CONFIG`: path to the config file.
652
+
653
+ Automatic setup covers Windows x64/ia32/ARM64, macOS x64/ARM64, Linux x64/ARM64,
654
+ and Android through Termux. Windows ARM64 media tools require x64 emulation.
655
+ `--open` uses `termux-open` in Termux; another Android app must be available to view the file.
656
+ Other systems should supply trusted native binaries. Node.js 22+, npm, internet
657
+ access, executable private storage and working OS libraries are prerequisites;
658
+ network blocks or unavailable repositories can still prevent setup. Android's
659
+ shared-storage permission must be granted by the user. Maintainers should update
660
+ the pinned yt-dlp release and hashes as websites change,
661
+ or users can install a newer release themselves.
662
+
663
+ ### Installing a newer backend
664
+
665
+ ```bash
666
+ veo backend update --check # is a newer yt-dlp release available?
667
+ veo backend update # install it
668
+ veo backend reset # forget it, use the pinned release again
669
+ ```
670
+
671
+ The pinned backend is hash-verified against values compiled into this veo version. That
672
+ guarantee cannot cover a release that did not exist when this version was built, so
673
+ `veo backend update` verifies the download against the `SHA2-256SUMS` file published with
674
+ that release over HTTPS: the trust anchor becomes HTTPS and GitHub instead of the npm
675
+ package. It is opt-in, it says so on stderr when it runs, and `veo backend reset` (plus the
676
+ next `veo update`) returns to the pinned release. An installed release is used only while it
677
+ is newer than the pinned one and its bytes still match the recorded hash; anything else
678
+ silently falls back to the pinned release.
679
+
680
+ ## Develop and publish
681
+
682
+ ```bash
683
+ npm install
684
+ npm test # unit tests, no network
685
+ npm run test:smoke # real downloads: local HTTP server, real yt-dlp, real FFmpeg
686
+ npm run test:open # real download plus a controlled desktop-opener replacement
687
+ npm pack
688
+ npm run check:package # inspect the packed tarball
689
+ npm link
690
+ veo --version
691
+ # Then, authenticated as the account that owns the name:
692
+ npm publish
693
+ ```
694
+
695
+ The smoke test generates a two-second synthetic video with FFmpeg, serves it on loopback,
696
+ and runs real yt-dlp downloads. It checks default output, the quality cap, duplicate names,
697
+ audio extraction, conversion, dry-run, format listing, JSON output, metadata embedding,
698
+ resume, batch behaviour, playlist selection and nonzero failures, and verifies that a
699
+ finished download whose destination was unavailable is retried from the local cache
700
+ without contacting the source again. It also confirms that real runs leave `veo history`
701
+ entries, that finished runs clean up their record, and that `veo runs`/`veo stop` report
702
+ nothing left behind. It needs network access once for yt-dlp acquisition; it downloads no
703
+ third-party video. Unit tests require no network.
704
+
705
+ The published tarball only includes `bin/`, `src/`, package metadata, README and LICENSE.
706
+ The lockfile is kept for reproducible development. There is no build step. npm makes the
707
+ shebang-bearing bin executable on installation (on Unix, `chmod +x bin/veo.js` also enables
708
+ direct checkout execution).
709
+
710
+ Continuous integration runs the unit tests on Windows, macOS and Linux for Node 22 and 24,
711
+ runs the end-to-end smoke test on Node 22, and verifies the packed tarball.
712
+
713
+ Modules separate argument parsing (`src/cli.js`), configuration (`src/config.js`,
714
+ `src/config-errors.js`, `src/config-template.js`), backend setup (`src/backend.js`),
715
+ backend updates (`src/backend-update.js`), download orchestration (`src/downloader.js`,
716
+ `src/download-cache.js`), retry jobs (`src/jobs.js`), playlists (`src/playlist.js`),
717
+ statistics, download history, the run registry and cleanup (`src/stats.js`,
718
+ `src/history.js`, `src/runs.js`, `src/flush.js`), diagnostics (`src/doctor.js`),
719
+ progress (`src/progress.js`), and helpers (`src/utils.js`, `src/paths.js`,
720
+ `src/state.js`, `src/version.js`) so new options and providers can be added without
721
+ replacing the CLI.
722
+
723
+ ## License
724
+
725
+ MIT for this CLI. yt-dlp and FFmpeg/FFprobe retain their respective upstream licenses.