Compare commits

..
21 Commits
Author SHA1 Message Date
sudacode a610c90c25 fix(anki): report stale timing reviews after preview and waveform awaits
Playback and ffmpeg waveform analysis both span long enough for the
review to end underneath them (decision watchdog, overlay teardown).
Both paths reported success or a generic failure afterwards, so the
modal stayed open on a review main had already dropped.
2026-09-03 23:12:24 -07:00
sudacode 144db675e9 fix(anki): close the timing review modal once main has dropped the review
Main ends a review on its own after the decision watchdog, on overlay
teardown, or when the modal window opened twice, but the renderer only
closed on a successful resolve. Every button then answered 'no longer
active' forever and the overlay stayed interactive over mpv. Stale
results now carry a typed flag and the modal closes on it.
2026-09-03 22:51:22 -07:00
sudacode 8e47e0d761 fix(anki): trim timing review clips to dialogue end
- Auto-adjust untouched clip ends after waveform analysis
- Document and test trailing-silence trimming behavior
2026-09-02 23:43:34 -07:00
sudacode dcd05ee4ea Merge remote-tracking branch 'origin/main' into t3code/media-timing-review 2026-09-02 22:54:30 -07:00
sudacode 56a2a25312 fix(anki): wait for media timing previews to fully finish
- Wait for mpv to drain audio before ending previews
- Notify the review modal when playback actually completes
2026-09-02 18:44:37 -07:00
sudacode c85db5e07e fix(mining): ignore stale captions and improve waveform contrast
- Use active parsed cue text and timings for overlay mining
- Normalize waveform levels against the clip's noise floor
2026-09-02 18:22:03 -07:00
sudacode f90ee78204 feat(mining): cache remote media windows during card creation
- Reuse one temporary download for timing review, audio, and screenshots
- Expire cached windows after inactivity and clean them up on exit
2026-09-02 01:21:26 -07:00
sudacode 760b3e1d3d test(immersion): wrap vocabulary fixture inserts in a transaction 2026-09-02 00:01:01 -07:00
sudacode 4d5ea8801f feat(anki): add Senren scene-switching field grouping
- Support auto, manual, and disabled Senren duplicate-card merges
- Group sentence, furigana, audio, picture, and miscInfo fields
2026-09-01 23:22:54 -07:00
sudacode b58f391b42 fix: handle notification changes and timing review text safely
- Dismiss stale overlay progress when notification mode changes
- Validate and forward combined timing review text
- Keep macOS timing preview socket paths within system limits
2026-08-30 02:49:37 -07:00
sudacode 90a5dcd2af fix(anki): move line boundary markers with added subtitle lines 2026-08-30 02:38:28 -07:00
sudacode 989122c006 test(anki): mock note deletion in manual update tests 2026-08-30 02:03:02 -07:00
sudacode ad1c1aea1b feat(anki): add multi-line subtitle selection to media timing review 2026-08-30 01:56:16 -07:00
sudacode 62d645e577 fix: apply CodeRabbit auto-fixes 2026-08-30 01:56:16 -07:00
sudacode f36e7f6182 feat(anki): add runtime toggle for media timing review
- Add session-only runtime option with live config hot-reload support
- Document the runtime palette toggle
2026-08-30 01:56:16 -07:00
sudacode 475f81d96d fix(anki): allow timing review cards without media
- Keep existing or create new cards without generating audio or images
- Refine timing review timeline expansion labels and boundary markers
2026-08-30 01:56:16 -07:00
sudacode 046ab59da9 fix(anki): clarify media timing review subtitle boundaries
- Compact the timing review layout
- Label mined subtitle line start and end boundaries
2026-08-30 01:56:16 -07:00
sudacode e91f031faa fix(anki): keep media timing review keyboard-focused
- Route modal keydown events before later modal handlers
- Restore focus and preserve visible focus styling
2026-08-30 01:56:16 -07:00
sudacode ec6a106748 fix(anki): prevent media timing review hangs and invalid ranges
- Reject stale or invalid timing actions
- Fall back to original timing when the renderer stops responding
2026-08-30 01:56:16 -07:00
sudacode ca27a4275f feat(anki): add waveform-guided timing controls
- Add speech-weighted waveform analysis and playback playhead
- Support dragging, sliding, and keyboard nudging for clip timing
2026-08-30 01:56:16 -07:00
sudacode 256eb5da1b feat(anki): add media timing review before card creation
- Add draggable timing previews with audio playback and exact media ranges
- Support cancellation choices, including deleting existing cards
2026-08-30 01:56:16 -07:00
18 changed files with 68 additions and 180 deletions
-31
View File
@@ -1,36 +1,5 @@
# Changelog
## v0.19.6 (2026-09-04)
### Added
- **Card Timing Review**:
- Optional pre-generation timing review for word, sentence, and audio cards, with a speech-weighted waveform that flattens background noise so dialogue edges stand out clearly.
- The clip end automatically snaps back to where the line's dialogue actually ends once the waveform loads, with drag and keyboard adjustments available.
- Audio preview includes a sweeping playhead that plays the clip to its true end, even on high-latency outputs like Bluetooth headphones.
- Previous and next subtitle lines can be pulled onto the card with `P`/`N` (or the Prev/Next steppers) and removed with Shift; the sentence preview and waveform markers update automatically.
- Cancelling lets you keep a card without media, and the review can be toggled on or off for the session.
- **Senren Field Grouping**:
- Enable `ankiConnect.isSenren` to merge duplicate mined cards using Senren's scene-switching markup, grouping sentence, furigana, audio, picture, and misc-info fields.
- Supports the same auto/manual/disabled modes as Kiku, including the manual merge modal; only one of Senren or Kiku can be enabled at a time.
### Changed
- **Remote Stream Mining Performance**: Mining a card from a remote stream (Jellyfin and other HTTP sources) now downloads the clip window once and reuses it for the timing review waveform, audio preview, audio extraction, and screenshot, instead of re-fetching the stream at each step; the temporary file is cleaned up after ten minutes of inactivity or on exit.
- **TsukiHime Release Filtering**: The TsukiHime modal's Japanese and secondary-language tabs now filter the release list by the subtitle languages each release actually carries, and report when no release has subtitles for the active tab.
### Fixed
- **Subtitle & Mining Accuracy**:
- Broadcast-style captions that split one sentence across two on-screen rows (e.g. Crunchyroll Japanese subs) now merge into a single line for the sidebar and mined cards, while separate speakers, sound effects, and labeled turns still stay on their own lines.
- Mining from the overlay no longer pulls in a lingering row from the previous caption; the mined sentence and clip timing now match what's actually on screen.
- Multi-line copy and mining now select lines backward in timeline order after seeking, instead of in playback encounter order.
- Copying a subtitle, mining a sentence, or recording immersion stats no longer includes the separate furigana line that broadcast ASS captions place above a word.
- **Card Update Notifications**: Dismissed lingering overlay card-update progress when notification settings switch to OSD before an update finishes.
- **Overlay Stability on Hyprland**: Opening a modal window (timing review, Jimaku, session help, and others) while mpv is fullscreen no longer causes the overlay to flicker while the modal loads; the overlay now stays on screen untouched until the modal is ready.
- **Jellyfin Subtitle Sync**: Jellyfin subtitle files now load with zero mpv delay instead of inferring and saving an offset from Japanese and English cue timelines.
- **Secondary Subtitle Visibility**: Native mpv secondary subtitles stay hidden when switching secondary subtitle tracks during playback.
## v0.19.5 (2026-08-30)
### Fixed
+4
View File
@@ -0,0 +1,4 @@
type: fixed
area: anki
- Dismissed active overlay card-update progress when notification settings switch to OSD before an update finishes.
+4
View File
@@ -0,0 +1,4 @@
type: fixed
area: overlay
- Broadcast-style Japanese caption tracks (Crunchyroll JA subs) that split one sentence across two positioned events now publish it as a single line, so `preserveLineBreaks: false` flattens it, the sidebar lists it once, and mined sentences are whole. Rows from two different speakers, sound effects, and labeled turns still stay on separate lines.
+4
View File
@@ -0,0 +1,4 @@
type: fixed
area: jellyfin
- Jellyfin subtitle files now load with zero mpv delay instead of inferring and saving an offset from Japanese and English cue timelines.
+5
View File
@@ -0,0 +1,5 @@
type: added
area: mining
- Added optional pre-generation timing review for word, sentence, and audio cards with a compact speech-weighted waveform that flattens steady background noise so dialogue edges are easy to see, clearly labeled mined-line boundaries, a clip end that moves back to where the line's dialogue actually ends once the waveform loads (subtitles usually linger past speech), drag and keyboard adjustments, audio preview with a sweeping playhead that plays the clip to its true end even on high-latency outputs such as Bluetooth headphones, exact screenshot and AVIF timing, cancellation choices that include keeping a card without media, and a session-only runtime toggle.
- The timing review can pull any number of previous and next subtitle lines onto the card: `P`/`N` (or the Prev/Next steppers) add lines one at a time, Shift removes them, the sentence preview highlights exactly what the card will contain, and the clip range and line boundary markers on the waveform follow the added lines automatically.
+4
View File
@@ -0,0 +1,4 @@
type: fixed
area: mining
- Mining from the overlay no longer copies a lingering row of the previous broadcast caption onto the card sentence. The mined line and its clip timing now come from the parsed cue the overlay is showing, falling back to mpv's live text only when the cue list cannot explain it.
+4
View File
@@ -0,0 +1,4 @@
type: fixed
area: mining
- Multi-line copy and mining now select backward from the current subtitle in timeline order after seeking, instead of copying lines in playback encounter order. Jumping back to a short previous line also counts as a seek with external subtitle files, so that line becomes the current one.
+4
View File
@@ -0,0 +1,4 @@
type: changed
area: mining
- Mining a card from a remote stream (Jellyfin and other HTTP sources) now downloads the clip window once into a temporary file and reuses it for the timing review waveform, audio preview, audio extraction, and screenshot, instead of re-fetching the stream for every step. The temporary window is removed after ten minutes without use or on exit.
@@ -0,0 +1,4 @@
type: fixed
area: overlay
- Native mpv secondary subtitles stay hidden when switching secondary subtitle tracks during playback.
+5
View File
@@ -0,0 +1,5 @@
type: added
area: anki
- Senren note type support for duplicate-card field grouping: enable `ankiConnect.isSenren` to merge duplicate mined cards using Senren's scene-switching markup, with grouped sentence, furigana, audio, picture, and miscInfo entries.
- Senren field grouping supports the same auto/manual/disabled modes as Kiku, including the manual merge modal, and is mutually exclusive with Kiku (only one can be enabled at a time).
@@ -0,0 +1,4 @@
type: fixed
area: subtitles
- Copying the current subtitle, Anki sentence mining from recent lines, and immersion stats no longer include the separate furigana lines that broadcast-caption ASS files place above a word; recorders now use the same furigana-free text the overlay displays.
+4
View File
@@ -0,0 +1,4 @@
type: changed
area: overlay
- The TsukiHime modal's Japanese and secondary-language tabs now filter the release list by the subtitle languages each release carries, and report when no release has subtitles for the active tab.
-31
View File
@@ -1,36 +1,5 @@
# Changelog
## v0.19.6 (2026-09-04)
**Added**
- **Card Timing Review**:
- Optional pre-generation timing review for word, sentence, and audio cards, with a speech-weighted waveform that flattens background noise so dialogue edges stand out clearly.
- The clip end automatically snaps back to where the line's dialogue actually ends once the waveform loads, with drag and keyboard adjustments available.
- Audio preview includes a sweeping playhead that plays the clip to its true end, even on high-latency outputs like Bluetooth headphones.
- Previous and next subtitle lines can be pulled onto the card with `P`/`N` (or the Prev/Next steppers) and removed with Shift; the sentence preview and waveform markers update automatically.
- Cancelling lets you keep a card without media, and the review can be toggled on or off for the session.
- **Senren Field Grouping**:
- Enable `ankiConnect.isSenren` to merge duplicate mined cards using Senren's scene-switching markup, grouping sentence, furigana, audio, picture, and misc-info fields.
- Supports the same auto/manual/disabled modes as Kiku, including the manual merge modal; only one of Senren or Kiku can be enabled at a time.
**Changed**
- **Remote Stream Mining Performance**: Mining a card from a remote stream (Jellyfin and other HTTP sources) now downloads the clip window once and reuses it for the timing review waveform, audio preview, audio extraction, and screenshot, instead of re-fetching the stream at each step; the temporary file is cleaned up after ten minutes of inactivity or on exit.
- **TsukiHime Release Filtering**: The TsukiHime modal's Japanese and secondary-language tabs now filter the release list by the subtitle languages each release actually carries, and report when no release has subtitles for the active tab.
**Fixed**
- **Subtitle & Mining Accuracy**:
- Broadcast-style captions that split one sentence across two on-screen rows (e.g. Crunchyroll Japanese subs) now merge into a single line for the sidebar and mined cards, while separate speakers, sound effects, and labeled turns still stay on their own lines.
- Mining from the overlay no longer pulls in a lingering row from the previous caption; the mined sentence and clip timing now match what's actually on screen.
- Multi-line copy and mining now select lines backward in timeline order after seeking, instead of in playback encounter order.
- Copying a subtitle, mining a sentence, or recording immersion stats no longer includes the separate furigana line that broadcast ASS captions place above a word.
- **Card Update Notifications**: Dismissed lingering overlay card-update progress when notification settings switch to OSD before an update finishes.
- **Overlay Stability on Hyprland**: Opening a modal window (timing review, Jimaku, session help, and others) while mpv is fullscreen no longer causes the overlay to flicker while the modal loads; the overlay now stays on screen untouched until the modal is ready.
- **Jellyfin Subtitle Sync**: Jellyfin subtitle files now load with zero mpv delay instead of inferring and saving an offset from Japanese and English cue timelines.
- **Secondary Subtitle Visibility**: Native mpv secondary subtitles stay hidden when switching secondary subtitle tracks during playback.
## v0.19.5 (2026-08-30)
**Fixed**
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "subminer",
"productName": "SubMiner",
"desktopName": "SubMiner.desktop",
"version": "0.19.6",
"version": "0.19.5",
"description": "All-in-one sentence mining overlay with AnkiConnect and dictionary integration",
"packageManager": "bun@1.3.5",
"main": "dist/main-entry.js",
+15 -33
View File
@@ -1,41 +1,23 @@
## Highlights
### Added
- **Pre-Mining Timing Review**:
- Optional review step before creating word, sentence, or audio cards, with a speech-focused waveform that filters out steady background noise so dialogue is easy to spot.
- The clip end automatically snaps back to where dialogue actually ends, since subtitles often linger after speech stops.
- Drag or use the keyboard to adjust clip boundaries, and preview audio with a sweeping playhead that plays to the true end even on high-latency outputs like Bluetooth headphones.
- Pull extra previous or next subtitle lines onto the card with `P`/`N` (or the Prev/Next steppers); a live preview shows exactly what the card will contain.
- You can cancel and still keep the card without media, and the review can be toggled on or off for the session.
- **Senren Note Type Support**:
- Enable `ankiConnect.isSenren` to merge duplicate mined cards using Senren's scene-switching markup, combining sentence, furigana, audio, picture, and misc-info fields.
- Supports the same auto/manual/disabled grouping modes as Kiku, including the manual merge modal. Senren and Kiku are mutually exclusive, so only one can be enabled at a time.
### Changed
- **Remote Streaming Mining**: Mining a card from a remote stream (Jellyfin and other HTTP sources) now downloads the clip window once and reuses it for the timing review waveform, audio preview, audio extraction, and screenshot, instead of re-fetching the stream for every step. No action needed; the temporary download is cleaned up automatically after ten minutes of inactivity.
- **TsukiHime Release Picker**: The Japanese and secondary-language tabs now filter releases down to ones that actually carry subtitles for that language, and tell you when none do.
### Fixed
- **Broadcast Caption Accuracy**:
- Japanese caption tracks split across two positioned lines (e.g. Crunchyroll) now merge into one, so mined sentences, the sidebar, and line-break settings treat them as a single line; lines from different speakers or sound effects still stay separate.
- Mining from the overlay no longer picks up a leftover line from the previous caption, so the mined sentence and clip timing match what's actually on screen.
- Copying or mining subtitles no longer includes the separate furigana line that some broadcast subtitle files place above kanji.
- **Multi-line Copy After Seeking**: Selecting multiple subtitle lines to copy or mine now selects backward in timeline order after a seek, rather than in playback encounter order.
- **Overlay Stability**:
- On Hyprland, opening a modal (timing review, Jimaku, session help, and others) over fullscreen mpv no longer makes the overlay flicker while the modal loads.
- Switching secondary subtitle tracks no longer causes mpv's native secondary subtitles to flash on screen.
- **Anki Update Notifications**: Switching notification settings to on-screen display while a card update is still in progress now correctly dismisses the old overlay progress indicator.
- **Jellyfin Subtitles**: Subtitle files now load with zero delay in mpv instead of Jellyfin inferring and applying a sync offset.
- **Anki Card Update Progress**: The update spinner now stays visible until audio and image updates actually finish, so you won't mistake an in-progress update for a failure.
- **Word-Card Field Enrichment**: Word-card enrichment now reliably writes sentence text and audio into whichever AnkiConnect fields you've configured, while the dedicated Lapis/Kiku sentence-card and audio-card actions still use their expected field names.
- **Overlapping Subtitles**:
- Lines that start while another is still on screen now show together instead of staying hidden until you switch tracks or seek.
- Subtitles shown at the same time now stack by their authored screen position, with signs and song lyrics above dialogue.
- Half-size ASS furigana no longer shows up as if it were its own subtitle line.
- **YouTube Auto-Generated Captions**:
- Captions now follow their intended timing instead of drifting off sync.
- Long speech is paged across two rows instead of piling into a wall of text.
- Timed sound cues like `[音楽]` no longer linger over later dialogue.
## What's Changed
- feat(anki): add media timing review before card creation by @ksyasuda in #203
- fix(jellyfin): stop inferring subtitle delays by @ksyasuda in #227
- feat(anki): support Senren scene-switching field grouping by @ksyasuda in #230
- fix(mining): copy multi-line subtitles backward from current line by @ksyasuda in #231
- fix(subtitles): keep native secondary subtitles hidden by @ksyasuda in #232
- fix(subtitles): drop ASS furigana from recorded cues by @ksyasuda in #233
- fix(subtitles): merge wrapped positioned caption rows by @ksyasuda in #234
- fix(tsukihime): filter releases by subtitle language by @ksyasuda in #235
- fix(anki): keep overlay progress visible through card updates by @ksyasuda in #218
- fix(youtube): keep auto captions on screen for their full span by @ksyasuda in #219
- fix(subtitles): keep overlapping lines that join an already active cue by @ksyasuda in #221
- fix(anki): respect configured fields for word-card enrichment by @ksyasuda in #223
## Installation
-60
View File
@@ -828,7 +828,6 @@ test('modal fallback reveal skips showing window when content is not ready', asy
setModalWindowBounds: () => {},
},
{
platform: 'darwin',
scheduleRevealFallback: (callback) => {
scheduledReveal = callback;
return { scheduled: true } as never;
@@ -1364,62 +1363,3 @@ test('modal placement reconcile cancels stale retry ladder after a newer visible
globalThis.clearTimeout = originalClearTimeout;
}
});
test('Linux keeps the dedicated modal window unmapped until the renderer opens the modal, then hides the overlay before revealing it', () => {
const mainWindow = createMockWindow();
mainWindow.visible = true;
const modalWindow = createMockWindow();
const order: string[] = [];
const hideMain = mainWindow.hide;
mainWindow.hide = () => {
order.push('main:hide');
hideMain();
};
const showModal = modalWindow.show;
modalWindow.show = () => {
order.push('modal:show');
showModal();
};
let revealScheduled = false;
const runtime = createOverlayModalRuntimeService(
{
getMainWindow: () => mainWindow as never,
getModalWindow: () => modalWindow as never,
createModalWindow: () => modalWindow as never,
getModalGeometry: () => ({ x: 0, y: 0, width: 400, height: 300 }),
setModalWindowBounds: () => {},
},
{
platform: 'linux',
scheduleRevealFallback: () => {
revealScheduled = true;
return { scheduled: true } as never;
},
clearRevealFallback: () => {},
},
);
const open = () =>
runtime.sendToActiveOverlayWindow(
'media-timing-review:open',
{ reviewId: 'review' },
{ restoreOnModalClose: 'media-timing-review', preferModalWindow: true },
);
assert.equal(open(), true);
assert.deepEqual(modalWindow.sent, [['media-timing-review:open', { reviewId: 'review' }]]);
assert.equal(revealScheduled, false);
assert.equal(modalWindow.getShowCount(), 0);
assert.equal(mainWindow.getHideCount(), 0);
// The open retry must not map the window before the renderer answers either.
assert.equal(open(), true);
assert.equal(modalWindow.getShowCount(), 0);
runtime.notifyOverlayModalOpened('media-timing-review');
assert.deepEqual(order, ['main:hide', 'modal:show']);
assert.equal(mainWindow.isVisible(), false);
assert.equal(modalWindow.isVisible(), true);
assert.equal(modalWindow.ignoreMouseEvents, false);
});
+5 -21
View File
@@ -90,12 +90,6 @@ export function createOverlayModalRuntimeService(
const platform = options.platform ?? process.platform;
const shouldPrimeModalWindow = platform === 'darwin' || platform === 'win32';
const reuseModalWindowAfterClose = platform === 'darwin';
// On Linux (Hyprland) every placement dispatch on a mapped window (resize, move, set_prop)
// blanks the still-visible overlay for a few frames while mpv is fullscreen. Revealing the
// dedicated modal window before its renderer has the modal open runs the placement ladder,
// and the open retry, against a visible overlay, which the user sees as flicker. Keep the
// window unmapped until the renderer acknowledges the open, then hide the overlay first.
const deferModalRevealUntilOpened = platform === 'linux';
const focusApplication = options.focusApplication ?? requestOverlayApplicationFocus;
const scheduleRevealFallback = (callback: () => void, delayMs: number): RevealFallbackHandle =>
(options.scheduleRevealFallback ?? globalThis.setTimeout)(callback, delayMs);
@@ -463,9 +457,7 @@ export function createOverlayModalRuntimeService(
deps.setModalWindowBounds(deps.getModalGeometry());
const wasVisible = modalWindow.isVisible();
if (!wasVisible) {
if (deferModalRevealUntilOpened) {
// notifyOverlayModalOpened reveals the window once the renderer has the modal open.
} else if (modalWindowPrimedForImmediateShow && isWindowReadyForIpc(modalWindow)) {
if (modalWindowPrimedForImmediateShow && isWindowReadyForIpc(modalWindow)) {
showModalWindow(modalWindow);
} else {
scheduleModalWindowReveal(modalWindow);
@@ -568,23 +560,15 @@ export function createOverlayModalRuntimeService(
}
const modalWindow = deps.getModalWindow();
const targetIsModalWindow =
modalWindow !== null && !modalWindow.isDestroyed() && targetWindow === modalWindow;
const handOffMainWindowToModal = (): void => {
setMainWindowMousePassthroughForModal(true);
setMainWindowVisibilityForModal(true);
};
if (targetIsModalWindow && deferModalRevealUntilOpened) {
handOffMainWindowToModal();
}
if (targetWindow.isVisible()) {
ensureModalWindowInteractive(targetWindow);
} else {
showModalWindow(targetWindow);
}
if (targetIsModalWindow && !deferModalRevealUntilOpened) {
handOffMainWindowToModal();
if (modalWindow && !modalWindow.isDestroyed() && targetWindow === modalWindow) {
setMainWindowMousePassthroughForModal(true);
setMainWindowVisibilityForModal(true);
}
};
+1 -3
View File
@@ -25,9 +25,7 @@ export async function openMediaTimingReviewModal(
{ waitForModalOpen: deps.waitForModalOpen, logWarn: deps.logWarn },
{
modal: MODAL,
// The review renderer regularly needs more than the 1.5 s the other modals allow; a
// premature retry re-sends the payload and reloads the waveform for nothing.
timeoutMs: 4_000,
timeoutMs: 1_500,
retryWarning:
'Media timing review did not acknowledge modal open; retrying the dedicated modal window.',
sendOpen: () =>