fix(anki): snapshot mining media clip timing (#197)

This commit is contained in:
2026-08-14 21:50:32 -07:00
committed by GitHub
parent 8ddc151435
commit c5a77ac067
9 changed files with 157 additions and 6 deletions
+1
View File
@@ -16,6 +16,7 @@ export {
export { createOverlayShortcutRuntimeHandlers } from './overlay-shortcut-handler';
export { createCliCommandDepsRuntime, handleCliCommand } from './cli-command';
export {
captureLiveSubtitleMiningContext,
copyCurrentSubtitle,
handleMineSentenceDigit,
handleMultiCopyDigit,
+44
View File
@@ -1,6 +1,7 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import {
captureLiveSubtitleMiningContext,
copyCurrentSubtitle,
handleMineSentenceDigit,
handleMultiCopyDigit,
@@ -345,3 +346,46 @@ test('handleMineSentenceDigit joins per-entry secondary subtitles when available
tracker.destroy();
}
});
test('captureLiveSubtitleMiningContext snapshots the current line and timings', () => {
const context = captureLiveSubtitleMiningContext(
{ currentSubText: ' 食べる ', currentSubStart: 12.5, currentSubEnd: 15.25 },
() => 1234,
);
assert.deepEqual(context, {
source: 'overlay',
text: '食べる',
startTime: 12.5,
endTime: 15.25,
capturedAtMs: 1234,
});
});
test('captureLiveSubtitleMiningContext rejects missing client, empty text and bad timings', () => {
assert.equal(captureLiveSubtitleMiningContext(null), null);
assert.equal(
captureLiveSubtitleMiningContext({
currentSubText: ' ',
currentSubStart: 1,
currentSubEnd: 2,
}),
null,
);
assert.equal(
captureLiveSubtitleMiningContext({
currentSubText: 'line',
currentSubStart: 5,
currentSubEnd: 5,
}),
null,
);
assert.equal(
captureLiveSubtitleMiningContext({
currentSubText: 'line',
currentSubStart: NaN,
currentSubEnd: 2,
}),
null,
);
});
+22
View File
@@ -1,4 +1,5 @@
import type { SubtitleTimingBlock } from '../../subtitle-timing-tracker';
import type { SubtitleMiningContext } from '../../types/subtitle';
interface SubtitleTimingTrackerLike {
getRecentBlocks: (count: number) => string[];
@@ -54,6 +55,27 @@ export function handleMultiCopyDigit(
}
}
/**
* Snapshot the live mpv subtitle line and its timings as a mining context. Media
* generation can run tens of seconds after the user mines (slow audio extraction),
* so anything that reads `currentSubStart`/`currentSubEnd` lazily clips whichever
* line is on screen by then; callers capture here at mining time instead.
*/
export function captureLiveSubtitleMiningContext(
client: Pick<MpvClientLike, 'currentSubText' | 'currentSubStart' | 'currentSubEnd'> | null,
now: () => number = Date.now,
): SubtitleMiningContext | null {
if (!client) {
return null;
}
const text = client.currentSubText?.trim();
const { currentSubStart: startTime, currentSubEnd: endTime } = client;
if (!text || !Number.isFinite(startTime) || !Number.isFinite(endTime) || endTime <= startTime) {
return null;
}
return { source: 'overlay', text, startTime, endTime, capturedAtMs: now() };
}
export function copyCurrentSubtitle(deps: {
subtitleTimingTracker: SubtitleTimingTrackerLike | null;
writeClipboardText: (text: string) => void;