veodl 1.9.0 → 1.10.0

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/CHANGELOG.md CHANGED
@@ -2,6 +2,34 @@
2
2
 
3
3
  All notable changes to veo. This project follows [Semantic Versioning](https://semver.org/).
4
4
 
5
+ ## 1.10.0
6
+
7
+ ### Added
8
+
9
+ - **Persistent Profile Management**: Added `veo profile [NAME|list|reset]` commands to view the active configuration profile, inspect available profiles, switch defaults, or reset back to default without manual configuration edits.
10
+ - **On-demand Config Guide**: Added `veo config guide` to display full configuration guidance, supported options, and templates in the terminal without opening the interactive editor.
11
+ - **Interactive Config Editor enhancements**:
12
+ - Full Undo (`Ctrl+Z`) and Redo (`Ctrl+Y`) history support when modifying settings.
13
+ - Select All (`Ctrl+A`) shortcut to easily highlight and overwrite lines or entire sections.
14
+ - Interactive F3 guide management prompt (`A` to add/refresh, `R` to strip documentation lines).
15
+ - Mouse edge drag auto-scrolling when dragging selections past viewport bounds.
16
+ - **Automatic Browser Source Discovery**: Enabled automatic source discovery fallback by default when direct extraction fails, automatically selecting and proceeding if a single verified media stream is found.
17
+
18
+ ### Changed
19
+
20
+ - Streamlined source discovery observation polling to immediately proceed once candidate streams are detected.
21
+ - Enhanced progress output styling and ANSI handling across terminal status reporting.
22
+
23
+ ## 1.9.1
24
+
25
+ ### Added
26
+
27
+ - Added typo and spelling suggestions for misspelled CLI options and subcommands (e.g. `--deepscan` suggests `--deep-scan`, `veo histroy` suggests `veo history`).
28
+
29
+ ### Fixed
30
+
31
+ - Improved browser-based player source discovery to continuously detect delayed play controls and overlays inside frames.
32
+
5
33
  ## 1.9.0
6
34
 
7
35
  ### Added
package/README.md CHANGED
@@ -193,9 +193,11 @@ example through `veo config edit`. Enable it with `veo URL --profile kompatibel`
193
193
  reserved by concurrent downloads in the same process. Unknown sizes are reported as
194
194
  unknown; conversion sizes and other processes' disk usage cannot be predicted exactly.
195
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.
196
+ - `--timings` shows phase times at the end of the run. Single downloads show
197
+ timing without a summary; multiple downloads show timing below the summary.
198
+ The phases cover setup, metadata, download/backend, processing, saving and retry waits.
199
+ Download/backend includes the backend's own startup and extraction overhead;
200
+ these are summed per-download times, not a diagnosis of network speed.
199
201
  JSON results include `timings` in milliseconds, or `entryTimings` for playlists.
200
202
  Use `--no-timings` to hide/omit them.
201
203
  - Terminal details are gray, titles bold, saves green and errors red. The same styling
@@ -230,7 +232,7 @@ veo config show --profile fast
230
232
  Config checks validate every effective profile (or the selected one), including unknown
231
233
  settings, ranges and conflicting options. Show prints merged built-in, global and profile
232
234
  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`.
235
+ video sites. All new options are also documented in `veo config guide`.
234
236
 
235
237
  ### Files, names and resume
236
238
 
@@ -307,9 +309,10 @@ download fails because a login is required, the error message points at these fl
307
309
 
308
310
  ### Inspection and scripting
309
311
 
310
- - `veo <page-url> --list-sources` opens the page in a temporary headless Chrome,
311
- Edge or Chromium profile, presses a clearly labeled Play button when present,
312
- and checks media requests loaded by the player. Use `--json` for numbered source
312
+ - `veo <page-url> --list-sources` opens the page in a temporary headless Chrome,
313
+ Edge or Chromium profile, repeatedly checks for visible Play controls (including
314
+ controls added later or inside frames), and watches media requests after interaction.
315
+ Use `--json` for numbered source
313
316
  metadata. Each result includes its title, host, HLS/DASH/direct type, known
314
317
  resolutions, estimated MiB size and Mbit/s bitrate when the source provides them. Signed media
315
318
  URLs are not printed. Set `VEO_BROWSER_PATH` if the browser is not found.
@@ -321,14 +324,17 @@ download fails because a login is required, the error message points at these fl
321
324
  is reached, verified sources found so far are shown with a timeout notice.
322
325
  A deep scan covers observed requests; it cannot discover sources that the
323
326
  page never loads. The timeout does not stop a subsequent download.
324
- - `veo <page-url> --source 2 --dry-run --json` previews source 2;
325
- `veo <page-url> --source 2 --json` downloads it. For an unsupported single page
326
- URL in a terminal, veo asks whether to search for sources. Answer `y` to see
327
- the numbered list and choose one; `n` or Enter keeps the original failure.
328
- Scripts must pass `--source <n>`; they never receive an interactive prompt.
327
+ - For an unsupported single page URL, veo searches for media automatically and
328
+ uses the only verified source when there is one. If several sources are found,
329
+ the terminal asks which one to use; scripts receive a numbered source list and
330
+ a nonzero exit. `veo <page-url> --source 2 --dry-run --json` previews a chosen
331
+ source, and `veo <page-url> --source 2 --json` downloads it. Scripts never
332
+ receive an interactive prompt.
329
333
  Retry jobs retain the page URL and selected number and discover a fresh media
330
334
  URL, because player links may expire. Browser discovery uses a temporary
331
- profile without copying browser cookies, and cannot unlock DRM content.
335
+ profile without copying browser cookies, and cannot unlock DRM content.
336
+ Misspelled flags and commands show a nearby valid spelling, for example
337
+ `--deepscan` suggests `--deep-scan`.
332
338
  - `--list-formats` prints the backend's own format table for one URL and exits.
333
339
  - `--dry-run` prints the title, the resolved quality and the destination path that would be
334
340
  used — without creating the output directory or downloading anything.
@@ -388,10 +394,11 @@ silently ignoring a typo would be worse.
388
394
  Source discovery defaults are `deepScan` (boolean), `timeout` (string such as
389
395
  `"30s"` or `"2m"`), `listSources` (boolean) and `autoListSources` (boolean).
390
396
  `listSources: true` makes a single URL list sources and exit; use
391
- `--no-list-sources` to download for one invocation. `autoListSources: true`
392
- starts a source search when ordinary extraction finds no downloadable video,
393
- without the yes/no question. It still requires a source number before a
394
- download; scripts receive the source list and a nonzero exit. `deepScan` and
397
+ `--no-list-sources` to download for one invocation. `autoListSources` defaults
398
+ to `true` and searches when ordinary extraction finds no downloadable video.
399
+ Set it to `false` or pass `--no-auto-list-sources` to disable that fallback.
400
+ A single verified source is selected automatically; multiple sources require
401
+ a choice. `deepScan` and
395
402
  `timeout` apply to either kind of source search.
396
403
 
397
404
  Additional defaults are `skipExisting` and `playlistItems`. Boolean defaults can be disabled
@@ -400,11 +407,16 @@ disables stored subtitle languages and subtitle embedding for that invocation.
400
407
 
401
408
  ### Named profiles
402
409
 
403
- If no profile is selected, `profiles.default` is applied automatically. You can also select
404
- it explicitly with `--profile default`. Other named profiles use global defaults rather
405
- than inheriting `default`. The wizard preselects `default` when it exists.
406
- `veo config edit` adds an empty `default` profile to existing configurations if missing,
407
- preserving existing settings. An empty profile does not change download behavior.
410
+ If no profile is selected, `profiles.default` is applied automatically. `veo profile music`
411
+ selects another named profile for future commands and stores it as `activeProfile` in the
412
+ config; `veo profile` shows the current choice, and `veo profile list` marks it in
413
+ the list. `veo profile reset` removes the stored choice and returns to `default`.
414
+ You can also use `veo profile default` to select it explicitly.
415
+ The names `list` and `reset` are reserved for these commands and cannot be used as profile names.
416
+ `--profile NAME` overrides this choice for one command. Named profiles use global defaults
417
+ rather than inheriting `default`. The wizard preselects the active profile.
418
+ New configurations include an empty `default` profile and the example profiles. Existing configurations are
419
+ left unchanged when opened. An empty profile does not change download behavior.
408
420
 
409
421
  ```json
410
422
  {
@@ -417,13 +429,15 @@ preserving existing settings. An empty profile does not change download behavior
417
429
  }
418
430
  ```
419
431
 
420
- `veo <url> --profile music` merges global defaults, then the selected profile, then explicit
421
- CLI flags. `veo config profiles` lists profile names; `veo config path` prints the file path.
422
- `veo config edit` fills new or empty files with a commented template explaining common
423
- options and example profiles. When the template changes in a new veo version,
424
- `veo config edit` refreshes its marked reference guide in existing files. Active settings
425
- and comments outside the marked guide remain intact; put personal notes below the guide.
426
- New options are visible without resetting the config.
432
+ `veo <url> --profile music` merges global defaults, then the selected profile, then explicit
433
+ CLI flags. `veo config profiles` lists profile names; `veo config path` prints the file path.
434
+ The download status shows `Profile: NAME` (or `global (no profile)` when none is configured).
435
+ `veo config edit` creates a small, valid configuration for new or empty files. Opening an
436
+ existing configuration does not add or update comments. The current option guide is kept in
437
+ the program; `veo config guide` prints it without changing the file. In the built-in editor,
438
+ F3 offers **A** to add or update the guide at the top and **R** to remove a previously
439
+ generated guide. Ctrl+S saves that choice. This can also clean up the long duplicate block
440
+ inserted by older versions. Personal settings and notes below a generated block are preserved.
427
441
  In an interactive terminal, it opens the built-in editor
428
442
  unless `VISUAL` or `EDITOR` is configured (an executable path, without shell arguments).
429
443
  The editor provides syntax colors, line numbers and live validation. Syntax errors mark
@@ -432,11 +446,13 @@ spelling suggestions, expected types and allowed values in the status area. Vali
432
446
  the same rules as the CLI, including profile overrides and audio/video formats.
433
447
  Press F2 on a property or value to open suggestions, use Up/Down to choose, Enter to apply
434
448
  or Esc to cancel. Suggestions require valid JSON syntax; free-text values and custom numeric
435
- resolutions such as `900p` remain supported. Ctrl+S validates and saves;
436
- Esc or Ctrl+Q exits, asking before discarding changes. Use arrows, Home/End and PageUp/PageDown
449
+ resolutions such as `900p` remain supported. Ctrl+S validates and saves;
450
+ Ctrl+Z undoes edits, Ctrl+Y redoes them, and Ctrl+A selects the whole file.
451
+ Esc or Ctrl+Q exits, asking before discarding changes. Use arrows, Home/End and PageUp/PageDown
437
452
  to navigate. Invalid configurations cannot be saved. `--no-color` disables syntax colors.
438
453
  In terminals supporting SGR mouse reporting, left-click positions the cursor and dragging
439
- selects text, including across lines. Backspace/Delete removes the selection; typing replaces
454
+ selects text, including across lines. Holding the pointer at the top or bottom while dragging
455
+ scrolls the file and extends the selection. Backspace/Delete removes the selection; typing replaces
440
456
  it. The mouse wheel scrolls without changing the editing position; Ctrl+Up/Down also scrolls.
441
457
  Ctrl+C copies selected text to the system clipboard, and Ctrl+V pastes clipboard text at the
442
458
  cursor or replaces the selection. Ctrl+Q or Esc exits the editor. The editor disables mouse
@@ -461,9 +477,8 @@ $env:VEO_CONFIG_EDITOR = 'external'
461
477
 
462
478
  Non-interactive sessions use the external editor unless `--terminal` is explicitly selected,
463
479
  in which case an interactive-terminal error is reported. The wizard also offers configured profiles.
464
- Generated templates and app messages are in English. Previously generated German template
465
- comments are translated the next time you run `veo config edit`; existing profile names,
466
- paths and custom comments are preserved.
480
+ Generated guides and app messages are in English. Existing comments, profile names and
481
+ settings are preserved when opening the editor.
467
482
 
468
483
  ### Terminal output
469
484
 
@@ -499,9 +514,14 @@ paths and custom comments are preserved.
499
514
  ```bash
500
515
  veo version # installed version (also: veo --version or veo -v)
501
516
  veo # interactive download wizard (terminal only)
502
- veo config edit # create/open config, including example profiles
517
+ veo config edit # create/open config; F3 offers the optional guide
518
+ veo config guide # print the current option guide
503
519
  veo config reset # confirm with y; back up config and restore the current template
504
- veo config profiles # list available profiles
520
+ veo config profiles # list available profiles
521
+ veo profile # show the currently selected profile
522
+ veo profile list # list all profiles and mark the active one
523
+ veo profile music # use music as the default for future commands
524
+ veo profile reset # return to default (or global settings if absent)
505
525
  veo config path # show the config file location
506
526
  veo doctor # diagnose the local setup; exit 1 if a check fails
507
527
  veo doctor fix # restore missing or damaged managed tools
@@ -10,11 +10,14 @@ veo "https://example.com/video" --json --output ./downloads
10
10
  veo inspect "./downloads/video.mp4" --check-audio --json
11
11
  ```
12
12
 
13
- For a page with an embedded player that the normal extractor cannot identify,
14
- run `veo "<page-url>" --list-sources --json` and choose an `index` from the
15
- returned `sources`. Then use `veo "<page-url>" --source <index> --dry-run --json`
16
- before the download. Source discovery opens a temporary headless Chromium-based
17
- browser and observes media requests after Play. It needs Chrome, Edge or Chromium,
13
+ For a page with an embedded player that the normal extractor cannot identify,
14
+ `veo "<page-url>" --dry-run --json` searches for playable media automatically.
15
+ It uses a single verified source without a second command. If several sources
16
+ are found, run `veo "<page-url>" --list-sources --json` and choose an `index`,
17
+ then use `veo "<page-url>" --source <index> --dry-run --json` before downloading.
18
+ Source discovery opens a temporary headless Chromium-based
19
+ browser, repeatedly checks for Play controls, and observes media requests after
20
+ interaction. It needs Chrome, Edge or Chromium,
18
21
  or `VEO_BROWSER_PATH`. A found source is verified by the backend; size may be
19
22
  unknown, and DRM-protected media is excluded. Source numbers are tied to the
20
23
  page's current discovery run and may change later.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "veodl",
3
- "version": "1.9.0",
3
+ "version": "1.10.0",
4
4
  "description": "A clean, simple video downloader powered by yt-dlp.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/backend.js CHANGED
@@ -291,11 +291,19 @@ async function downloadBackend({ asset, release, expected, directory, signal, st
291
291
  }
292
292
  }
293
293
 
294
- async function acquire(asset, directory, signal, status) {
294
+ async function acquire(asset, directory, signal, status, statusDone = () => {}) {
295
295
  const destination = path.join(directory, `yt-dlp${exeSuffix()}`);
296
296
  const expected = HASHES[asset];
297
297
  status('Checking yt-dlp…');
298
- if (await matches(destination, expected, signal)) {
298
+ let cached;
299
+ try {
300
+ cached = await matches(destination, expected, signal);
301
+ statusDone('Checking yt-dlp…', 'done');
302
+ } catch (error) {
303
+ if (!signal?.aborted && error?.name !== 'AbortError') statusDone('Checking yt-dlp…', 'failed');
304
+ throw error;
305
+ }
306
+ if (cached) {
299
307
  if (process.platform !== 'win32') await chmod(destination, 0o755);
300
308
  return destination;
301
309
  }
@@ -330,7 +338,6 @@ async function managedMediaTools({ signal, status, directory }) {
330
338
  } catch (cause) {
331
339
  return `the bundled static media tools are unusable (${cause.message})`;
332
340
  }
333
- status('Preparing ffmpeg and ffprobe…');
334
341
  const suffix = exeSuffix();
335
342
  await stage(ffmpeg, path.join(directory, `ffmpeg${suffix}`), signal);
336
343
  await stage(ffprobe, path.join(directory, `ffprobe${suffix}`), signal);
@@ -343,38 +350,58 @@ async function managedMediaTools({ signal, status, directory }) {
343
350
  * then a system installation (which is what Windows on ARM needs, because
344
351
  * ffmpeg-static and ffprobe-static ship no arm64 Windows binaries).
345
352
  */
346
- export async function resolveMediaTools({ signal, status = () => {}, directory, offline = false,
353
+ export async function resolveMediaTools({ signal, status = () => {}, statusDone = () => {}, directory, offline = false,
347
354
  find = findOnPath, managed = managedMediaTools, install = installMediaTools } = {}) {
348
- const suffix = exeSuffix();
349
- const override = envPath('VEO_FFMPEG_PATH');
350
- if (override) {
351
- await executable(path.join(override, `ffmpeg${suffix}`), 'VEO_FFMPEG_PATH ffmpeg');
352
- await executable(path.join(override, `ffprobe${suffix}`), 'VEO_FFMPEG_PATH ffprobe');
353
- status('Using VEO_FFMPEG_PATH override.');
354
- return override;
355
- }
356
- const problem = await managed({ signal, status, directory });
357
- if (!problem) return directory;
358
- // A previous run may already have staged a working pair into the cache.
359
- if (await isExecutable(path.join(directory, `ffmpeg${suffix}`))
360
- && await isExecutable(path.join(directory, `ffprobe${suffix}`))) return directory;
361
- const ffmpeg = await find(['ffmpeg']);
362
- const ffprobe = await find(['ffprobe']);
363
- if (!ffmpeg || !ffprobe) {
364
- if (!offline) return install({ signal, status, directory, find, stage });
365
- const missing = [!ffmpeg && 'ffmpeg', !ffprobe && 'ffprobe'].filter(Boolean).join(' and ');
366
- throw new Error(`Cannot load media tools: ${problem}, and no ${missing} was found on PATH. `
367
- + 'Run veo doctor fix without --offline to install them automatically, or set VEO_FFMPEG_PATH to a directory containing both binaries.');
368
- }
369
- // Same directory: yt-dlp can use it in place instead of duplicating ~150 MB.
370
- if (path.dirname(ffmpeg) === path.dirname(ffprobe)) {
371
- status(`Using the system ffmpeg in ${path.dirname(ffmpeg)}.`);
372
- return path.dirname(ffmpeg);
355
+ const preparing = 'Preparing ffmpeg and ffprobe…';
356
+ let currentStatus;
357
+ const report = message => { currentStatus = message; status(message); };
358
+ const finish = outcome => {
359
+ if (currentStatus !== preparing) report(preparing);
360
+ statusDone(preparing, outcome);
361
+ };
362
+ report(preparing);
363
+ try {
364
+ const suffix = exeSuffix();
365
+ const override = envPath('VEO_FFMPEG_PATH');
366
+ if (override) {
367
+ await executable(path.join(override, `ffmpeg${suffix}`), 'VEO_FFMPEG_PATH ffmpeg');
368
+ await executable(path.join(override, `ffprobe${suffix}`), 'VEO_FFMPEG_PATH ffprobe');
369
+ finish('done');
370
+ report('Using VEO_FFMPEG_PATH override.');
371
+ return override;
372
+ }
373
+ const problem = await managed({ signal, status: report, directory });
374
+ if (!problem) { finish('done'); return directory; }
375
+ // A previous run may already have staged a working pair into the cache.
376
+ if (await isExecutable(path.join(directory, `ffmpeg${suffix}`))
377
+ && await isExecutable(path.join(directory, `ffprobe${suffix}`))) { finish('done'); return directory; }
378
+ const ffmpeg = await find(['ffmpeg']);
379
+ const ffprobe = await find(['ffprobe']);
380
+ if (!ffmpeg || !ffprobe) {
381
+ if (!offline) {
382
+ const location = await install({ signal, status: report, directory, find, stage });
383
+ finish('done');
384
+ return location;
385
+ }
386
+ const missing = [!ffmpeg && 'ffmpeg', !ffprobe && 'ffprobe'].filter(Boolean).join(' and ');
387
+ throw new Error(`Cannot load media tools: ${problem}, and no ${missing} was found on PATH. `
388
+ + 'Run veo doctor fix without --offline to install them automatically, or set VEO_FFMPEG_PATH to a directory containing both binaries.');
389
+ }
390
+ // Same directory: yt-dlp can use it in place instead of duplicating ~150 MB.
391
+ if (path.dirname(ffmpeg) === path.dirname(ffprobe)) {
392
+ finish('done');
393
+ report(`Using the system ffmpeg in ${path.dirname(ffmpeg)}.`);
394
+ return path.dirname(ffmpeg);
395
+ }
396
+ report('Copying system ffmpeg and ffprobe into the backend cache…');
397
+ await stage(ffmpeg, path.join(directory, `ffmpeg${suffix}`), signal);
398
+ await stage(ffprobe, path.join(directory, `ffprobe${suffix}`), signal);
399
+ finish('done');
400
+ return directory;
401
+ } catch (error) {
402
+ if (!signal?.aborted && error?.name !== 'AbortError') finish('failed');
403
+ throw error;
373
404
  }
374
- status('Copying system ffmpeg and ffprobe into the backend cache…');
375
- await stage(ffmpeg, path.join(directory, `ffmpeg${suffix}`), signal);
376
- await stage(ffprobe, path.join(directory, `ffprobe${suffix}`), signal);
377
- return directory;
378
405
  }
379
406
 
380
407
  /**
@@ -457,12 +484,13 @@ export async function inspectBackend({ signal, platform = process.platform, arch
457
484
  * cannot serve the current platform (notably Windows on ARM).
458
485
  * onStatus receives plain strings. Throws on cancellation or acquisition failure.
459
486
  */
460
- export async function resolveBackend({ signal, onStatus, offline = false, platform = process.platform, find = findOnPath,
487
+ export async function resolveBackend({ signal, onStatus, onStatusDone, offline = false, platform = process.platform, find = findOnPath,
461
488
  setupTermux = installTermuxTools, hasEjs = hasTermuxEjs, run = runSetup } = {}) {
462
489
  if (signal !== undefined && !(signal instanceof AbortSignal)) throw new TypeError('signal must be an AbortSignal.');
463
490
  if (onStatus !== undefined && typeof onStatus !== 'function') throw new TypeError('onStatus must be a function.');
464
491
  signal?.throwIfAborted();
465
492
  const status = onStatus || (() => {});
493
+ const statusDone = onStatusDone || (() => {});
466
494
  const ytOverride = envPath('VEO_YT_DLP_PATH');
467
495
  if (platform === 'android') {
468
496
  const mediaOverride = envPath('VEO_FFMPEG_PATH');
@@ -508,13 +536,13 @@ export async function resolveBackend({ signal, onStatus, offline = false, platfo
508
536
  const installed = ytDlp ? null : await activeOverride(asset, { signal });
509
537
  const directory = cacheDirectory();
510
538
  await mkdir(directory, { recursive: true, mode: 0o700 });
511
- const ffmpegLocation = await resolveMediaTools({ signal, status, directory, offline });
539
+ const ffmpegLocation = await resolveMediaTools({ signal, status, statusDone, directory, offline });
512
540
  if (ytDlp) status('Using VEO_YT_DLP_PATH override.');
513
541
  else if (installed) status(`Using the installed yt-dlp ${installed.release}.`);
514
542
  if (offline && !ytDlp && !installed && !await matches(path.join(directory, `yt-dlp${exeSuffix()}`), HASHES[asset], signal)) {
515
543
  throw new Error('yt-dlp is missing or damaged. Run veo doctor fix without --offline to download it.');
516
544
  }
517
- const backend = ytDlp || installed?.path || await acquire(asset, directory, signal, status);
545
+ const backend = ytDlp || installed?.path || await acquire(asset, directory, signal, status, statusDone);
518
546
  signal?.throwIfAborted();
519
547
  return { ytDlp: backend, ffmpegLocation };
520
548
  }