@x12i/youtube-video-uploader-cli 1.1.0 → 1.3.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/README.md +362 -25
- package/bin/cli.js +735 -2
- package/package.json +7 -1
- package/src/actions/index.js +274 -0
- package/src/actions/journey-overlay.js +567 -0
- package/src/actions/replace-background.js +154 -0
- package/src/actions/shorten.js +271 -0
- package/src/auth.js +1 -1
- package/src/batch/clone-metadata.js +51 -0
- package/src/batch/plan.js +194 -0
- package/src/cli/apply-actions.js +152 -0
- package/src/cli/preview-journey.js +106 -0
- package/src/index.d.ts +596 -1
- package/src/index.js +91 -1
- package/src/media/cache.js +134 -0
- package/src/media/compose.js +227 -0
- package/src/media/download.js +151 -0
- package/src/media/resolve.js +476 -0
- package/src/media/select.js +176 -0
- package/src/providers/index.js +235 -0
- package/src/providers/pexels.js +171 -0
- package/src/providers/pixabay.js +184 -0
- package/src/state.js +11 -0
- package/src/swap/steps.js +161 -0
- package/src/swap/store.js +120 -0
- package/src/swap/swapSong.js +154 -0
- package/src/update.js +392 -0
- package/src/uploader.js +20 -0
package/README.md
CHANGED
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
# @x12i/youtube-video-uploader-cli 📺
|
|
2
2
|
|
|
3
|
-
> Automated batch video uploader for YouTube Data API v3 with OAuth2 authentication, playlist
|
|
3
|
+
> Automated batch video uploader for YouTube Data API v3 with OAuth2 authentication, album/playlist metadata, safe-quota batching, playlist video swap, and smart upload-state resume.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
## 🚀 Features
|
|
8
8
|
|
|
9
|
-
- **🔐 Interactive OAuth2 Authentication**:
|
|
10
|
-
- **💾 Token Persistence**: Caches tokens
|
|
11
|
-
- **📄 Metadata-Driven Batch Uploads**:
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
9
|
+
- **🔐 Interactive OAuth2 Authentication**: Local loopback server to capture the callback redirect, with a terminal prompt fallback. Scopes: `youtube.upload` + `youtube.force-ssl`.
|
|
10
|
+
- **💾 Token Persistence**: Caches tokens in `token.json` and refreshes automatically.
|
|
11
|
+
- **📄 Metadata-Driven Batch Uploads**: Flat `videos[]` schema **or** rich album/`songs` + `playlist` + `uploadPolicy` + `{{template}}` variables.
|
|
12
|
+
- **📑 Playlist Auto-Create**: Creates a playlist from `playlist.snippet` when no `playlistId` is set, then inserts videos at their metadata positions.
|
|
13
|
+
- **⚡ Smart Skip & Resume**: Saves status to `.upload-history.json` and skips already-uploaded files on rerun.
|
|
14
|
+
- **🛡️ Safe Quota Batching**: `--safe-quota`, `--limit`, and `--max-quota` defer excess uploads for the next day (~1,600 units/video + 50/playlist item).
|
|
15
|
+
- **📊 Quota Estimation**: Warns when estimated units approach the standard 10,000/day limit.
|
|
16
|
+
- **🔁 Playlist Video Swap**: `swap` subcommand replaces a playlist entry in place; old video is unlisted, never deleted. Resumable via `.swap-history.json`.
|
|
17
|
+
- **🏷️ Metadata & Title Updates**: `update-metadata` / `update-titles` subcommand to batch update video titles and playlist titles on YouTube and in `metadata.json` (e.g. adding postfixes, prefixes, or syncing).
|
|
18
|
+
- **🎬 Automated Stock Media Sourcing**: Search Pexels and Pixabay for high-res stock videos/photos, rank and auto-select best matches, cache responses, and compose final `.mp4` files from audio tracks via ffmpeg.
|
|
19
|
+
- **🔍 Dry Run Mode**: Validate metadata and estimate quota before uploading or updating.
|
|
16
20
|
|
|
17
21
|
---
|
|
18
22
|
|
|
@@ -39,6 +43,9 @@ Before using the uploader, you need OAuth2 credentials from Google Cloud:
|
|
|
39
43
|
```bash
|
|
40
44
|
# In the folder containing your .mp4 files and metadata.json:
|
|
41
45
|
npx @x12i/youtube-video-uploader-cli
|
|
46
|
+
|
|
47
|
+
# Cap under daily quota (~5–6 videos), defer the rest:
|
|
48
|
+
npx @x12i/youtube-video-uploader-cli --safe-quota
|
|
42
49
|
```
|
|
43
50
|
|
|
44
51
|
### Install globally
|
|
@@ -52,12 +59,12 @@ yt-uploader [folder]
|
|
|
52
59
|
yvu-cli [folder]
|
|
53
60
|
```
|
|
54
61
|
|
|
62
|
+
Credentials can also come from environment variables (`YOUTUBE_CLIENT_ID` / `YOUTUBE_CLIENT_SECRET`, or `GOOGLE_CLIENT_ID` / `GOOGLE_CLIENT_SECRET`) or a nearby `.env` file.
|
|
63
|
+
|
|
55
64
|
---
|
|
56
65
|
|
|
57
66
|
## 📂 Folder Structure & `metadata.json`
|
|
58
67
|
|
|
59
|
-
Place your videos and a `metadata.json` file in your target folder:
|
|
60
|
-
|
|
61
68
|
```text
|
|
62
69
|
my-videos/
|
|
63
70
|
├── metadata.json
|
|
@@ -66,7 +73,7 @@ my-videos/
|
|
|
66
73
|
└── track3.mp4
|
|
67
74
|
```
|
|
68
75
|
|
|
69
|
-
### `
|
|
76
|
+
### Flat `videos[]` schema
|
|
70
77
|
|
|
71
78
|
```json
|
|
72
79
|
{
|
|
@@ -90,6 +97,72 @@ my-videos/
|
|
|
90
97
|
}
|
|
91
98
|
```
|
|
92
99
|
|
|
100
|
+
### Rich album / `songs` schema
|
|
101
|
+
|
|
102
|
+
Supports album metadata, upload policy, playlist auto-create, ordered keys, and `{{playlistUrl}}` / `{{channelUrl}}` templates:
|
|
103
|
+
|
|
104
|
+
```json
|
|
105
|
+
{
|
|
106
|
+
"channel": {
|
|
107
|
+
"name": "My Artist",
|
|
108
|
+
"channelUrl": "https://www.youtube.com/@myartist"
|
|
109
|
+
},
|
|
110
|
+
"album": {
|
|
111
|
+
"title": "GRAY TO LIGHT",
|
|
112
|
+
"artist": "My Artist"
|
|
113
|
+
},
|
|
114
|
+
"templateVariables": {
|
|
115
|
+
"channelUrl": "https://www.youtube.com/@myartist",
|
|
116
|
+
"playlistUrl": null
|
|
117
|
+
},
|
|
118
|
+
"uploadPolicy": {
|
|
119
|
+
"initialVideoPrivacyStatus": "private",
|
|
120
|
+
"categoryId": "10",
|
|
121
|
+
"notifySubscribersOnInitialUpload": false
|
|
122
|
+
},
|
|
123
|
+
"playlist": {
|
|
124
|
+
"key": "gray_to_light",
|
|
125
|
+
"orderedSongKeys": ["01-first.mp4", "02-second.mp4"],
|
|
126
|
+
"youtube": {
|
|
127
|
+
"snippet": {
|
|
128
|
+
"title": "GRAY TO LIGHT — Full Album",
|
|
129
|
+
"description": "Full album playlist.\n\nChannel: {{channelUrl}}"
|
|
130
|
+
},
|
|
131
|
+
"status": { "privacyStatus": "private" }
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
"songs": {
|
|
135
|
+
"01-first.mp4": {
|
|
136
|
+
"trackNumber": 1,
|
|
137
|
+
"playlistPosition": 0,
|
|
138
|
+
"youtube": {
|
|
139
|
+
"snippet": {
|
|
140
|
+
"title": "First Song — My Artist | GRAY TO LIGHT",
|
|
141
|
+
"description": "Track 1.\n\nPlaylist: {{playlistUrl}}",
|
|
142
|
+
"tags": ["album", "music"]
|
|
143
|
+
},
|
|
144
|
+
"status": { "privacyStatus": "private" }
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
"02-second.mp4": {
|
|
148
|
+
"trackNumber": 2,
|
|
149
|
+
"playlistPosition": 1,
|
|
150
|
+
"youtube": {
|
|
151
|
+
"snippet": {
|
|
152
|
+
"title": "Second Song — My Artist | GRAY TO LIGHT",
|
|
153
|
+
"description": "Track 2.",
|
|
154
|
+
"tags": ["album", "music"]
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
If `playlistId` is missing but `playlist.youtube.snippet` is present, the uploader creates the playlist on first run and stores its ID in `.upload-history.json`. Template variables are re-interpolated after creation so `{{playlistUrl}}` resolves correctly.
|
|
163
|
+
|
|
164
|
+
If no `videos` / `songs` entries exist, all `.mp4` files in the folder are auto-discovered.
|
|
165
|
+
|
|
93
166
|
#### Category IDs:
|
|
94
167
|
- `"10"` = Music (default)
|
|
95
168
|
- `"22"` = People & Blogs
|
|
@@ -100,6 +173,8 @@ my-videos/
|
|
|
100
173
|
|
|
101
174
|
## ⚙️ CLI Options Reference
|
|
102
175
|
|
|
176
|
+
### Batch upload (default command)
|
|
177
|
+
|
|
103
178
|
```text
|
|
104
179
|
Usage: youtube-video-uploader [directory] [options]
|
|
105
180
|
|
|
@@ -111,7 +186,10 @@ Options:
|
|
|
111
186
|
-d, --dir <path> Explicit target directory path
|
|
112
187
|
-m, --metadata <file> Custom path to metadata.json file
|
|
113
188
|
-f, --force Force re-upload of already uploaded videos (default: false)
|
|
114
|
-
-n, --dry-run Preview video uploads and estimate quota without uploading
|
|
189
|
+
-n, --dry-run Preview video uploads and estimate quota without uploading
|
|
190
|
+
-l, --limit <number> Maximum number of videos to upload in this run
|
|
191
|
+
--safe-quota Cap batch under ~9,950 daily units (~5–6 videos); defer the rest
|
|
192
|
+
--max-quota <units> Hard API quota unit cap for this run
|
|
115
193
|
--client-id <id> Google OAuth Client ID
|
|
116
194
|
--client-secret <secret> Google OAuth Client Secret
|
|
117
195
|
--client-secrets-file <file> Path to client_secret.json downloaded from Google Cloud
|
|
@@ -120,6 +198,58 @@ Options:
|
|
|
120
198
|
-h, --help Display help
|
|
121
199
|
```
|
|
122
200
|
|
|
201
|
+
### `swap` subcommand
|
|
202
|
+
|
|
203
|
+
```text
|
|
204
|
+
Usage: youtube-video-uploader swap <videoFile> [options]
|
|
205
|
+
|
|
206
|
+
Arguments:
|
|
207
|
+
videoFile Path to the new MP4 video file
|
|
208
|
+
|
|
209
|
+
Options:
|
|
210
|
+
--playlist <id> Playlist ID containing the old video (required)
|
|
211
|
+
--old-video <videoId> Video ID of the old video to replace (required)
|
|
212
|
+
-k, --key <songKey> Stable song key for swap state (default: video file basename)
|
|
213
|
+
--title <title> Override the new video title (default: carried over from old)
|
|
214
|
+
--state-file <file> Swap state filename (default: .swap-history.json)
|
|
215
|
+
--token-file <file> OAuth token path (default: token.json in cwd)
|
|
216
|
+
--client-secrets-file <file> Path to client_secret.json
|
|
217
|
+
--client-id <id> Google OAuth Client ID
|
|
218
|
+
--client-secret <secret> Google OAuth Client Secret
|
|
219
|
+
--fresh Discard prior swap state for this key and start over
|
|
220
|
+
-n, --dry-run Print the swap plan without any API calls
|
|
221
|
+
-q, --quiet Minimal output mode
|
|
222
|
+
-h, --help Display help
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### `update-metadata` / `update-titles` subcommand
|
|
226
|
+
|
|
227
|
+
```text
|
|
228
|
+
Usage: youtube-video-uploader update-metadata [directory] [options]
|
|
229
|
+
|
|
230
|
+
Arguments:
|
|
231
|
+
directory Target folder containing metadata.json and upload history (default: ".")
|
|
232
|
+
|
|
233
|
+
Options:
|
|
234
|
+
-d, --dir <path> Explicit target directory path
|
|
235
|
+
-m, --metadata <file> Custom path to metadata.json file
|
|
236
|
+
--title-postfix <postfix> Postfix to append to each video title (e.g. " (long version)")
|
|
237
|
+
--playlist-postfix <postfix> Postfix to append to playlist title (e.g. " (long versions)")
|
|
238
|
+
--title-prefix <prefix> Prefix to prepend to each video title
|
|
239
|
+
--playlist-title <title> Explicit new title for the playlist
|
|
240
|
+
--video <videoId> Target a specific YouTube video ID
|
|
241
|
+
--title <title> Explicit new title for the targeted video
|
|
242
|
+
--playlist <playlistId> Target a specific YouTube playlist ID
|
|
243
|
+
--no-update-file Do not update metadata.json on disk
|
|
244
|
+
-n, --dry-run Preview changes without making any API calls
|
|
245
|
+
--token-file <file> OAuth token path (default: token.json in target dir)
|
|
246
|
+
--client-secrets-file <file> Path to client_secret.json
|
|
247
|
+
--client-id <id> Google OAuth Client ID
|
|
248
|
+
--client-secret <secret> Google OAuth Client Secret
|
|
249
|
+
-q, --quiet Minimal output mode
|
|
250
|
+
-h, --help Display help
|
|
251
|
+
```
|
|
252
|
+
|
|
123
253
|
---
|
|
124
254
|
|
|
125
255
|
## 💡 Examples
|
|
@@ -129,7 +259,18 @@ Options:
|
|
|
129
259
|
npx @x12i/youtube-video-uploader-cli ./my-videos --dry-run
|
|
130
260
|
```
|
|
131
261
|
|
|
132
|
-
### 2.
|
|
262
|
+
### 2. Safe-quota batch (upload today’s slice, defer the rest)
|
|
263
|
+
```bash
|
|
264
|
+
npx @x12i/youtube-video-uploader-cli ./my-videos --safe-quota
|
|
265
|
+
# Re-run the same command tomorrow for the next batch
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### 3. Hard limit of N videos
|
|
269
|
+
```bash
|
|
270
|
+
npx @x12i/youtube-video-uploader-cli ./my-videos --limit 5
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### 4. Upload with credentials from environment variables
|
|
133
274
|
```bash
|
|
134
275
|
export YOUTUBE_CLIENT_ID="your-client-id.apps.googleusercontent.com"
|
|
135
276
|
export YOUTUBE_CLIENT_SECRET="your-client-secret"
|
|
@@ -137,29 +278,225 @@ export YOUTUBE_CLIENT_SECRET="your-client-secret"
|
|
|
137
278
|
npx @x12i/youtube-video-uploader-cli ./my-videos
|
|
138
279
|
```
|
|
139
280
|
|
|
140
|
-
###
|
|
281
|
+
### 5. Upload with downloaded `client_secret.json`
|
|
141
282
|
```bash
|
|
142
283
|
npx @x12i/youtube-video-uploader-cli ./my-videos --client-secrets-file ./client_secret.json
|
|
143
284
|
```
|
|
144
285
|
|
|
145
286
|
---
|
|
146
287
|
|
|
147
|
-
##
|
|
288
|
+
## 🔁 Replacing a Video in a Playlist (`swap`)
|
|
148
289
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
290
|
+
Re-uploads a song (e.g. a new version with lyrics subtitles) and swaps it into a playlist in place of the old video. The old video is **unlisted, never deleted**, so existing direct links keep working.
|
|
291
|
+
|
|
292
|
+
```bash
|
|
293
|
+
yt-uploader swap ./song-v2.mp4 --playlist PLxxxx --old-video VIDEOID
|
|
294
|
+
yt-uploader swap ./song-v2.mp4 --playlist PLxxxx --old-video VIDEOID --dry-run
|
|
295
|
+
yt-uploader swap ./song-v2.mp4 --playlist PLxxxx --old-video VIDEOID --key "01-first.mp4" --title "First Song (Lyrics)"
|
|
296
|
+
yt-uploader swap ./song-v2.mp4 --playlist PLxxxx --old-video VIDEOID --fresh
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
The swap runs as a resumable five-step state machine (state persisted to `.swap-history.json` after every step):
|
|
300
|
+
|
|
301
|
+
1. **UPLOAD** — upload the new video *unlisted* (metadata carried over from the old video)
|
|
302
|
+
2. **LOCATE** — find the old video's playlist item + position
|
|
303
|
+
3. **INSERT** — insert the new video at the old position
|
|
304
|
+
4. **REMOVE** — remove the old playlist item
|
|
305
|
+
5. **FINALIZE** — old video → unlisted, new video → public
|
|
306
|
+
|
|
307
|
+
If any step fails, re-run the same command — completed steps are skipped automatically (no duplicate uploads or playlist entries). A missing old video aborts with a "needs manual review" flag instead of guessing. Use `--fresh` to discard prior state for that key.
|
|
308
|
+
|
|
309
|
+
**Quota cost:** ~150 shared-pool units + 1 upload-bucket unit per swap (well within the 10,000/day default pool).
|
|
310
|
+
|
|
311
|
+
**Notes:**
|
|
312
|
+
- OAuth scopes are `youtube.upload` + `youtube.force-ssl` — existing `token.json` files need a one-time re-consent if issued under older scopes.
|
|
313
|
+
- The target playlist must use **Manual** ordering for position-preserving inserts (set in YouTube Studio).
|
|
314
|
+
- Videos from an unverified Google Cloud project are forced private until the project passes YouTube's API audit.
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## 🎬 Automated Stock Media Sourcing (Pexels + Pixabay)
|
|
319
|
+
|
|
320
|
+
If you have audio tracks (`.wav`, `.mp3`) and no visuals, the uploader can automatically query Pexels and Pixabay for relevant stock video/photo backgrounds, select the best candidate, download it, and compose `.mp4` videos via FFmpeg before uploading.
|
|
321
|
+
|
|
322
|
+
### Environment Variables & Credentials
|
|
323
|
+
|
|
324
|
+
| Variable | Description | Aliases |
|
|
325
|
+
|---|---|---|
|
|
326
|
+
| `PEXELS_KEY` | Pexels API Key | `PEXEL_KEY` |
|
|
327
|
+
| `PIXABAY_KEY` | Pixabay API Key | `PICABAY_KEY` |
|
|
328
|
+
| `MEDIA_PROVIDER_ORDER` | Fallback search order (default: `pexels,pixabay`) | |
|
|
329
|
+
|
|
330
|
+
API keys can also be specified via CLI flags (`--pexels-key <key>`, `--pixabay-key <key>`) or in a `.env` file.
|
|
331
|
+
|
|
332
|
+
### CLI Commands
|
|
333
|
+
|
|
334
|
+
#### 1. Preview search results (`search-media`)
|
|
335
|
+
Preview ranked candidates without downloading anything:
|
|
336
|
+
```bash
|
|
337
|
+
yt-uploader search-media "calm night drive neon city" --type video --count 5
|
|
338
|
+
yt-uploader search-media "sunset over ocean" --provider pixabay --orientation landscape --json
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
#### 2. Resolve media & write to `metadata.json` (`resolve-media`)
|
|
342
|
+
Search, download winning media to `.media-cache/assets/`, and optionally compose the final `.mp4`:
|
|
343
|
+
```bash
|
|
344
|
+
# Resolve and download assets, update metadata.json
|
|
345
|
+
yt-uploader resolve-media ./my-songs
|
|
346
|
+
|
|
347
|
+
# Resolve, download, and build the final .mp4 with ffmpeg immediately
|
|
348
|
+
yt-uploader resolve-media ./my-songs --compose
|
|
349
|
+
|
|
350
|
+
# Dry-run preview
|
|
351
|
+
yt-uploader resolve-media ./my-songs --dry-run
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
#### 3. Upload with automatic media resolution (`--auto-media`)
|
|
355
|
+
Run media resolution and ffmpeg composition automatically for missing video files before running the batch upload:
|
|
356
|
+
```bash
|
|
357
|
+
yt-uploader ./my-songs --auto-media
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
#### 4. Standalone Video Composer (`compose-video`)
|
|
361
|
+
Mux any background video or photo with an audio file:
|
|
362
|
+
```bash
|
|
363
|
+
yt-uploader compose-video --background ./bg.mp4 --audio ./song.wav --output ./final.mp4
|
|
364
|
+
yt-uploader compose-video --background ./photo.jpg --audio ./song.wav --output ./final.mp4 --mode ken-burns
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### `metadata.json` Configuration
|
|
368
|
+
|
|
369
|
+
Add optional top-level `mediaPolicy` or per-song `media` overrides:
|
|
370
|
+
|
|
371
|
+
```json
|
|
372
|
+
{
|
|
373
|
+
"mediaPolicy": {
|
|
374
|
+
"provider": "auto",
|
|
375
|
+
"providerOrder": ["pexels", "pixabay"],
|
|
376
|
+
"mergeStrategy": "fallback",
|
|
377
|
+
"type": "video",
|
|
378
|
+
"orientation": "landscape",
|
|
379
|
+
"minWidth": 1280,
|
|
380
|
+
"minHeight": 720,
|
|
381
|
+
"safeSearch": true,
|
|
382
|
+
"avoidReuse": true,
|
|
383
|
+
"composeMode": "video-loop",
|
|
384
|
+
"cacheDir": ".media-cache"
|
|
385
|
+
},
|
|
386
|
+
"songs": {
|
|
387
|
+
"01_track.wav": {
|
|
388
|
+
"youtube": {
|
|
389
|
+
"snippet": {
|
|
390
|
+
"title": "Night Lights"
|
|
391
|
+
}
|
|
392
|
+
},
|
|
393
|
+
"media": {
|
|
394
|
+
"query": "neon city driving rain",
|
|
395
|
+
"type": "video",
|
|
396
|
+
"orientation": "landscape"
|
|
397
|
+
}
|
|
158
398
|
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
After resolution, `resolvedMedia` is saved directly to `metadata.json` and `ATTRIBUTIONS.md` is generated automatically with full licensing credits.
|
|
404
|
+
|
|
405
|
+
---
|
|
406
|
+
|
|
407
|
+
## 🎬 Batch Video Actions Pipeline
|
|
408
|
+
|
|
409
|
+
The actions pipeline allows non-destructive video modifications (shortening, background replacement, and playlist journey overlays) processed into a **new sibling folder** with cloned `metadata.json`:
|
|
410
|
+
|
|
411
|
+
### Available Actions
|
|
412
|
+
|
|
413
|
+
1. **`shorten`**: Cut video at `cutPoint`, smoothly fade out audio and video into the cut, then append a silent padding buffer.
|
|
414
|
+
2. **`replace-background`**: Keep existing audio and swap the visual background with a freshly sourced stock video or photo.
|
|
415
|
+
3. **`journey-overlay`**: Burn in a sidebar playlist graphic (tracking past and current songs) and optional centered text.
|
|
416
|
+
|
|
417
|
+
### `metadata.json` Actions Configuration
|
|
418
|
+
|
|
419
|
+
```json
|
|
420
|
+
{
|
|
421
|
+
"defaultActions": [
|
|
422
|
+
{ "type": "journey-overlay" }
|
|
423
|
+
],
|
|
424
|
+
"journey": {
|
|
425
|
+
"enabled": true,
|
|
426
|
+
"mode": "before-current",
|
|
427
|
+
"theme": "dark",
|
|
428
|
+
"displayDuration": 5,
|
|
429
|
+
"maxCenterChars": 60
|
|
159
430
|
},
|
|
431
|
+
"songs": {
|
|
432
|
+
"01-first.mp4": {
|
|
433
|
+
"actions": [
|
|
434
|
+
{ "type": "shorten", "cutPoint": "02:15", "fadeDuration": 3, "paddingDuration": 2 },
|
|
435
|
+
{ "type": "journey-overlay", "centerText": "What does the night sound like?" }
|
|
436
|
+
]
|
|
437
|
+
},
|
|
438
|
+
"02-second.mp4": {
|
|
439
|
+
"actions": [
|
|
440
|
+
{ "type": "replace-background", "query": "empty highway sunrise" }
|
|
441
|
+
]
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
### CLI Commands
|
|
448
|
+
|
|
449
|
+
#### Apply Actions (`apply-actions`)
|
|
450
|
+
```bash
|
|
451
|
+
# Process all videos and output to sibling folder (e.g. ./my-videos (short))
|
|
452
|
+
yt-uploader apply-actions ./my-videos
|
|
453
|
+
|
|
454
|
+
# Preview planned ffmpeg operations without modifying disk
|
|
455
|
+
yt-uploader apply-actions ./my-videos --dry-run
|
|
456
|
+
|
|
457
|
+
# Run only a specific video entry
|
|
458
|
+
yt-uploader apply-actions ./my-videos --only 01-first.mp4
|
|
459
|
+
|
|
460
|
+
# Overwrite existing outputs
|
|
461
|
+
yt-uploader apply-actions ./my-videos --force
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
#### Preview Journey Overlay (`preview-journey`)
|
|
465
|
+
Generate a PNG preview of the journey graphic to iterate on sidebar and center text appearance without re-compositing videos:
|
|
466
|
+
```bash
|
|
467
|
+
yt-uploader preview-journey 01-first.mp4 --dir ./my-videos
|
|
468
|
+
yt-uploader preview-journey 01-first.mp4 --center-text "Custom prompt question?" -o ./preview.png
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
---
|
|
472
|
+
|
|
473
|
+
## 🛠️ Programmatic Node.js API
|
|
474
|
+
|
|
475
|
+
```javascript
|
|
476
|
+
import {
|
|
477
|
+
searchMedia,
|
|
478
|
+
resolveMediaForMetadata,
|
|
479
|
+
composeVideo,
|
|
480
|
+
batchUpload,
|
|
481
|
+
} from '@x12i/youtube-video-uploader-cli';
|
|
482
|
+
|
|
483
|
+
// 1. Search stock media
|
|
484
|
+
const results = await searchMedia({
|
|
485
|
+
query: 'calm night drive neon city',
|
|
486
|
+
type: 'video',
|
|
487
|
+
provider: 'auto',
|
|
160
488
|
});
|
|
161
489
|
|
|
162
|
-
|
|
490
|
+
// 2. Resolve media for metadata.json and compose videos
|
|
491
|
+
const { resolved, skipped, errors } = await resolveMediaForMetadata('./my-videos', {
|
|
492
|
+
compose: true,
|
|
493
|
+
provider: 'auto',
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
// 3. Batch upload
|
|
497
|
+
const uploadResult = await batchUpload('./my-videos', {
|
|
498
|
+
safeQuota: true,
|
|
499
|
+
});
|
|
163
500
|
```
|
|
164
501
|
|
|
165
502
|
---
|