Skip to content

Commit e89a71c

Browse files
committed
fix(cli): handle STYLE and REGION blocks unsupported by node-webvtt
1 parent d18be81 commit e89a71c

File tree

2 files changed

+127
-2
lines changed

2 files changed

+127
-2
lines changed

packages/cli/src/cli/loaders/index.spec.ts

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2645,6 +2645,98 @@ Another cue
26452645
expect(Object.values(data)).toContain("Another cue");
26462646
expect(Object.values(data)).not.toContain("Hello world!");
26472647
});
2648+
2649+
it("should handle vtt files with STYLE blocks", async () => {
2650+
setupFileMocks();
2651+
2652+
const input = `
2653+
WEBVTT
2654+
2655+
STYLE
2656+
::cue(.heading) {
2657+
font-size: 150%;
2658+
font-weight: bold;
2659+
}
2660+
2661+
00:00:00.000 --> 00:00:01.000
2662+
Hello world!
2663+
2664+
00:00:30.000 --> 00:00:31.000
2665+
Another cue
2666+
`.trim();
2667+
2668+
const expectedOutput = {
2669+
"0#0-1#": "Hello world!",
2670+
"1#30-31#": "Another cue",
2671+
};
2672+
2673+
mockFileOperations(input);
2674+
2675+
const vttLoader = createBucketLoader("vtt", "i18n/[locale].vtt", {
2676+
defaultLocale: "en",
2677+
});
2678+
vttLoader.setDefaultLocale("en");
2679+
const data = await vttLoader.pull("en");
2680+
2681+
expect(data).toEqual(expectedOutput);
2682+
});
2683+
2684+
it("should preserve STYLE blocks in push output", async () => {
2685+
setupFileMocks();
2686+
2687+
const input = `
2688+
WEBVTT
2689+
2690+
STYLE
2691+
::cue(.heading) {
2692+
font-size: 150%;
2693+
font-weight: bold;
2694+
}
2695+
2696+
00:00:00.000 --> 00:00:01.000
2697+
Hello world!
2698+
`.trim();
2699+
2700+
mockFileOperations(input);
2701+
2702+
const vttLoader = createBucketLoader("vtt", "i18n/[locale].vtt", {
2703+
defaultLocale: "en",
2704+
});
2705+
vttLoader.setDefaultLocale("en");
2706+
await vttLoader.pull("en");
2707+
await vttLoader.push("es", { "0#0-1#": "¡Hola mundo!" });
2708+
2709+
const written = (fs.writeFile as any).mock.calls[0][1] as string;
2710+
expect(written).toContain("STYLE");
2711+
expect(written).toContain("::cue(.heading)");
2712+
expect(written).toContain("¡Hola mundo!");
2713+
});
2714+
2715+
it("should handle vtt files with REGION blocks", async () => {
2716+
setupFileMocks();
2717+
2718+
const input = `
2719+
WEBVTT
2720+
2721+
REGION
2722+
id:sidebar
2723+
width:30%
2724+
lines:3
2725+
2726+
00:00:00.000 --> 00:00:01.000
2727+
Hello world!
2728+
`.trim();
2729+
2730+
mockFileOperations(input);
2731+
2732+
const vttLoader = createBucketLoader("vtt", "i18n/[locale].vtt", {
2733+
defaultLocale: "en",
2734+
});
2735+
vttLoader.setDefaultLocale("en");
2736+
const data = await vttLoader.pull("en");
2737+
2738+
expect(data).toEqual({ "0#0-1#": "Hello world!" });
2739+
});
26482740
});
26492741

26502742
describe("XML bucket loader", () => {

packages/cli/src/cli/loaders/vtt.ts

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,44 @@ import webvtt from "node-webvtt";
22
import { ILoader } from "./_types";
33
import { createLoader } from "./_utils";
44

5+
// node-webvtt doesn't handle STYLE/REGION blocks — strip them before parsing
6+
function extractUnsupportedBlocks(input: string): {
7+
cleaned: string;
8+
blocks: string[];
9+
} {
10+
const parts = input.replace(/\r\n/g, "\n").split("\n\n");
11+
const blocks: string[] = [];
12+
const kept: string[] = [];
13+
14+
const unsupportedRegex = /^(?:STYLE|REGION)/;
15+
16+
for (const part of parts) {
17+
const firstLine = part.trimStart().split("\n", 1)[0];
18+
19+
if (unsupportedRegex.test(firstLine)) {
20+
blocks.push(part);
21+
} else {
22+
kept.push(part);
23+
}
24+
}
25+
26+
return { cleaned: kept.join("\n\n"), blocks };
27+
}
28+
529
export default function createVttLoader(): ILoader<
630
string,
731
Record<string, any>
832
> {
33+
let savedBlocks: string[] = [];
34+
935
return createLoader({
1036
async pull(locale, input) {
1137
if (!input) {
1238
return ""; // if VTT file does not exist yet we can not parse it - return empty string
1339
}
14-
const vtt = webvtt.parse(input)?.cues;
40+
const { cleaned, blocks } = extractUnsupportedBlocks(input);
41+
savedBlocks = blocks;
42+
const vtt = webvtt.parse(cleaned)?.cues;
1543
if (Object.keys(vtt).length === 0) {
1644
return {};
1745
} else {
@@ -42,7 +70,12 @@ export default function createVttLoader(): ILoader<
4270
cues: output,
4371
};
4472

45-
return webvtt.compile(input);
73+
const compiled = webvtt.compile(input);
74+
if (savedBlocks.length === 0) return compiled;
75+
76+
// Re-insert STYLE/REGION blocks after the WEBVTT header
77+
const [header, ...rest] = compiled.split("\n\n");
78+
return [header, ...savedBlocks, ...rest].join("\n\n");
4679
},
4780
});
4881
}

0 commit comments

Comments
 (0)