mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-08-23 00:15:26 -07:00
fix(subtitles): preserve authored spaces in fragmented ASS karaoke
- Only trim indentation before slicing ASS event fields so a trailing authored space at a fragment's end is kept, preventing words from being joined together when fragmented karaoke lines are reconstructed - Add regression test covering event-boundary word spacing
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
type: fixed
|
||||
area: overlay
|
||||
|
||||
- Secondary subtitles now parse the selected ASS/SRT/VTT source with the primary subtitle deduplication pipeline, preventing layered animation text from appearing several times in the overlay, mined cards, and statistics. Long ASS lines repeated as dialogue and positioned signs are also collapsed when they differ only in whitespace or terminal punctuation. Dense multi-row sign layouts no longer become one concatenated secondary line. Live mpv text remains the fallback for unreadable tracks and applies full-line duplicate filtering before display.
|
||||
- Secondary subtitles now parse the selected ASS/SRT/VTT source with the primary subtitle deduplication pipeline, preventing layered animation text from appearing several times in the overlay, mined cards, and statistics. Fragmented ASS karaoke keeps spaces authored at event boundaries instead of joining every word together. Long ASS lines repeated as dialogue and positioned signs are also collapsed when they differ only in whitespace or terminal punctuation. Dense multi-row sign layouts no longer become one concatenated secondary line. Live mpv text remains the fallback for unreadable tracks and applies full-line duplicate filtering before display.
|
||||
|
||||
@@ -853,6 +853,10 @@ function parseAnnotatedAssEvents(content: string): ParsedAssEvents {
|
||||
|
||||
for (const line of lines) {
|
||||
const trimmed = line.trim();
|
||||
// Event text can end in an authored space. Fragmented karaoke commonly uses that
|
||||
// space to retain word boundaries when its separately positioned events are joined
|
||||
// back into a line, so only remove indentation before slicing the event fields.
|
||||
const eventLine = line.trimStart();
|
||||
|
||||
if (trimmed.startsWith('[') && trimmed.endsWith(']')) {
|
||||
inEventsSection = trimmed.toLowerCase() === '[events]';
|
||||
@@ -883,9 +887,9 @@ function parseAnnotatedAssEvents(content: string): ParsedAssEvents {
|
||||
continue;
|
||||
}
|
||||
|
||||
const eventPrefix = trimmed.startsWith(ASS_DIALOGUE_PREFIX)
|
||||
const eventPrefix = eventLine.startsWith(ASS_DIALOGUE_PREFIX)
|
||||
? ASS_DIALOGUE_PREFIX
|
||||
: trimmed.startsWith(ASS_COMMENT_PREFIX)
|
||||
: eventLine.startsWith(ASS_COMMENT_PREFIX)
|
||||
? ASS_COMMENT_PREFIX
|
||||
: null;
|
||||
if (!eventPrefix) {
|
||||
@@ -896,7 +900,7 @@ function parseAnnotatedAssEvents(content: string): ParsedAssEvents {
|
||||
continue;
|
||||
}
|
||||
|
||||
const fields = trimmed.slice(eventPrefix.length).split(',');
|
||||
const fields = eventLine.slice(eventPrefix.length).split(',');
|
||||
if (
|
||||
fieldIndex.start >= fields.length ||
|
||||
fieldIndex.end >= fields.length ||
|
||||
|
||||
@@ -181,6 +181,41 @@ test('ASS fragment karaoke stays separated by style with authored word spacing',
|
||||
assert.equal(findActiveSubtitleText(parseSubtitleCues(ass, 'ending.ass'), 8.5), 'Iwanttogo');
|
||||
});
|
||||
|
||||
test('ASS fragment karaoke preserves word spaces authored at event boundaries', () => {
|
||||
const fragments = [
|
||||
'The ',
|
||||
'shoot',
|
||||
'ing ',
|
||||
'stars ',
|
||||
'arc',
|
||||
'ing ',
|
||||
'across ',
|
||||
'the ',
|
||||
'sky ',
|
||||
'I ',
|
||||
'wish ',
|
||||
'upon,',
|
||||
];
|
||||
const events: string[] = [];
|
||||
for (const layer of [0, 1]) {
|
||||
fragments.forEach((fragment, index) => {
|
||||
events.push(
|
||||
`Dialogue: ${layer},0:00:01.00,0:00:04.00,op_english,,0,0,0,,{\\pos(${100 + index * 40},110)\\t(0,200,\\fscx110)}${fragment}`,
|
||||
);
|
||||
});
|
||||
}
|
||||
const ass = [
|
||||
'[Events]',
|
||||
'Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text',
|
||||
...events,
|
||||
].join('\n');
|
||||
|
||||
assert.equal(
|
||||
findActiveSubtitleText(parseSubtitleCues(ass, 'bravern-s01e10.ass'), 2),
|
||||
'The shooting stars arcing across the sky I wish upon,',
|
||||
);
|
||||
});
|
||||
|
||||
test('findActiveSubtitleText keeps a complete reconstructed line over entrance fragments', () => {
|
||||
const current = {
|
||||
startTime: 1,
|
||||
|
||||
Reference in New Issue
Block a user