mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-09-08 17:16:18 -07:00
fix(subtitles): preserve lyric selection and deduplicate secondary text
- Seek sidebar selections past overlapping karaoke exit spans - Collapse duplicate long ASS lines and omit reconstructed sign grids
This commit is contained in:
@@ -240,14 +240,15 @@ test('subtitle sidebar modal opens from snapshot and clicking cue seeks playback
|
||||
|
||||
const snapshot: SubtitleSidebarSnapshot = {
|
||||
cues: [
|
||||
{ startTime: 1, endTime: 2, text: 'first' },
|
||||
{ startTime: 1, endTime: 3.4, text: 'first' },
|
||||
{ startTime: 3, endTime: 4, text: 'second' },
|
||||
],
|
||||
currentSubtitle: {
|
||||
text: 'second',
|
||||
startTime: 3,
|
||||
startTime: 3.5,
|
||||
endTime: 4,
|
||||
},
|
||||
currentTimeSec: 3.5,
|
||||
config: {
|
||||
enabled: true,
|
||||
autoOpen: false,
|
||||
@@ -361,6 +362,9 @@ test('subtitle sidebar modal opens from snapshot and clicking cue seeks playback
|
||||
modal.seekToCue(snapshot.cues[0]!);
|
||||
assert.deepEqual(mpvCommands.at(-1), ['seek', 1.08, 'absolute+exact']);
|
||||
|
||||
modal.seekToCue(snapshot.cues[1]!);
|
||||
assert.deepEqual(mpvCommands.at(-1), ['seek', 3.48, 'absolute+exact']);
|
||||
|
||||
modal.closeSubtitleSidebarModal();
|
||||
assert.deepEqual(visibilityChanges, [true, false]);
|
||||
assert.deepEqual(modalNotifications, ['open:subtitle-sidebar', 'close:subtitle-sidebar']);
|
||||
|
||||
@@ -4,7 +4,7 @@ import type {
|
||||
SubtitleMiningContext,
|
||||
SubtitleSidebarSnapshot,
|
||||
} from '../../types';
|
||||
import { subtitleCueSeekTime } from '../../core/services/subtitle-cue-navigation.js';
|
||||
import { subtitleCueListSeekTime } from '../../core/services/subtitle-cue-navigation.js';
|
||||
import type { ModalStateReader, RendererContext } from '../context';
|
||||
import { syncOverlayMouseIgnoreState } from '../overlay-mouse-ignore.js';
|
||||
import {
|
||||
@@ -392,7 +392,11 @@ export function createSubtitleSidebarModal(
|
||||
}
|
||||
|
||||
function seekToCue(cue: SubtitleCue): void {
|
||||
window.electronAPI.sendMpvCommand(['seek', subtitleCueSeekTime(cue), 'absolute+exact']);
|
||||
window.electronAPI.sendMpvCommand([
|
||||
'seek',
|
||||
subtitleCueListSeekTime(ctx.state.subtitleSidebarCues, cue),
|
||||
'absolute+exact',
|
||||
]);
|
||||
}
|
||||
|
||||
function getCueRowLabel(cue: SubtitleCue): string {
|
||||
|
||||
@@ -1454,6 +1454,15 @@ test('prepareSecondarySubtitleLines preserves repeated short dialogue without la
|
||||
assert.deepEqual(prepareSecondarySubtitleLines(dialogue.join('\\N')), dialogue);
|
||||
});
|
||||
|
||||
test('prepareSecondarySubtitleLines collapses punctuation variants of a full-sentence fallback', () => {
|
||||
const dialogue = 'A question veiled as an insult!';
|
||||
const positionedSign = 'A question veiled as an insult';
|
||||
|
||||
assert.deepEqual(prepareSecondarySubtitleLines([dialogue, positionedSign].join('\\N')), [
|
||||
dialogue,
|
||||
]);
|
||||
});
|
||||
|
||||
test('prepareSecondarySubtitleLines preserves short simultaneous dialogue without repeats', () => {
|
||||
const dialogue = ['Wait', 'Go!', 'No!', 'Run!'];
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import type {
|
||||
SubtitleRendererStyleConfig,
|
||||
} from '../types';
|
||||
import { assToPlainText, normalizePlainSubtitleText } from '../core/services/ass-text.js';
|
||||
import { flattenedSecondarySubtitleLineIdentity } from '../core/services/secondary-subtitle-line-identity.js';
|
||||
import type { RendererContext } from './context';
|
||||
import { PRIMARY_SUB_VISIBLE_ON_YOMITAN_POPUP_CLASS } from './yomitan-popup.js';
|
||||
|
||||
@@ -665,6 +666,17 @@ function isKaraokeLikeLineSet(lines: string[]): boolean {
|
||||
return median <= KARAOKE_MAX_MEDIAN_LINE_LENGTH;
|
||||
}
|
||||
|
||||
function collapseFullLineFallbackCopies(lines: string[]): string[] {
|
||||
const seen = new Set<string>();
|
||||
return lines.filter((line) => {
|
||||
const identity = flattenedSecondarySubtitleLineIdentity(line);
|
||||
if (!identity) return true;
|
||||
if (seen.has(identity)) return false;
|
||||
seen.add(identity);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
export function prepareSecondarySubtitleLines(text: string): string[] {
|
||||
// The one display-side ASS decode: secondary text also reaches the overlay from
|
||||
// websocket clients that forward their source line untouched, so unlike the primary
|
||||
@@ -678,7 +690,7 @@ export function prepareSecondarySubtitleLines(text: string): string[] {
|
||||
.map((line) => line.trim())
|
||||
.filter((line) => line.length > 0);
|
||||
if (!isKaraokeLikeLineSet(lines)) {
|
||||
return lines;
|
||||
return collapseFullLineFallbackCopies(lines);
|
||||
}
|
||||
|
||||
const seen = new Set<string>();
|
||||
|
||||
Reference in New Issue
Block a user