fix(subtitles): recover positioned ASS word spacing and drop control deb

- Recover Latin/romaji word spaces encoded only by positioned `\pos`/`\move` fragment gaps in ASS karaoke
- Drop malformed ASS rotation-reset spacer lines and skip zero-duration metadata events
- Dedupe duplicate lines across multiline primary/secondary cues, including full-width variants
- Treat dense fragment-grid sign layouts as visual typesetting, not publishable text, during primary subtitle resolution
This commit is contained in:
2026-08-23 16:34:36 -07:00
parent ed7d3f4c3d
commit c87dcd6239
12 changed files with 576 additions and 40 deletions
+12 -9
View File
@@ -1,5 +1,6 @@
import type { SubtitleCue } from '../../types/subtitle';
import { flattenedSecondarySubtitleLineIdentity } from '../../core/services/secondary-subtitle-line-identity';
import { removeAssControlDebrisLines } from '../../core/services/ass-text';
type SecondarySubtitleMpvClient = {
connected?: boolean;
@@ -153,15 +154,17 @@ export function findActiveSubtitleText(cues: readonly SubtitleCue[], timeSeconds
activeCues.sort(compareAuthoredSubtitleOrder);
for (const { cue } of activeCues) {
const text = cue.text.trim();
const compactText = text.replace(/\s+/gu, '');
if (!compactText || seenExact.has(compactText)) continue;
seenExact.add(compactText);
for (const line of cue.text.split('\n')) {
const text = line.trim();
const compactText = text.normalize('NFKC').replace(/\s+/gu, '');
if (!compactText || seenExact.has(compactText)) continue;
seenExact.add(compactText);
const flattenedIdentity = flattenedSecondarySubtitleLineIdentity(text);
if (flattenedIdentity && seenFlattened.has(flattenedIdentity)) continue;
if (flattenedIdentity) seenFlattened.add(flattenedIdentity);
activeText.push(text);
const flattenedIdentity = flattenedSecondarySubtitleLineIdentity(text);
if (flattenedIdentity && seenFlattened.has(flattenedIdentity)) continue;
if (flattenedIdentity) seenFlattened.add(flattenedIdentity);
activeText.push(text);
}
}
return activeText.join('\n');
}
@@ -305,7 +308,7 @@ export function createSecondarySubtitleTrackController(deps: {
refresh,
scheduleRefresh,
handleLiveText(text: string): void {
lastLiveText = text;
lastLiveText = removeAssControlDebrisLines(text);
publish(resolveAtTime(deps.getCurrentTimePos()));
},
handleTimePos(timeSeconds: number): void {