mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-08-28 12:15:27 -07:00
fix(youtube): preserve explicit 3000ms caption durations
- Distinguish generated rolling cues from plain-text cues - Keep explicit sound-cue spans intact
This commit is contained in:
@@ -125,11 +125,11 @@ test('convertYoutubeTimedTextToVtt leaves pop-on captions intact', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('convertYoutubeTimedTextToVtt keeps explicit sound-cue durations in rolling documents', () => {
|
||||
test('convertYoutubeTimedTextToVtt keeps explicit 3000ms sound-cue durations in rolling documents', () => {
|
||||
const result = convertYoutubeTimedTextToVtt(
|
||||
[
|
||||
'<timedtext><body>',
|
||||
'<p t="20305" d="2020" w="1">[音楽]</p>',
|
||||
'<p t="20305" d="3000" w="1">[音楽]</p>',
|
||||
'<p t="26269" w="1" a="1">\n</p>',
|
||||
'<p t="26279" d="3000" w="1"><s ac="0">じゃあ、君からお願いします。</s></p>',
|
||||
'</body></timedtext>',
|
||||
@@ -141,7 +141,7 @@ test('convertYoutubeTimedTextToVtt keeps explicit sound-cue durations in rolling
|
||||
[
|
||||
'WEBVTT',
|
||||
'',
|
||||
'00:00:20.305 --> 00:00:22.325',
|
||||
'00:00:20.305 --> 00:00:23.305',
|
||||
'[音楽]',
|
||||
'',
|
||||
'00:00:26.279 --> 00:00:29.279',
|
||||
|
||||
@@ -2,6 +2,7 @@ interface YoutubeTimedTextRow {
|
||||
startMs: number;
|
||||
durationMs: number;
|
||||
text: string;
|
||||
isGenerated: boolean;
|
||||
rollingWindow: YoutubeRollingWindow | null;
|
||||
}
|
||||
|
||||
@@ -152,7 +153,8 @@ function extractYoutubeTimedTextDocument(xml: string): YoutubeTimedTextDocument
|
||||
continue;
|
||||
}
|
||||
|
||||
const inner = (match[2] ?? '').replace(/<br\s*\/?>/gi, '\n').replace(/<[^>]+>/g, '');
|
||||
const rawInner = match[2] ?? '';
|
||||
const inner = rawInner.replace(/<br\s*\/?>/gi, '\n').replace(/<[^>]+>/g, '');
|
||||
const text = decodeHtmlEntities(inner).trim();
|
||||
if (!text) {
|
||||
continue;
|
||||
@@ -162,6 +164,7 @@ function extractYoutubeTimedTextDocument(xml: string): YoutubeTimedTextDocument
|
||||
startMs,
|
||||
durationMs,
|
||||
text,
|
||||
isGenerated: /<s\b/.test(rawInner),
|
||||
rollingWindow: resolveRollingWindow(attrs, windowDefinitions),
|
||||
});
|
||||
}
|
||||
@@ -179,6 +182,10 @@ function findNextEventStartMs(eventStartsMs: number[], afterMs: number): number
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function isGeneratedRollingCue(row: YoutubeTimedTextRow, hasRollingWindowEvents: boolean): boolean {
|
||||
return row.isGenerated && (row.rollingWindow !== null || hasRollingWindowEvents);
|
||||
}
|
||||
|
||||
function formatVttTimestamp(ms: number): string {
|
||||
const totalMs = Math.max(0, Math.floor(ms));
|
||||
const hours = Math.floor(totalMs / 3_600_000);
|
||||
@@ -280,10 +287,11 @@ export function convertYoutubeTimedTextToVtt(xml: string): string {
|
||||
const row = rows[index]!;
|
||||
const nextRow = rows[index + 1];
|
||||
const unclampedEnd = row.startMs + row.durationMs;
|
||||
// YouTube uses exactly 3000ms as a placeholder for rolling speech rows.
|
||||
// Other durations are explicit, including short sound cues such as [音楽].
|
||||
// YouTube uses exactly 3000ms as a placeholder for generated rolling speech.
|
||||
// Plain-text cues can explicitly use the same duration and must keep it.
|
||||
const nextEventStart =
|
||||
hasRollingWindowEvents && row.durationMs === YOUTUBE_ROLLING_PLACEHOLDER_DURATION_MS
|
||||
isGeneratedRollingCue(row, hasRollingWindowEvents) &&
|
||||
row.durationMs === YOUTUBE_ROLLING_PLACEHOLDER_DURATION_MS
|
||||
? findNextEventStartMs(eventStartsMs, row.startMs)
|
||||
: undefined;
|
||||
const clampedEnd =
|
||||
|
||||
Reference in New Issue
Block a user