mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-09-01 12:15:27 -07:00
fix(mining): copy multi-line subtitles backward from current line
- Select lines in subtitle timeline order after seeking
This commit is contained in:
@@ -244,6 +244,35 @@ test('handleMultiCopyDigit copies available history and reports truncation', ()
|
||||
assert.equal(osd.at(-1), 'Only 2 lines available, copied 2');
|
||||
});
|
||||
|
||||
test('handleMultiCopyDigit copies backward from the current subtitle after a backward seek', () => {
|
||||
const copied: string[] = [];
|
||||
const tracker = new SubtitleTimingTracker();
|
||||
|
||||
try {
|
||||
tracker.recordSubtitle('A', 1, 2);
|
||||
tracker.recordSubtitle('B', 3, 4);
|
||||
tracker.recordSubtitle('C', 5, 6);
|
||||
tracker.recordSubtitle('B', 3, 4);
|
||||
|
||||
const deps = {
|
||||
subtitleTimingTracker: tracker,
|
||||
writeClipboardText: (text: string) => copied.push(text),
|
||||
showMpvOsd: () => {},
|
||||
};
|
||||
|
||||
handleMultiCopyDigit(1, deps);
|
||||
handleMultiCopyDigit(2, deps);
|
||||
|
||||
assert.deepEqual(copied, ['B', 'A\n\nB']);
|
||||
assert.deepEqual(tracker.getRecentEntries(2), [
|
||||
{ displayText: 'A', startTime: 1, endTime: 2, secondaryText: undefined },
|
||||
{ displayText: 'B', startTime: 3, endTime: 4, secondaryText: undefined },
|
||||
]);
|
||||
} finally {
|
||||
tracker.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
test('handleMineSentenceDigit reports async create failures', async () => {
|
||||
const osd: string[] = [];
|
||||
const logs: Array<{ message: string; err: unknown }> = [];
|
||||
|
||||
@@ -107,28 +107,20 @@ export class SubtitleTimingTracker {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get recent subtitle blocks in chronological order.
|
||||
* Returns the last `count` subtitle events (oldest → newest).
|
||||
* Get recent subtitle blocks in timeline order.
|
||||
* Returns up to `count` known subtitle events ending at the current event.
|
||||
* Blocks preserve internal line breaks and are joined with blank lines.
|
||||
*/
|
||||
getRecentBlocks(count: number): string[] {
|
||||
if (count <= 0) return [];
|
||||
if (count > this.history.length) {
|
||||
count = this.history.length;
|
||||
}
|
||||
return this.history.slice(-count).map((entry) => entry.displayText);
|
||||
return this.getRecentTimelineEntries(count).map((entry) => entry.displayText);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get recent subtitle blocks with their original event timings.
|
||||
* Returns the last `count` subtitle events (oldest → newest).
|
||||
* Get recent subtitle blocks with their original event timings in timeline order.
|
||||
* Returns up to `count` known subtitle events ending at the current event.
|
||||
*/
|
||||
getRecentEntries(count: number): SubtitleTimingBlock[] {
|
||||
if (count <= 0) return [];
|
||||
if (count > this.history.length) {
|
||||
count = this.history.length;
|
||||
}
|
||||
return this.history.slice(-count).map((entry) => ({
|
||||
return this.getRecentTimelineEntries(count).map((entry) => ({
|
||||
displayText: entry.displayText,
|
||||
startTime: entry.startTime,
|
||||
endTime: entry.endTime,
|
||||
@@ -144,6 +136,43 @@ export class SubtitleTimingTracker {
|
||||
return lastEntry ? lastEntry.displayText : null;
|
||||
}
|
||||
|
||||
private getRecentTimelineEntries(count: number): HistoryEntry[] {
|
||||
if (count <= 0) return [];
|
||||
|
||||
const currentEntry = this.history[this.history.length - 1];
|
||||
if (!currentEntry) return [];
|
||||
|
||||
const timelineEntries: HistoryEntry[] = [];
|
||||
for (const entry of this.history) {
|
||||
const existingIndex = timelineEntries.findIndex((candidate) =>
|
||||
this.isSameSubtitleEvent(candidate, entry),
|
||||
);
|
||||
if (existingIndex === -1) {
|
||||
timelineEntries.push(entry);
|
||||
} else {
|
||||
timelineEntries[existingIndex] = entry;
|
||||
}
|
||||
}
|
||||
|
||||
timelineEntries.sort(
|
||||
(left, right) => left.startTime - right.startTime || left.endTime - right.endTime,
|
||||
);
|
||||
const currentIndex = timelineEntries.findIndex((entry) =>
|
||||
this.isSameSubtitleEvent(entry, currentEntry),
|
||||
);
|
||||
if (currentIndex === -1) return [];
|
||||
|
||||
return timelineEntries.slice(Math.max(0, currentIndex - count + 1), currentIndex + 1);
|
||||
}
|
||||
|
||||
private isSameSubtitleEvent(left: HistoryEntry, right: HistoryEntry): boolean {
|
||||
return (
|
||||
left.timingKey === right.timingKey &&
|
||||
left.startTime === right.startTime &&
|
||||
left.endTime === right.endTime
|
||||
);
|
||||
}
|
||||
|
||||
private findFuzzyMatch(text: string): { startTime: number; endTime: number } | null {
|
||||
let bestMatch: TimingEntry | null = null;
|
||||
let bestScore = 0;
|
||||
|
||||
Reference in New Issue
Block a user