@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
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { planBatchActions } from '../batch/plan.js';
|
|
3
|
+
import { cloneMetadataForOutput } from '../batch/clone-metadata.js';
|
|
4
|
+
import { runActionsPipeline } from '../actions/index.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Apply actions pipeline across videos in a directory.
|
|
8
|
+
*
|
|
9
|
+
* @param {string} targetDir
|
|
10
|
+
* @param {object} [options]
|
|
11
|
+
* @param {string} [options.metadataFile]
|
|
12
|
+
* @param {string} [options.actionsFile]
|
|
13
|
+
* @param {string} [options.outputDir]
|
|
14
|
+
* @param {string} [options.outputSuffix]
|
|
15
|
+
* @param {string} [options.only]
|
|
16
|
+
* @param {boolean} [options.force=false]
|
|
17
|
+
* @param {boolean} [options.dryRun=false]
|
|
18
|
+
* @param {string} [options.pexelsKey]
|
|
19
|
+
* @param {string} [options.pixabayKey]
|
|
20
|
+
* @param {string} [options.cacheDir]
|
|
21
|
+
* @param {typeof fetch} [options.fetchFn]
|
|
22
|
+
* @param {(event: { type: string, [key: string]: any }) => void} [options.onEvent]
|
|
23
|
+
* @returns {Promise<{
|
|
24
|
+
* sourceDir: string,
|
|
25
|
+
* outputDir: string,
|
|
26
|
+
* total: number,
|
|
27
|
+
* processed: Array<object>,
|
|
28
|
+
* skipped: Array<object>,
|
|
29
|
+
* failed: Array<object>,
|
|
30
|
+
* dryRun: boolean
|
|
31
|
+
* }>}
|
|
32
|
+
*/
|
|
33
|
+
export async function batchApplyActions(targetDir, options = {}) {
|
|
34
|
+
const plan = planBatchActions(targetDir, options);
|
|
35
|
+
const onEvent = options.onEvent;
|
|
36
|
+
|
|
37
|
+
if (onEvent) {
|
|
38
|
+
onEvent({
|
|
39
|
+
type: 'actions_plan',
|
|
40
|
+
sourceDir: plan.sourceDir,
|
|
41
|
+
outputDir: plan.outputDir,
|
|
42
|
+
hasShorten: plan.hasShorten,
|
|
43
|
+
total: plan.entries.length,
|
|
44
|
+
entries: plan.entries,
|
|
45
|
+
dryRun: plan.dryRun,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const results = [];
|
|
50
|
+
const skipped = [];
|
|
51
|
+
const failed = [];
|
|
52
|
+
|
|
53
|
+
const context = {
|
|
54
|
+
sourceDir: plan.sourceDir,
|
|
55
|
+
outputDir: plan.outputDir,
|
|
56
|
+
rawMetadata: plan.rawMetadata,
|
|
57
|
+
journeyConfig: plan.journeyConfig,
|
|
58
|
+
dryRun: plan.dryRun,
|
|
59
|
+
force: plan.force,
|
|
60
|
+
pexelsKey: options.pexelsKey,
|
|
61
|
+
pixabayKey: options.pixabayKey,
|
|
62
|
+
cacheDir: options.cacheDir,
|
|
63
|
+
fetchFn: options.fetchFn,
|
|
64
|
+
onEvent,
|
|
65
|
+
onWarn: (msg) => {
|
|
66
|
+
if (onEvent) {
|
|
67
|
+
onEvent({ type: 'action_warn', message: msg });
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
for (let i = 0; i < plan.entries.length; i++) {
|
|
73
|
+
const entry = plan.entries[i];
|
|
74
|
+
|
|
75
|
+
if (onEvent) {
|
|
76
|
+
onEvent({
|
|
77
|
+
type: 'entry_start',
|
|
78
|
+
index: i,
|
|
79
|
+
total: plan.entries.length,
|
|
80
|
+
songKey: entry.songKey,
|
|
81
|
+
filename: entry.filename,
|
|
82
|
+
actionsCount: entry.actions.length,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
const res = await runActionsPipeline(entry, entry.actions, context);
|
|
88
|
+
if (res.skipped) {
|
|
89
|
+
skipped.push(res);
|
|
90
|
+
if (onEvent) {
|
|
91
|
+
onEvent({
|
|
92
|
+
type: 'entry_skipped',
|
|
93
|
+
songKey: entry.songKey,
|
|
94
|
+
filename: entry.filename,
|
|
95
|
+
reason: res.reason,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
} else {
|
|
99
|
+
results.push(res);
|
|
100
|
+
if (onEvent) {
|
|
101
|
+
onEvent({
|
|
102
|
+
type: 'entry_success',
|
|
103
|
+
index: i,
|
|
104
|
+
total: plan.entries.length,
|
|
105
|
+
songKey: entry.songKey,
|
|
106
|
+
filename: entry.filename,
|
|
107
|
+
outputPath: res.outputPath,
|
|
108
|
+
actionsApplied: res.actionsApplied,
|
|
109
|
+
steps: res.steps,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
} catch (err) {
|
|
114
|
+
failed.push({
|
|
115
|
+
songKey: entry.songKey,
|
|
116
|
+
filename: entry.filename,
|
|
117
|
+
error: err,
|
|
118
|
+
});
|
|
119
|
+
if (onEvent) {
|
|
120
|
+
onEvent({
|
|
121
|
+
type: 'entry_error',
|
|
122
|
+
index: i,
|
|
123
|
+
total: plan.entries.length,
|
|
124
|
+
songKey: entry.songKey,
|
|
125
|
+
filename: entry.filename,
|
|
126
|
+
error: err,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (!plan.dryRun && results.length > 0) {
|
|
133
|
+
cloneMetadataForOutput(plan.rawMetadata, plan.outputDir, results);
|
|
134
|
+
if (onEvent) {
|
|
135
|
+
onEvent({
|
|
136
|
+
type: 'metadata_cloned',
|
|
137
|
+
outputDir: plan.outputDir,
|
|
138
|
+
outputPath: path.join(plan.outputDir, 'metadata.json'),
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
sourceDir: plan.sourceDir,
|
|
145
|
+
outputDir: plan.outputDir,
|
|
146
|
+
total: plan.entries.length,
|
|
147
|
+
processed: results,
|
|
148
|
+
skipped,
|
|
149
|
+
failed,
|
|
150
|
+
dryRun: plan.dryRun,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import {
|
|
4
|
+
generateJourneySvg,
|
|
5
|
+
renderSvgToPng,
|
|
6
|
+
resolveJourneyTracks,
|
|
7
|
+
truncate,
|
|
8
|
+
} from '../actions/journey-overlay.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Preview journey overlay as a standalone PNG image.
|
|
12
|
+
*
|
|
13
|
+
* @param {string} songKey - Song key or filename to preview
|
|
14
|
+
* @param {object} [options]
|
|
15
|
+
* @param {string} [options.dir] - Target source folder containing metadata.json
|
|
16
|
+
* @param {string} [options.metadata] - Path to custom metadata.json
|
|
17
|
+
* @param {string} [options.centerText] - Override center text for preview
|
|
18
|
+
* @param {string} [options.output] - Output path for the PNG file
|
|
19
|
+
* @param {number} [options.width=1920]
|
|
20
|
+
* @param {number} [options.height=1080]
|
|
21
|
+
* @returns {Promise<{ outputPath: string, width: number, height: number, tracksCount: number }>}
|
|
22
|
+
*/
|
|
23
|
+
export async function previewJourney(songKey, options = {}) {
|
|
24
|
+
const targetDir = path.resolve(options.dir || process.cwd());
|
|
25
|
+
const metadataPath = options.metadata
|
|
26
|
+
? path.resolve(options.metadata)
|
|
27
|
+
: path.join(targetDir, 'metadata.json');
|
|
28
|
+
|
|
29
|
+
let rawMetadata = {};
|
|
30
|
+
if (fs.existsSync(metadataPath)) {
|
|
31
|
+
try {
|
|
32
|
+
rawMetadata = JSON.parse(fs.readFileSync(metadataPath, 'utf-8'));
|
|
33
|
+
} catch (err) {
|
|
34
|
+
throw new Error(`Failed to parse metadata file (${metadataPath}): ${err.message}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const journeyConfig = {
|
|
39
|
+
enabled: true,
|
|
40
|
+
mode: 'before-current',
|
|
41
|
+
position: 'left',
|
|
42
|
+
displayDuration: 5,
|
|
43
|
+
sidebarDisplayDuration: undefined,
|
|
44
|
+
sidebarLabel: 'title',
|
|
45
|
+
theme: 'dark',
|
|
46
|
+
maxCenterChars: 60,
|
|
47
|
+
maxVisibleRows: 8,
|
|
48
|
+
...(rawMetadata.journey || {}),
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const songData = rawMetadata.songs?.[songKey] || {};
|
|
52
|
+
const journeyAction = Array.isArray(songData.actions)
|
|
53
|
+
? songData.actions.find(a => a.type === 'journey-overlay')
|
|
54
|
+
: null;
|
|
55
|
+
|
|
56
|
+
let centerText = options.centerText;
|
|
57
|
+
if (centerText === undefined) {
|
|
58
|
+
if (Array.isArray(journeyAction?.centerConcepts) && journeyAction.centerConcepts.length > 0) {
|
|
59
|
+
const idx = options.conceptIndex !== undefined ? parseInt(options.conceptIndex, 10) : 0;
|
|
60
|
+
centerText = journeyAction.centerConcepts[idx] || journeyAction.centerConcepts[0];
|
|
61
|
+
} else {
|
|
62
|
+
centerText = journeyAction?.centerText || '';
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (centerText && centerText.length > journeyConfig.maxCenterChars) {
|
|
67
|
+
centerText = truncate(centerText, journeyConfig.maxCenterChars);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const tracks = resolveJourneyTracks({
|
|
71
|
+
currentSongKey: songKey,
|
|
72
|
+
rawMetadata,
|
|
73
|
+
mode: journeyConfig.mode,
|
|
74
|
+
maxVisibleRows: journeyConfig.maxVisibleRows,
|
|
75
|
+
sidebarLabel: journeyConfig.sidebarLabel,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const width = options.width || 1920;
|
|
79
|
+
const height = options.height || 1080;
|
|
80
|
+
|
|
81
|
+
const svgContent = generateJourneySvg({
|
|
82
|
+
width,
|
|
83
|
+
height,
|
|
84
|
+
tracks,
|
|
85
|
+
centerText,
|
|
86
|
+
showSidebar: journeyConfig.enabled !== false,
|
|
87
|
+
theme: journeyConfig.theme,
|
|
88
|
+
position: journeyConfig.position,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const pngBuffer = renderSvgToPng(svgContent, width, height);
|
|
92
|
+
|
|
93
|
+
const outputPath = options.output
|
|
94
|
+
? path.resolve(options.output)
|
|
95
|
+
: path.join(targetDir, `journey-preview-${path.basename(songKey, path.extname(songKey))}.png`);
|
|
96
|
+
|
|
97
|
+
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
98
|
+
fs.writeFileSync(outputPath, pngBuffer);
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
outputPath,
|
|
102
|
+
width,
|
|
103
|
+
height,
|
|
104
|
+
tracksCount: tracks.length,
|
|
105
|
+
};
|
|
106
|
+
}
|