@glissade/cli 0.9.0 → 0.9.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/dist/cli.js +3 -0
- package/dist/cues.js +21 -10
- package/dist/index.d.ts +2 -0
- package/dist/render.js +1 -1
- package/package.json +10 -10
package/dist/cli.js
CHANGED
|
@@ -61,6 +61,8 @@ render options:
|
|
|
61
61
|
--music <m> auto (default): mix a sibling *.music.timing.json bed, ducked under narration | off
|
|
62
62
|
--sfx <m> auto (default): mix effect hits from a sibling *.sfx.timing.json | off
|
|
63
63
|
--chapters <m> vtt: also write WebVTT chapters from cue markers (cues.json is always written when cues exist)
|
|
64
|
+
(YouTube needs the 1st chapter at 0:00 — auto-anchored — and each chapter >= 10s; author cue ts accordingly)
|
|
65
|
+
--chapters-kind <k[,k]> cue kinds that become VTT chapters (default: chapter); cues.json keeps all kinds
|
|
64
66
|
--strict fail on an unregistered font family or an uncovered glyph (§3.6; default: warn)
|
|
65
67
|
|
|
66
68
|
dev options:
|
|
@@ -189,6 +191,7 @@ async function main() {
|
|
|
189
191
|
...frameRange ? { frameRange } : {},
|
|
190
192
|
...formatFlag === "png-seq" ? { format: "png-seq" } : {},
|
|
191
193
|
...flags.get("chapters") === "vtt" ? { chapters: "vtt" } : {},
|
|
194
|
+
...flags.has("chapters-kind") ? { chapterKinds: new Set(flags.get("chapters-kind").split(",").map((s) => s.trim()).filter(Boolean)) } : {},
|
|
192
195
|
...flags.has("trace") ? { trace: flags.get("trace") } : {},
|
|
193
196
|
...flags.has("state") ? { state: flags.get("state") } : {},
|
|
194
197
|
...flags.has("force") ? { force: true } : {},
|
package/dist/cues.js
CHANGED
|
@@ -28,20 +28,31 @@ function vttTime(seconds) {
|
|
|
28
28
|
const p = (n, w = 2) => String(n).padStart(w, "0");
|
|
29
29
|
return `${p(Math.floor(ms / 36e5))}:${p(Math.floor(ms % 36e5 / 6e4))}:${p(Math.floor(ms % 6e4 / 1e3))}.${p(ms % 1e3, 3)}`;
|
|
30
30
|
}
|
|
31
|
+
/** The cue kinds that become WebVTT chapters by default (the YouTube use case). */
|
|
32
|
+
const DEFAULT_CHAPTER_KINDS = new Set(["chapter"]);
|
|
31
33
|
/**
|
|
32
|
-
* WebVTT chapters from cues
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
34
|
+
* WebVTT chapters from cues. Only cues whose `kind` is in `kinds` (default just
|
|
35
|
+
* `'chapter'`) become chapters — ad-break / plain `cue` markers stay out of the
|
|
36
|
+
* chapter list (they remain in `cues.json` for machines), so the VTT pastes
|
|
37
|
+
* straight into a YouTube description. Each chapter runs until its duration or
|
|
38
|
+
* the next chapter; the cue text is the human `title` (falling back to `name`),
|
|
39
|
+
* never the machine `kind`. When the earliest chapter starts after 0, a `00:00`
|
|
40
|
+
* "Intro" chapter is auto-anchored (valid WebVTT, and YouTube needs a 0:00 start).
|
|
41
|
+
*
|
|
42
|
+
* Author note — YouTube's chapter rules are author-side, not enforced here:
|
|
43
|
+
* it needs the FIRST chapter at exactly 0:00 (the auto-anchor handles this, but
|
|
44
|
+
* a tiny first chapter from a lead-in will read oddly — pin chapter 1 to t=0
|
|
45
|
+
* instead) and EVERY chapter to be >= 10s (fold a too-short beat into its
|
|
46
|
+
* neighbour). cues.json is unaffected; only the human-pasted VTT cares.
|
|
37
47
|
*/
|
|
38
|
-
function cuesToVtt(cues, totalDuration) {
|
|
39
|
-
const
|
|
48
|
+
function cuesToVtt(cues, totalDuration, kinds = DEFAULT_CHAPTER_KINDS) {
|
|
49
|
+
const chapters = cues.filter((c) => kinds.has(c.kind));
|
|
50
|
+
const anchored = chapters.length > 0 && chapters[0].t > 0 ? [{
|
|
40
51
|
t: 0,
|
|
41
52
|
kind: "chapter",
|
|
42
53
|
name: "Intro",
|
|
43
54
|
title: "Intro"
|
|
44
|
-
}, ...
|
|
55
|
+
}, ...chapters] : chapters;
|
|
45
56
|
let out = "WEBVTT\n\n";
|
|
46
57
|
anchored.forEach((c, i) => {
|
|
47
58
|
const end = c.duration !== void 0 ? c.t + c.duration : anchored[i + 1]?.t ?? totalDuration;
|
|
@@ -54,7 +65,7 @@ function cuesToVtt(cues, totalDuration) {
|
|
|
54
65
|
* `<stem>.chapters.vtt`, next to the render output. Deterministic. Returns the
|
|
55
66
|
* paths written.
|
|
56
67
|
*/
|
|
57
|
-
function writeCueSidecars(outPath, markers, totalDuration, chaptersVtt) {
|
|
68
|
+
function writeCueSidecars(outPath, markers, totalDuration, chaptersVtt, chapterKinds = DEFAULT_CHAPTER_KINDS) {
|
|
58
69
|
const cues = collectCues(markers);
|
|
59
70
|
if (cues.length === 0) return [];
|
|
60
71
|
const isFile = /\.(mp4|webm)$/i.test(outPath);
|
|
@@ -66,7 +77,7 @@ function writeCueSidecars(outPath, markers, totalDuration, chaptersVtt) {
|
|
|
66
77
|
written.push(jsonPath);
|
|
67
78
|
if (chaptersVtt) {
|
|
68
79
|
const vttPath = join(dir, `${prefix}chapters.vtt`);
|
|
69
|
-
writeFileSync(vttPath, cuesToVtt(cues, totalDuration));
|
|
80
|
+
writeFileSync(vttPath, cuesToVtt(cues, totalDuration, chapterKinds));
|
|
70
81
|
written.push(vttPath);
|
|
71
82
|
}
|
|
72
83
|
return written;
|
package/dist/index.d.ts
CHANGED
|
@@ -32,6 +32,8 @@ interface RenderOptions {
|
|
|
32
32
|
sfx?: 'auto' | 'off';
|
|
33
33
|
/** also write WebVTT chapters from cue markers ('vtt'); cues.json is always written when cues exist. */
|
|
34
34
|
chapters?: 'vtt' | 'off';
|
|
35
|
+
/** cue kinds that become VTT chapters (default just 'chapter'); cues.json keeps all kinds. */
|
|
36
|
+
chapterKinds?: ReadonlySet<string>;
|
|
35
37
|
/** --strict: font validation throws on an unregistered family / missing glyph (§3.6). Default dev-warn. */
|
|
36
38
|
strictFonts?: boolean;
|
|
37
39
|
onProgress?: (frame: number, total: number) => void;
|
package/dist/render.js
CHANGED
|
@@ -126,7 +126,7 @@ async function render(opts) {
|
|
|
126
126
|
process.stderr.write(`captions: ${srt}, ${vtt}\n`);
|
|
127
127
|
};
|
|
128
128
|
const emitCues = (target) => {
|
|
129
|
-
const written = writeCueSidecars(target, compiled.markers, duration, opts.chapters === "vtt");
|
|
129
|
+
const written = writeCueSidecars(target, compiled.markers, duration, opts.chapters === "vtt", opts.chapterKinds);
|
|
130
130
|
if (written.length) process.stderr.write(`cues: ${written.join(", ")}\n`);
|
|
131
131
|
};
|
|
132
132
|
if (!isVideo) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glissade/cli",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "glissade CLI: headless rendering via backend-skia (+ FFmpeg mux).",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -20,15 +20,15 @@
|
|
|
20
20
|
"@napi-rs/canvas": "^0.1.65",
|
|
21
21
|
"esbuild": "^0.28.0",
|
|
22
22
|
"jiti": "^2.4.2",
|
|
23
|
-
"@glissade/backend-skia": "0.9.
|
|
24
|
-
"@glissade/core": "0.9.
|
|
25
|
-
"@glissade/interact": "0.9.
|
|
26
|
-
"@glissade/lottie": "0.9.
|
|
27
|
-
"@glissade/svg": "0.9.
|
|
28
|
-
"@glissade/narrate": "0.9.
|
|
29
|
-
"@glissade/player": "0.9.
|
|
30
|
-
"@glissade/scene": "0.9.
|
|
31
|
-
"@glissade/sfx": "0.9.
|
|
23
|
+
"@glissade/backend-skia": "0.9.1",
|
|
24
|
+
"@glissade/core": "0.9.1",
|
|
25
|
+
"@glissade/interact": "0.9.1",
|
|
26
|
+
"@glissade/lottie": "0.9.1",
|
|
27
|
+
"@glissade/svg": "0.9.1",
|
|
28
|
+
"@glissade/narrate": "0.9.1",
|
|
29
|
+
"@glissade/player": "0.9.1",
|
|
30
|
+
"@glissade/scene": "0.9.1",
|
|
31
|
+
"@glissade/sfx": "0.9.1"
|
|
32
32
|
},
|
|
33
33
|
"repository": {
|
|
34
34
|
"type": "git",
|