veodl 1.12.1 → 2.0.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 +15 -0
- package/LICENSE +21 -21
- package/README.md +178 -97
- package/bin/veo.js +47 -2
- package/docs/AGENT_GUIDE.md +1 -1
- package/native/android-arm64/veo +0 -0
- package/native/darwin-arm64/veo +0 -0
- package/native/darwin-x64/veo +0 -0
- package/native/linux-arm64/veo +0 -0
- package/native/linux-x64/veo +0 -0
- package/native/win32-arm64/veo.exe +0 -0
- package/native/win32-x64/veo.exe +0 -0
- package/package.json +53 -57
- package/src/alias.js +0 -258
- package/src/backend-update.js +0 -191
- package/src/backend.js +0 -572
- package/src/changes.js +0 -278
- package/src/cli.js +0 -785
- package/src/compatibility.js +0 -39
- package/src/config-clipboard.js +0 -67
- package/src/config-diagnostics.js +0 -155
- package/src/config-editor.js +0 -404
- package/src/config-errors.js +0 -64
- package/src/config-reset.js +0 -39
- package/src/config-template.js +0 -227
- package/src/config.js +0 -307
- package/src/disk-space.js +0 -68
- package/src/doctor.js +0 -306
- package/src/download-cache.js +0 -31
- package/src/downloader.js +0 -704
- package/src/execution.js +0 -68
- package/src/experimental-fragments.js +0 -29
- package/src/flush.js +0 -66
- package/src/history.js +0 -173
- package/src/inline-region.js +0 -72
- package/src/inspect-media.js +0 -179
- package/src/interactive.js +0 -120
- package/src/jobs.js +0 -218
- package/src/legacy-config-comments.js +0 -106
- package/src/media-assets.js +0 -272
- package/src/naming.js +0 -54
- package/src/open-file.js +0 -15
- package/src/option-suggestions.js +0 -43
- package/src/output.js +0 -81
- package/src/path-links.js +0 -53
- package/src/paths.js +0 -26
- package/src/playlist.js +0 -31
- package/src/progress.js +0 -341
- package/src/run-archive.js +0 -148
- package/src/runs.js +0 -346
- package/src/source-discovery.js +0 -305
- package/src/state.js +0 -27
- package/src/stats.js +0 -63
- package/src/terminal-size.js +0 -8
- package/src/terminal-title.js +0 -19
- package/src/tool-setup.js +0 -289
- package/src/uninstall.js +0 -222
- package/src/updater.js +0 -229
- package/src/utils.js +0 -208
- package/src/version.js +0 -38
package/src/media-assets.js
DELETED
|
@@ -1,272 +0,0 @@
|
|
|
1
|
-
// SHA-256 and byte counts pinned at development time from upstream GitHub assets.
|
|
2
|
-
// https://github.com/eugeneware/ffmpeg-static/releases/tag/b6.1.1
|
|
3
|
-
// Windows ia32 uses b6.0 with measured download hashes and its Windows license.
|
|
4
|
-
// Never fetch replacement checksum metadata at runtime.
|
|
5
|
-
export const MEDIA_ASSET_BASE = 'https://github.com/eugeneware/ffmpeg-static/releases/download';
|
|
6
|
-
const freeze = value => { for (const child of Object.values(value)) if (child && typeof child === "object") freeze(child); return Object.freeze(value); };
|
|
7
|
-
export const MEDIA_ASSETS = freeze({
|
|
8
|
-
"win32-x64": {
|
|
9
|
-
"release": "b6.1.1",
|
|
10
|
-
"ffmpeg": {
|
|
11
|
-
"name": "ffmpeg-win32-x64",
|
|
12
|
-
"sha256": "04e1307997530f9cf2fe35cba2ca7e8875ca91da02f89d6c7243df819c94ad00",
|
|
13
|
-
"size": 82797568,
|
|
14
|
-
"gzip": {
|
|
15
|
-
"name": "ffmpeg-win32-x64.gz",
|
|
16
|
-
"sha256": "8883a3dffbd0a16cf4ef95206ea05283f78908dbfb118f73c83f4951dcc06d77",
|
|
17
|
-
"size": 29581307
|
|
18
|
-
}
|
|
19
|
-
},
|
|
20
|
-
"ffprobe": {
|
|
21
|
-
"name": "ffprobe-win32-x64",
|
|
22
|
-
"sha256": "3a7e2dc003dc2cd1472827e4c7c4f056ae1ae0ae7c5bbc580c99b49827351ba4",
|
|
23
|
-
"size": 82668032,
|
|
24
|
-
"gzip": {
|
|
25
|
-
"name": "ffprobe-win32-x64.gz",
|
|
26
|
-
"sha256": "f309e6223ad89d2fe54bccd420a7709b66fd27540674e92309578ed491a43c8d",
|
|
27
|
-
"size": 29521644
|
|
28
|
-
}
|
|
29
|
-
},
|
|
30
|
-
"license": {
|
|
31
|
-
"name": "win32-x64.LICENSE",
|
|
32
|
-
"sha256": "8ceb4b9ee5adedde47b31e975c1d90c73ad27b6b165a1dcd80c7c545eb65b903",
|
|
33
|
-
"size": 35147
|
|
34
|
-
},
|
|
35
|
-
"readme": {
|
|
36
|
-
"name": "win32-x64.README",
|
|
37
|
-
"sha256": "a636a7183c58006351acbaf35303c0ed85c6e1320fd4e80de453ba6157de6311",
|
|
38
|
-
"size": 39494
|
|
39
|
-
}
|
|
40
|
-
},
|
|
41
|
-
"darwin-x64": {
|
|
42
|
-
"release": "b6.1.1",
|
|
43
|
-
"ffmpeg": {
|
|
44
|
-
"name": "ffmpeg-darwin-x64",
|
|
45
|
-
"sha256": "ebdddc936f61e14049a2d4b549a412b8a40deeff6540e58a9f2a2da9e6b18894",
|
|
46
|
-
"size": 78862176,
|
|
47
|
-
"gzip": {
|
|
48
|
-
"name": "ffmpeg-darwin-x64.gz",
|
|
49
|
-
"sha256": "929b375c1182d956c51f7ac25e0b2b0411fb01f6f407aa15c9758efeb4242106",
|
|
50
|
-
"size": 25296431
|
|
51
|
-
}
|
|
52
|
-
},
|
|
53
|
-
"ffprobe": {
|
|
54
|
-
"name": "ffprobe-darwin-x64",
|
|
55
|
-
"sha256": "fa3add0ce901f7241abe0dfc0155d958fc834aca3f8ce61f87cc712ae669c1e0",
|
|
56
|
-
"size": 78780408,
|
|
57
|
-
"gzip": {
|
|
58
|
-
"name": "ffprobe-darwin-x64.gz",
|
|
59
|
-
"sha256": "d4da574d6e2e197bd259b47d69cf262df9e312af24ad960444f6d806d3d4c186",
|
|
60
|
-
"size": 25239438
|
|
61
|
-
}
|
|
62
|
-
},
|
|
63
|
-
"license": {
|
|
64
|
-
"name": "darwin-x64.LICENSE",
|
|
65
|
-
"sha256": "2e1d16c72fd74e12063776371da757322f8b77589386532f4fd8634bde7de1af",
|
|
66
|
-
"size": 4346
|
|
67
|
-
},
|
|
68
|
-
"readme": {
|
|
69
|
-
"name": "darwin-x64.README",
|
|
70
|
-
"sha256": "e88a0325f8e5b75210355e37341824f074d3cd82def2125be54c914b62848a36",
|
|
71
|
-
"size": 6227
|
|
72
|
-
}
|
|
73
|
-
},
|
|
74
|
-
"darwin-arm64": {
|
|
75
|
-
"release": "b6.1.1",
|
|
76
|
-
"ffmpeg": {
|
|
77
|
-
"name": "ffmpeg-darwin-arm64",
|
|
78
|
-
"sha256": "a90e3db6a3fd35f6074b013f948b1aa45b31c6375489d39e572bea3f18336584",
|
|
79
|
-
"size": 45568216,
|
|
80
|
-
"gzip": {
|
|
81
|
-
"name": "ffmpeg-darwin-arm64.gz",
|
|
82
|
-
"sha256": "8923876afa8db5585022d7860ec7e589af192f441c56793971276d450ed3bbfa",
|
|
83
|
-
"size": 19246198
|
|
84
|
-
}
|
|
85
|
-
},
|
|
86
|
-
"ffprobe": {
|
|
87
|
-
"name": "ffprobe-darwin-arm64",
|
|
88
|
-
"sha256": "bb2db6f5d8cef919da12fbf592119a987202a8c060a886f3cab091f9cab90b64",
|
|
89
|
-
"size": 45528808,
|
|
90
|
-
"gzip": {
|
|
91
|
-
"name": "ffprobe-darwin-arm64.gz",
|
|
92
|
-
"sha256": "d986a8ec7b030899fe66a8a288ed809a3543338705a3ce178cfb85869c5d80be",
|
|
93
|
-
"size": 19207077
|
|
94
|
-
}
|
|
95
|
-
},
|
|
96
|
-
"license": {
|
|
97
|
-
"name": "darwin-arm64.LICENSE",
|
|
98
|
-
"sha256": "cb48bf09a11f5fb576cddb0431c8f5ed0a60157a9ec942adffc13907cbe083f2",
|
|
99
|
-
"size": 4376
|
|
100
|
-
},
|
|
101
|
-
"readme": {
|
|
102
|
-
"name": "darwin-arm64.README",
|
|
103
|
-
"sha256": "05ba4b92c96605434b1aaae3eedf5a2c280c9607bf78ffca9a5b536d9af2dc6a",
|
|
104
|
-
"size": 1810
|
|
105
|
-
}
|
|
106
|
-
},
|
|
107
|
-
"linux-x64": {
|
|
108
|
-
"release": "b6.1.1",
|
|
109
|
-
"ffmpeg": {
|
|
110
|
-
"name": "ffmpeg-linux-x64",
|
|
111
|
-
"sha256": "e7e7fb30477f717e6f55f9180a70386c62677ef8a4d4d1a5d948f4098aa3eb99",
|
|
112
|
-
"size": 79826272,
|
|
113
|
-
"gzip": {
|
|
114
|
-
"name": "ffmpeg-linux-x64.gz",
|
|
115
|
-
"sha256": "bfe8a8fc511530457b528c48d77b5737527b504a3797a9bc4866aeca69c2dffa",
|
|
116
|
-
"size": 29354986
|
|
117
|
-
}
|
|
118
|
-
},
|
|
119
|
-
"ffprobe": {
|
|
120
|
-
"name": "ffprobe-linux-x64",
|
|
121
|
-
"sha256": "4f231a1960d83e403d08f7971e271707bec278a9ae18e21b8b5b03186668450d",
|
|
122
|
-
"size": 79665792,
|
|
123
|
-
"gzip": {
|
|
124
|
-
"name": "ffprobe-linux-x64.gz",
|
|
125
|
-
"sha256": "25d9b6ccb05e3d9de9e04e31e2506d8dd7f9f0418981965ac6df12e8d3afd067",
|
|
126
|
-
"size": 29276839
|
|
127
|
-
}
|
|
128
|
-
},
|
|
129
|
-
"license": {
|
|
130
|
-
"name": "linux-x64.LICENSE",
|
|
131
|
-
"sha256": "8ceb4b9ee5adedde47b31e975c1d90c73ad27b6b165a1dcd80c7c545eb65b903",
|
|
132
|
-
"size": 35147
|
|
133
|
-
},
|
|
134
|
-
"readme": {
|
|
135
|
-
"name": "linux-x64.README",
|
|
136
|
-
"sha256": "72f4b1b06d419d22ace6e7cc75f06826f90737345aa0b1736158929f4aacc537",
|
|
137
|
-
"size": 2235
|
|
138
|
-
}
|
|
139
|
-
},
|
|
140
|
-
"linux-ia32": {
|
|
141
|
-
"release": "b6.1.1",
|
|
142
|
-
"ffmpeg": {
|
|
143
|
-
"name": "ffmpeg-linux-ia32",
|
|
144
|
-
"sha256": "c6472eb993612db72ca50893a34137ba11173e60a1a4c028d4660a3f755d2490",
|
|
145
|
-
"size": 52155436,
|
|
146
|
-
"gzip": {
|
|
147
|
-
"name": "ffmpeg-linux-ia32.gz",
|
|
148
|
-
"sha256": "169b27c078a8ecedb814cac67afccf15a9868d63e9d74ef86088adefaa500d00",
|
|
149
|
-
"size": 22184031
|
|
150
|
-
}
|
|
151
|
-
},
|
|
152
|
-
"ffprobe": {
|
|
153
|
-
"name": "ffprobe-linux-ia32",
|
|
154
|
-
"sha256": "a13b4cce0a4b9bd6714672e4df81076ec06f4c295d798162d48e3de68c781d6d",
|
|
155
|
-
"size": 51955948,
|
|
156
|
-
"gzip": {
|
|
157
|
-
"name": "ffprobe-linux-ia32.gz",
|
|
158
|
-
"sha256": "a75c55bcaad1b0e79f2201d82b9cc43903d950857615f91baaea5ce92d756e63",
|
|
159
|
-
"size": 22093495
|
|
160
|
-
}
|
|
161
|
-
},
|
|
162
|
-
"license": {
|
|
163
|
-
"name": "linux-ia32.LICENSE",
|
|
164
|
-
"sha256": "8ceb4b9ee5adedde47b31e975c1d90c73ad27b6b165a1dcd80c7c545eb65b903",
|
|
165
|
-
"size": 35147
|
|
166
|
-
},
|
|
167
|
-
"readme": {
|
|
168
|
-
"name": "linux-ia32.README",
|
|
169
|
-
"sha256": "3bbce64eba997007b3fd5324bb105f10ab6b0a499796c7d34b844b51fda0528b",
|
|
170
|
-
"size": 2209
|
|
171
|
-
}
|
|
172
|
-
},
|
|
173
|
-
"linux-arm64": {
|
|
174
|
-
"release": "b6.1.1",
|
|
175
|
-
"ffmpeg": {
|
|
176
|
-
"name": "ffmpeg-linux-arm64",
|
|
177
|
-
"sha256": "6bb182d0d75d23028db82e9e4f723ca69b853d055698486e6984ddb2c06fb8ce",
|
|
178
|
-
"size": 51134160,
|
|
179
|
-
"gzip": {
|
|
180
|
-
"name": "ffmpeg-linux-arm64.gz",
|
|
181
|
-
"sha256": "754a678672298bc68156adff58aa7385a592c2b30b1d0ae8750c45c915c4bac0",
|
|
182
|
-
"size": 25568691
|
|
183
|
-
}
|
|
184
|
-
},
|
|
185
|
-
"ffprobe": {
|
|
186
|
-
"name": "ffprobe-linux-arm64",
|
|
187
|
-
"sha256": "d17ae9b4c297d48e2521ba14e417bb0537c6ff77c584cdbcd6bb0d8d0307a2e8",
|
|
188
|
-
"size": 50994160,
|
|
189
|
-
"gzip": {
|
|
190
|
-
"name": "ffprobe-linux-arm64.gz",
|
|
191
|
-
"sha256": "2ab6aba60ee84412dff9188720703376cb4e7aaf7e0b5e43aa8249f2acae5bf8",
|
|
192
|
-
"size": 25493573
|
|
193
|
-
}
|
|
194
|
-
},
|
|
195
|
-
"license": {
|
|
196
|
-
"name": "linux-arm64.LICENSE",
|
|
197
|
-
"sha256": "8ceb4b9ee5adedde47b31e975c1d90c73ad27b6b165a1dcd80c7c545eb65b903",
|
|
198
|
-
"size": 35147
|
|
199
|
-
},
|
|
200
|
-
"readme": {
|
|
201
|
-
"name": "linux-arm64.README",
|
|
202
|
-
"sha256": "d6777d2fd276b23f0ac6666fa619e88ffe4826521881c7ff83836e30cb4acec2",
|
|
203
|
-
"size": 2217
|
|
204
|
-
}
|
|
205
|
-
},
|
|
206
|
-
"linux-arm": {
|
|
207
|
-
"release": "b6.1.1",
|
|
208
|
-
"ffmpeg": {
|
|
209
|
-
"name": "ffmpeg-linux-arm",
|
|
210
|
-
"sha256": "0afba4a11110e6e402053e0fc14c33a7eb207d7a588688ae87dba471a0f06c71",
|
|
211
|
-
"size": 31793580,
|
|
212
|
-
"gzip": {
|
|
213
|
-
"name": "ffmpeg-linux-arm.gz",
|
|
214
|
-
"sha256": "64b115a12f0ab77c277e3c418aae8b40ef881e75e746a0e2d066a206b9bc5172",
|
|
215
|
-
"size": 19167563
|
|
216
|
-
}
|
|
217
|
-
},
|
|
218
|
-
"ffprobe": {
|
|
219
|
-
"name": "ffprobe-linux-arm",
|
|
220
|
-
"sha256": "e61ef24870ddc3b80aa8b435512a545bd889e4ed4c2fc7c885f7d3dabad2a53f",
|
|
221
|
-
"size": 31671692,
|
|
222
|
-
"gzip": {
|
|
223
|
-
"name": "ffprobe-linux-arm.gz",
|
|
224
|
-
"sha256": "2471169c19fea00018413eebf188703c19ae5ab614477465146d4cdc7458b55d",
|
|
225
|
-
"size": 19084901
|
|
226
|
-
}
|
|
227
|
-
},
|
|
228
|
-
"license": {
|
|
229
|
-
"name": "linux-arm.LICENSE",
|
|
230
|
-
"sha256": "8ceb4b9ee5adedde47b31e975c1d90c73ad27b6b165a1dcd80c7c545eb65b903",
|
|
231
|
-
"size": 35147
|
|
232
|
-
},
|
|
233
|
-
"readme": {
|
|
234
|
-
"name": "linux-arm.README",
|
|
235
|
-
"sha256": "faaea83a6d92419cb112d5de5ac673f51b72401830e068c4d003b3229ee1c89a",
|
|
236
|
-
"size": 2178
|
|
237
|
-
}
|
|
238
|
-
},
|
|
239
|
-
"win32-ia32": {
|
|
240
|
-
"release": "b6.0",
|
|
241
|
-
"ffprobe": {
|
|
242
|
-
"name": "ffprobe-win32-ia32",
|
|
243
|
-
"sha256": "c1ca7f2c0f93285629360d5afe622080ec4ce53407c427f7b8a48d7e8fcab02d",
|
|
244
|
-
"size": 74125312,
|
|
245
|
-
"gzip": {
|
|
246
|
-
"name": "ffprobe-win32-ia32.gz",
|
|
247
|
-
"sha256": "9ccc2eb5c37f8ba8c4cf8f0a73ecb508b740fa64b0a2c3171acc98f89f409e87",
|
|
248
|
-
"size": 31332907
|
|
249
|
-
}
|
|
250
|
-
},
|
|
251
|
-
"ffmpeg": {
|
|
252
|
-
"name": "ffmpeg-win32-ia32",
|
|
253
|
-
"sha256": "fb3766af5cc193ca863e15cd4554a33732973209dad5e3c1433b5e291bceb16c",
|
|
254
|
-
"size": 74250240,
|
|
255
|
-
"gzip": {
|
|
256
|
-
"name": "ffmpeg-win32-ia32.gz",
|
|
257
|
-
"sha256": "af47ca5de7b9f859f48e4e96ea831bf551b100144b0b871762ab48fba1152f56",
|
|
258
|
-
"size": 31390495
|
|
259
|
-
}
|
|
260
|
-
},
|
|
261
|
-
"license": {
|
|
262
|
-
"name": "win32-x64.LICENSE",
|
|
263
|
-
"sha256": "8ceb4b9ee5adedde47b31e975c1d90c73ad27b6b165a1dcd80c7c545eb65b903",
|
|
264
|
-
"size": 35147
|
|
265
|
-
},
|
|
266
|
-
"readme": {
|
|
267
|
-
"name": "win32-x64.README",
|
|
268
|
-
"sha256": "8cf2907e089d95142c752a69cd934956b552e71d8d4762e65dbd227f5e40237e",
|
|
269
|
-
"size": 38167
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
});
|
package/src/naming.js
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
import { lstat, mkdir } from 'node:fs/promises';
|
|
3
|
-
import { randomBytes } from 'node:crypto';
|
|
4
|
-
import { sanitizeTitle } from './utils.js';
|
|
5
|
-
|
|
6
|
-
const FIELDS = new Set(['title', 'id', 'channel', 'year', 'playlist', 'index']);
|
|
7
|
-
export function validateTemplate(template, { folders = false } = {}) {
|
|
8
|
-
if (typeof template !== 'string' || !template.trim()) throw new Error('A naming template cannot be empty.');
|
|
9
|
-
if (path.win32.isAbsolute(template) || path.posix.isAbsolute(template) || /[:\\\x00-\x1f]/.test(template)) throw new Error('Templates must be relative and use / for subfolders.');
|
|
10
|
-
if (!folders && template.includes('/')) throw new Error('Use --folder-template for subfolders.');
|
|
11
|
-
if (template.split('/').some(part => !part.trim() || ['.', '..'].includes(part.trim()))) throw new Error('Templates cannot contain empty, . or .. path components.');
|
|
12
|
-
const rest = template.replace(/\{([^{}]+)\}/g, (_, field) => {
|
|
13
|
-
if (!FIELDS.has(field)) throw new Error(`Unknown template field: ${field}. Use ${[...FIELDS].map(key => `{${key}}`).join(', ')}.`);
|
|
14
|
-
return '';
|
|
15
|
-
});
|
|
16
|
-
if (/[{}]/.test(rest)) throw new Error('Unbalanced braces in naming template.');
|
|
17
|
-
return template;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function mediaDestination(options, metadata, fallbackTitle) {
|
|
21
|
-
if (options.neutralFilename) {
|
|
22
|
-
return { directory: path.resolve(options.output), title: `video-${randomBytes(6).toString('hex')}` };
|
|
23
|
-
}
|
|
24
|
-
const fields = {
|
|
25
|
-
title: metadata.title || metadata.id || 'video', id: metadata.id || 'unknown',
|
|
26
|
-
channel: metadata.channel || metadata.uploader || 'Unknown channel',
|
|
27
|
-
year: /^\d{4}/.exec(metadata.upload_date || '')?.[0] || 'Unknown year',
|
|
28
|
-
playlist: options._playlistTitle || metadata.playlist_title || metadata.playlist || 'No playlist',
|
|
29
|
-
index: String(options._entryIndex || metadata.playlist_index || 1).padStart(3, '0'),
|
|
30
|
-
};
|
|
31
|
-
const expand = template => template.replace(/\{([^{}]+)\}/g, (_, field) => sanitizeTitle(String(fields[field])));
|
|
32
|
-
const title = options.filenameTemplate ? sanitizeTitle(expand(validateTemplate(options.filenameTemplate))) : fallbackTitle;
|
|
33
|
-
const folders = options.folderTemplate ? validateTemplate(options.folderTemplate, { folders: true }).split('/').map(part => sanitizeTitle(expand(part))) : [];
|
|
34
|
-
return { directory: path.resolve(options.output, ...folders), title };
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Refuse symlink/junction subfolders: metadata must never redirect a save outside output.
|
|
38
|
-
export async function prepareDestination(root, directory, { create = false } = {}) {
|
|
39
|
-
root = path.resolve(root); directory = path.resolve(directory);
|
|
40
|
-
const relative = path.relative(root, directory);
|
|
41
|
-
if (relative.startsWith('..' + path.sep) || relative === '..' || path.isAbsolute(relative)) throw new Error('Destination escapes output directory.');
|
|
42
|
-
if (create) await mkdir(root, { recursive: true });
|
|
43
|
-
let current = root;
|
|
44
|
-
for (const component of relative.split(path.sep).filter(Boolean)) {
|
|
45
|
-
current = path.join(current, component);
|
|
46
|
-
const info = await lstat(current).catch(error => { if (error.code === 'ENOENT') return null; throw error; });
|
|
47
|
-
if (info && (!info.isDirectory() || info.isSymbolicLink())) throw new Error(`Output subfolder is not a plain directory: ${current}`);
|
|
48
|
-
if (!info && create) {
|
|
49
|
-
await mkdir(current).catch(error => { if (error.code !== 'EEXIST') throw error; });
|
|
50
|
-
const created = await lstat(current);
|
|
51
|
-
if (!created.isDirectory() || created.isSymbolicLink()) throw new Error(`Output subfolder is not a plain directory: ${current}`);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
package/src/open-file.js
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
|
|
4
|
-
// Pass the absolute filename as one argument, never through a shell. Detach so
|
|
5
|
-
// a media player that stays running does not keep the CLI alive.
|
|
6
|
-
export function openFile(filename, { platform = process.platform, spawnProcess = spawn } = {}) {
|
|
7
|
-
const command = platform === 'win32' ? 'explorer.exe' : platform === 'darwin' ? 'open'
|
|
8
|
-
: platform === 'android' ? 'termux-open' : 'xdg-open';
|
|
9
|
-
const absolute = path.resolve(filename);
|
|
10
|
-
return new Promise((resolve, reject) => {
|
|
11
|
-
const child = spawnProcess(command, [absolute], { shell: false, detached: true, stdio: 'ignore', windowsHide: true });
|
|
12
|
-
child.once('error', reject);
|
|
13
|
-
child.once('spawn', () => { child.unref(); resolve(); });
|
|
14
|
-
});
|
|
15
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
// Match misspelled flags without silently accepting them. Ignore punctuation
|
|
2
|
-
// differences first, then allow a small number of insertions or transpositions.
|
|
3
|
-
function editDistance(left, right) {
|
|
4
|
-
const rows = Array.from({ length: left.length + 1 }, () => Array(right.length + 1).fill(0));
|
|
5
|
-
for (let i = 0; i <= left.length; i++) rows[i][0] = i;
|
|
6
|
-
for (let j = 0; j <= right.length; j++) rows[0][j] = j;
|
|
7
|
-
for (let i = 1; i <= left.length; i++) {
|
|
8
|
-
for (let j = 1; j <= right.length; j++) {
|
|
9
|
-
rows[i][j] = Math.min(rows[i - 1][j] + 1, rows[i][j - 1] + 1,
|
|
10
|
-
rows[i - 1][j - 1] + (left[i - 1] === right[j - 1] ? 0 : 1));
|
|
11
|
-
if (i > 1 && j > 1 && left[i - 1] === right[j - 2] && left[i - 2] === right[j - 1]) {
|
|
12
|
-
rows[i][j] = Math.min(rows[i][j], rows[i - 2][j - 2] + 1);
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
return rows[left.length][right.length];
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function suggestOption(unknown, options) {
|
|
20
|
-
const normalized = value => value.toLowerCase().replace(/-/g, '');
|
|
21
|
-
const target = normalized(unknown);
|
|
22
|
-
const prefix = unknown.startsWith('--') ? '--' : unknown.startsWith('-') ? '-' : null;
|
|
23
|
-
const candidates = [...new Set(options)].filter(option => prefix ? option.startsWith(prefix) : !option.startsWith('-'));
|
|
24
|
-
const ranked = candidates.map(option => ({ option, distance: editDistance(target, normalized(option)) }))
|
|
25
|
-
.sort((a, b) => a.distance - b.distance || a.option.length - b.option.length);
|
|
26
|
-
const best = ranked[0];
|
|
27
|
-
return best && best.distance <= Math.min(3, Math.max(1, Math.floor(target.length / 3))) ? best.option : null;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function optionSpellings(schema) {
|
|
31
|
-
return Object.entries(schema).flatMap(([name, details]) => [
|
|
32
|
-
`--${name}`, ...(details.short ? [`-${details.short}`] : []),
|
|
33
|
-
...(details.type === 'boolean' ? [`--no-${name}`] : []),
|
|
34
|
-
]);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function explainUnknownOption(error, schema) {
|
|
38
|
-
if (error?.code !== 'ERR_PARSE_ARGS_UNKNOWN_OPTION') return error;
|
|
39
|
-
const unknown = /Unknown option '([^']+)'/.exec(error.message)?.[1];
|
|
40
|
-
if (!unknown) return error;
|
|
41
|
-
const suggestion = suggestOption(unknown, optionSpellings(schema));
|
|
42
|
-
return new Error(`Unknown option "${unknown}".${suggestion ? ` Did you mean "${suggestion}"?` : ' Run veo --help to see available options.'}`, { cause: error });
|
|
43
|
-
}
|
package/src/output.js
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
-
import { styleText, terminalText, terminalTitle } from './progress.js';
|
|
3
|
-
import { linkOutputPaths } from './path-links.js';
|
|
4
|
-
import { terminalColumns } from './terminal-size.js';
|
|
5
|
-
|
|
6
|
-
const settings = new AsyncLocalStorage();
|
|
7
|
-
export const withOutputSettings = (enabled, work) => settings.run({ enabled }, work);
|
|
8
|
-
|
|
9
|
-
export function outputOptions(args) {
|
|
10
|
-
let profile, color;
|
|
11
|
-
const remaining = [];
|
|
12
|
-
for (let i = 0; i < args.length; i++) {
|
|
13
|
-
const arg = args[i];
|
|
14
|
-
if (arg === '--') { remaining.push(...args.slice(i)); break; }
|
|
15
|
-
if (arg === '--profile' || arg.startsWith('--profile=')) {
|
|
16
|
-
profile = arg === '--profile' ? args[++i] : arg.slice(10);
|
|
17
|
-
if (!profile || profile.startsWith('--')) throw new Error('--profile requires a profile name.');
|
|
18
|
-
} else {
|
|
19
|
-
if (arg === '--color') color = true;
|
|
20
|
-
if (arg === '--no-color') color = false;
|
|
21
|
-
remaining.push(arg);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
return { profile, color, remaining };
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function formatOutput(text, stream, enabled = settings.getStore()?.enabled !== false) {
|
|
28
|
-
return text.split(/(\r?\n)/).map(line => {
|
|
29
|
-
if (!line.trim() || /\x1b/.test(line)) return line;
|
|
30
|
-
const value = line.trim();
|
|
31
|
-
const original = line;
|
|
32
|
-
// Limit display values, keeping help, JSON/config text and runnable commands intact.
|
|
33
|
-
if (stream.isTTY) {
|
|
34
|
-
const indent = line.match(/^\s*/)[0];
|
|
35
|
-
const available = { isTTY: true, columns: Math.max(2, terminalColumns(stream) - indent.length) };
|
|
36
|
-
if (/^\d+\. /.test(value)) line = terminalTitle(available, value).split('\n').map(part => indent + part).join('\n');
|
|
37
|
-
else if (/^(?:URL[s]?:|Saved:|Would save:|File:|Missing:|Output:|Job:|Error:|Profile:|Media:|Format:|Streams:|Source:|Title:|Command directory:|Config OK:|veo:|\[?(?:OK|FAIL)\]?\s|[0-9a-z]{6}\s+\d+\s|https?:\/\/)/.test(value)
|
|
38
|
-
|| /^\s{6,}\S/.test(line)) line = indent + terminalText(available, value);
|
|
39
|
-
}
|
|
40
|
-
let role = 'muted';
|
|
41
|
-
if (/^(?:veo (?:stats|history|changes|changelog|runs|stop|doctor|flush|update|backend|alias|uninstall)\b|veo \d[^ ]* doctor|ID\s+PID\s+STATE|Usage:|Options:|Commands:|Examples:|\d+\. )/.test(value)) role = 'title';
|
|
42
|
-
// A bare version line introduces one release in `veo changes` range output; the
|
|
43
|
-
// list rows carry a summary and stay muted.
|
|
44
|
-
if (/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.]+)?(?:\s+\(installed\))?$/.test(value)) role = 'title';
|
|
45
|
-
if (/^(?:\[?OK\]?\s|Saved:|Flushed:|Stopped\b|Removed\b|Config OK:|Statistics reset|Managed tools are ready)|\bis up to date\b|\bupdated to\b|\binstalled and will be used\b/i.test(value)) role = 'success';
|
|
46
|
-
if (/^(?:\[?FAIL\]?\s|Error:|veo: (?!note:))|Status:\s*(?:failed|cancelled)/i.test(value)) role = 'error';
|
|
47
|
-
if (/Status:\s*saved/.test(value)) role = 'success';
|
|
48
|
-
if (/^(?:Videos saved|Audio saved|Download time):/.test(value)) role = 'title';
|
|
49
|
-
if (/^Total failures:\s*[1-9]/.test(value)) role = 'error';
|
|
50
|
-
if (/^[1-9]\d* problems? found/.test(value)) role = 'error';
|
|
51
|
-
if (/^No problems found/.test(value)) role = 'success';
|
|
52
|
-
if (/^Config reset:/.test(value)) role = 'success';
|
|
53
|
-
if (/^Summary:/.test(value)) role = /\b[1-9]\d* failed\b|cancelled/.test(value) ? 'error' : 'success';
|
|
54
|
-
return styleText(stream, linkOutputPaths(stream, original, line), role, enabled);
|
|
55
|
-
}).join('');
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Wrap only application-owned prose; no global stream monkey-patching.
|
|
59
|
-
export function outputStream(stream, { enabled = settings.getStore()?.enabled !== false, plain = false } = {}) {
|
|
60
|
-
if (plain || !stream.isTTY) return stream;
|
|
61
|
-
return {
|
|
62
|
-
isTTY: stream.isTTY,
|
|
63
|
-
get columns() { return terminalColumns(stream); },
|
|
64
|
-
on(event, listener) { stream.on?.(event, listener); },
|
|
65
|
-
removeListener(event, listener) { stream.removeListener?.(event, listener); },
|
|
66
|
-
write(chunk, ...args) {
|
|
67
|
-
if (typeof chunk !== 'string') return stream.write(chunk, ...args);
|
|
68
|
-
// Live redraw frames already carry erase/cursor sequences; prose fitting
|
|
69
|
-
// and role styling must not touch them, otherwise the invisible styling
|
|
70
|
-
// bytes push raw lines past the terminal width.
|
|
71
|
-
if (chunk.includes('\x1b')) return stream.write(chunk, ...args);
|
|
72
|
-
return stream.write(formatOutput(chunk, stream, enabled), ...args);
|
|
73
|
-
},
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export function commandOutput(args, ...streams) {
|
|
78
|
-
const enabled = (outputOptions(args).color ?? settings.getStore()?.enabled) !== false;
|
|
79
|
-
const plain = args.includes('--json');
|
|
80
|
-
return [args.filter(arg => arg !== '--no-color' && arg !== '--color'), ...streams.map(stream => outputStream(stream, { enabled, plain }))];
|
|
81
|
-
}
|
package/src/path-links.js
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
import { statSync } from 'node:fs';
|
|
3
|
-
import { pathToFileURL } from 'node:url';
|
|
4
|
-
|
|
5
|
-
export function supportsPathLinks(stream, env = process.env) {
|
|
6
|
-
return Boolean(stream.isTTY && env.TERM !== 'dumb' &&
|
|
7
|
-
(env.WT_SESSION || env.TERM_PROGRAM === 'iTerm.app' || env.TERM_PROGRAM === 'WezTerm' ||
|
|
8
|
-
env.TERM_PROGRAM === 'vscode' || env.KITTY_WINDOW_ID || Number(env.VTE_VERSION) >= 5000));
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
// File links target their containing folder so clicking does not play the media.
|
|
12
|
-
export function folderLink(stream, target, label = target, env = process.env) {
|
|
13
|
-
const safeLabel = String(label).replace(/[\x00-\x1f\x7f-\x9f]/g, ' ');
|
|
14
|
-
if (!supportsPathLinks(stream, env)) return safeLabel;
|
|
15
|
-
try {
|
|
16
|
-
const absolute = path.resolve(target);
|
|
17
|
-
const folder = statSync(absolute).isDirectory() ? absolute : path.dirname(absolute);
|
|
18
|
-
const url = pathToFileURL(folder + path.sep).href;
|
|
19
|
-
return `\x1b]8;;${url}\x1b\\${safeLabel}\x1b]8;;\x1b\\`;
|
|
20
|
-
} catch { return safeLabel; }
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// Recognize absolute paths in application prose before display truncation.
|
|
24
|
-
// Resolve the longest existing candidate so spaces and parentheses in names survive.
|
|
25
|
-
export function linkOutputPaths(stream, original, displayed = original, env = process.env) {
|
|
26
|
-
if (!supportsPathLinks(stream, env) || /\x1b/.test(original) ||
|
|
27
|
-
/^\s*(?:["{[]|Retry:|Usage:|Manual command:|Run with|Stop:|Cleanup:)/.test(original)) return displayed;
|
|
28
|
-
const matches = [...original.matchAll(/(?:^|[\s("'])([A-Za-z]:[\\/]|\\\\[^\\\s]+\\|\/(?!\/))/g)];
|
|
29
|
-
for (const match of matches.reverse()) {
|
|
30
|
-
const start = match.index + match[0].length - match[1].length;
|
|
31
|
-
const rest = original.slice(start);
|
|
32
|
-
const ends = [rest.length];
|
|
33
|
-
for (let i = rest.length - 1; i > 0; i--) {
|
|
34
|
-
if (/[\s)"',;:.]/.test(rest[i])) ends.push(i);
|
|
35
|
-
}
|
|
36
|
-
let target;
|
|
37
|
-
for (const end of ends) {
|
|
38
|
-
const candidate = rest.slice(0, end);
|
|
39
|
-
try { statSync(candidate); target = candidate; break; } catch { /* try a prose boundary */ }
|
|
40
|
-
}
|
|
41
|
-
if (!target) continue;
|
|
42
|
-
// Truncation has already happened; keep the complete destination in the URL.
|
|
43
|
-
if (displayed.slice(start, start + target.length) === target) {
|
|
44
|
-
displayed = displayed.slice(0, start) + folderLink(stream, target, target, env) + displayed.slice(start + target.length);
|
|
45
|
-
} else {
|
|
46
|
-
const visible = displayed.slice(start);
|
|
47
|
-
if (visible.endsWith('…') && target.startsWith(visible.slice(0, -1))) {
|
|
48
|
-
displayed = displayed.slice(0, start) + folderLink(stream, target, visible, env);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
return displayed;
|
|
53
|
-
}
|
package/src/paths.js
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import os from 'node:os';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Single source of truth for veo's per-user cache root. Used for the yt-dlp
|
|
6
|
-
* backend, the staged FFmpeg binaries, and small state files.
|
|
7
|
-
* An invalid relative OS cache setting must never write into the output directory.
|
|
8
|
-
*/
|
|
9
|
-
export function cacheBase({ platform = process.platform, env = process.env, home = os.homedir() } = {}) {
|
|
10
|
-
let base;
|
|
11
|
-
if (platform === 'win32') base = env.LOCALAPPDATA || path.join(home, 'AppData', 'Local');
|
|
12
|
-
else if (platform === 'darwin') base = path.join(home, 'Library', 'Caches');
|
|
13
|
-
else base = env.XDG_CACHE_HOME || path.join(home, '.cache');
|
|
14
|
-
if (!path.isAbsolute(base)) base = path.join(home, '.cache');
|
|
15
|
-
return path.join(base, 'veo');
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/** Per-user configuration directory, following each platform's own convention. */
|
|
19
|
-
export function configBase({ platform = process.platform, env = process.env, home = os.homedir() } = {}) {
|
|
20
|
-
let base;
|
|
21
|
-
if (platform === 'win32') base = env.APPDATA || path.join(home, 'AppData', 'Roaming');
|
|
22
|
-
else if (platform === 'darwin') base = path.join(home, 'Library', 'Application Support');
|
|
23
|
-
else base = env.XDG_CONFIG_HOME || path.join(home, '.config');
|
|
24
|
-
if (!path.isAbsolute(base)) base = path.join(home, '.config');
|
|
25
|
-
return path.join(base, 'veo');
|
|
26
|
-
}
|
package/src/playlist.js
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
export function validateItems(value) {
|
|
2
|
-
if (typeof value !== 'string' || !/^\d+(?:-\d+)?(?:,\d+(?:-\d+)?)*$/.test(value)) {
|
|
3
|
-
throw new Error('--playlist-items expects positive indices or ranges, e.g. 1,3-5.');
|
|
4
|
-
}
|
|
5
|
-
for (const part of value.split(',')) {
|
|
6
|
-
const [start, end = start] = part.split('-').map(Number);
|
|
7
|
-
if (!Number.isSafeInteger(start) || !Number.isSafeInteger(end) || start < 1 || end < start) {
|
|
8
|
-
throw new Error('--playlist-items requires ascending, positive ranges.');
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
return value;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function selectedEntries(metadata, selection) {
|
|
15
|
-
const ranges = selection ? validateItems(selection).split(',').map(part => part.split('-').map(Number)) : null;
|
|
16
|
-
const entries = (metadata.entries || []).map((entry, offset) => ({ entry, index: entry?.playlist_index || offset + 1 }));
|
|
17
|
-
const selected = entries.filter(({ index }) => !ranges || ranges.some(([start, end = start]) => index >= start && index <= end));
|
|
18
|
-
if (!selected.length) throw new Error('No playlist entries match the selection.');
|
|
19
|
-
return selected;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function sizeEstimate(entries) {
|
|
23
|
-
const sizes = entries.map(({ entry }) => entry?.filesize || entry?.filesize_approx);
|
|
24
|
-
const known = sizes.filter(value => Number.isFinite(value) && value > 0);
|
|
25
|
-
return { estimatedBytes: known.reduce((sum, value) => sum + value, 0), knownSizes: known.length, totalEntries: entries.length };
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function describeEstimate(estimate) {
|
|
29
|
-
if (!estimate.knownSizes) return `${estimate.totalEntries} entries; total size unknown`;
|
|
30
|
-
return `${estimate.totalEntries} entries; approximately ${(estimate.estimatedBytes / 1048576).toFixed(1)} MiB${estimate.knownSizes < estimate.totalEntries ? ` for ${estimate.knownSizes} entries; remaining sizes unknown` : ''}`;
|
|
31
|
-
}
|