mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-08-18 12:18:29 -07:00
fix(overlay): prevent secondary subtitle duplication
- Reject decimal subtitle track IDs - Preserve repeated short dialogue lines
This commit is contained in:
@@ -181,6 +181,18 @@ test('dispatchMpvProtocolMessage emits subtitle track changes', async () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('dispatchMpvProtocolMessage rejects decimal subtitle track IDs', async () => {
|
||||||
|
const { deps, state } = createDeps();
|
||||||
|
|
||||||
|
await dispatchMpvProtocolMessage({ event: 'property-change', name: 'sid', data: '4.5' }, deps);
|
||||||
|
await dispatchMpvProtocolMessage(
|
||||||
|
{ event: 'property-change', name: 'secondary-sid', data: '4.5' },
|
||||||
|
deps,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.deepEqual(state.events, [{ sid: null }, { sid: null }]);
|
||||||
|
});
|
||||||
|
|
||||||
test('dispatchMpvProtocolMessage enforces sub-visibility hidden when overlay suppression is enabled', async () => {
|
test('dispatchMpvProtocolMessage enforces sub-visibility hidden when overlay suppression is enabled', async () => {
|
||||||
const { deps, state } = createDeps({
|
const { deps, state } = createDeps({
|
||||||
isVisibleOverlayVisible: () => true,
|
isVisibleOverlayVisible: () => true,
|
||||||
|
|||||||
@@ -283,7 +283,7 @@ export async function dispatchMpvProtocolMessage(
|
|||||||
: typeof msg.data === 'string'
|
: typeof msg.data === 'string'
|
||||||
? Number(msg.data)
|
? Number(msg.data)
|
||||||
: null;
|
: null;
|
||||||
deps.emitSubtitleTrackChange({ sid: sid !== null && Number.isFinite(sid) ? sid : null });
|
deps.emitSubtitleTrackChange({ sid: sid !== null && Number.isInteger(sid) ? sid : null });
|
||||||
} else if (msg.name === 'secondary-sid') {
|
} else if (msg.name === 'secondary-sid') {
|
||||||
const sid =
|
const sid =
|
||||||
typeof msg.data === 'number'
|
typeof msg.data === 'number'
|
||||||
@@ -292,7 +292,7 @@ export async function dispatchMpvProtocolMessage(
|
|||||||
? Number(msg.data)
|
? Number(msg.data)
|
||||||
: null;
|
: null;
|
||||||
deps.emitSecondarySubtitleTrackChange({
|
deps.emitSecondarySubtitleTrackChange({
|
||||||
sid: sid !== null && Number.isFinite(sid) ? sid : null,
|
sid: sid !== null && Number.isInteger(sid) ? sid : null,
|
||||||
});
|
});
|
||||||
} else if (msg.name === 'secondary-sub-delay') {
|
} else if (msg.name === 'secondary-sub-delay') {
|
||||||
const delay =
|
const delay =
|
||||||
|
|||||||
@@ -1434,10 +1434,10 @@ test('prepareSecondarySubtitleLines collapses karaoke syllable spam into one ded
|
|||||||
assert.deepEqual(prepareSecondarySubtitleLines(karaoke), ['ya This no ma ups']);
|
assert.deepEqual(prepareSecondarySubtitleLines(karaoke), ['ya This no ma ups']);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('prepareSecondarySubtitleLines collapses a short stack of repeated ASS layers', () => {
|
test('prepareSecondarySubtitleLines preserves repeated short dialogue without layer metadata', () => {
|
||||||
const layeredSign = ['Your', 'Your', 'Your', 'Your', 'mosaic'].join('\\N');
|
const dialogue = ['Wait', 'Wait', 'Wait'];
|
||||||
|
|
||||||
assert.deepEqual(prepareSecondarySubtitleLines(layeredSign), ['Your mosaic']);
|
assert.deepEqual(prepareSecondarySubtitleLines(dialogue.join('\\N')), dialogue);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('prepareSecondarySubtitleLines preserves short simultaneous dialogue without repeats', () => {
|
test('prepareSecondarySubtitleLines preserves short simultaneous dialogue without repeats', () => {
|
||||||
|
|||||||
@@ -657,24 +657,12 @@ function renderPlainTextPreserveLineBreaks(root: ParentNode, text: string): void
|
|||||||
// hover-pause band into a full-screen trap.
|
// hover-pause band into a full-screen trap.
|
||||||
const KARAOKE_MIN_LINE_COUNT = 8;
|
const KARAOKE_MIN_LINE_COUNT = 8;
|
||||||
const KARAOKE_MAX_MEDIAN_LINE_LENGTH = 4;
|
const KARAOKE_MAX_MEDIAN_LINE_LENGTH = 4;
|
||||||
const LAYERED_REPEAT_MIN_LINE_COUNT = 4;
|
|
||||||
const LAYERED_REPEAT_MIN_COUNT = 3;
|
|
||||||
|
|
||||||
function isKaraokeLikeLineSet(lines: string[]): boolean {
|
function isKaraokeLikeLineSet(lines: string[]): boolean {
|
||||||
|
if (lines.length < KARAOKE_MIN_LINE_COUNT) return false;
|
||||||
const lengths = lines.map((line) => line.length).sort((a, b) => a - b);
|
const lengths = lines.map((line) => line.length).sort((a, b) => a - b);
|
||||||
const median = lengths[Math.floor(lengths.length / 2)] ?? 0;
|
const median = lengths[Math.floor(lengths.length / 2)] ?? 0;
|
||||||
if (median > KARAOKE_MAX_MEDIAN_LINE_LENGTH) return false;
|
return median <= KARAOKE_MAX_MEDIAN_LINE_LENGTH;
|
||||||
|
|
||||||
if (lines.length >= KARAOKE_MIN_LINE_COUNT) return true;
|
|
||||||
if (lines.length < LAYERED_REPEAT_MIN_LINE_COUNT) return false;
|
|
||||||
|
|
||||||
const repeatCounts = new Map<string, number>();
|
|
||||||
for (const line of lines) {
|
|
||||||
const count = (repeatCounts.get(line) ?? 0) + 1;
|
|
||||||
if (count >= LAYERED_REPEAT_MIN_COUNT) return true;
|
|
||||||
repeatCounts.set(line, count);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function prepareSecondarySubtitleLines(text: string): string[] {
|
export function prepareSecondarySubtitleLines(text: string): string[] {
|
||||||
|
|||||||
Reference in New Issue
Block a user