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
This commit is contained in:
2026-08-16 14:18:39 -07:00
parent 2174e689a2
commit 1b1b062803
44 changed files with 2865 additions and 39 deletions
+49
View File
@@ -11,6 +11,54 @@ export type CardKind = 'sentence' | 'audio' | 'word-and-sentence' | 'click';
/** Card kind SubMiner flags on word cards; 'none' leaves the flag fields untouched. */
export type WordCardKind = CardKind | 'none';
export type MediaTimingReviewKind = 'word' | 'sentence' | 'audio';
export interface MediaTimingReviewRequest {
kind: MediaTimingReviewKind;
text: string;
startTime: number;
endTime: number;
noteId?: number;
audioPadding: number;
maxMediaDuration: number;
}
export type MediaTimingReviewDecision =
| { action: 'confirm'; startTime: number; endTime: number }
| { action: 'use-original' }
| { action: 'discard' };
export interface MediaTimingReviewOpenPayload {
reviewId: string;
kind: MediaTimingReviewKind;
text: string;
noteId?: number;
originalStartTime: number;
originalEndTime: number;
selectionStartTime: number;
selectionEndTime: number;
timelineStartTime: number;
timelineEndTime: number;
mediaDuration?: number;
maxMediaDuration: number;
}
export interface MediaTimingReviewPreviewRequest {
reviewId: string;
startTime: number;
endTime: number;
}
export interface MediaTimingReviewResolveRequest {
reviewId: string;
decision: MediaTimingReviewDecision;
}
export interface MediaTimingReviewActionResult {
ok: boolean;
message?: string;
}
export interface NotificationOptions {
body?: string;
icon?: string;
@@ -85,6 +133,7 @@ export interface AnkiConnectConfig {
syncAnimatedImageToWordAudio?: boolean;
normalizeAudio?: boolean;
mirrorMpvVolume?: boolean;
reviewTiming?: boolean;
audioPadding?: number;
fallbackDuration?: number;
maxMediaDuration?: number;
+1
View File
@@ -248,6 +248,7 @@ export interface ResolvedConfig {
syncAnimatedImageToWordAudio: boolean;
normalizeAudio: boolean;
mirrorMpvVolume: boolean;
reviewTiming: boolean;
audioPadding: number;
fallbackDuration: number;
maxMediaDuration: number;
+14
View File
@@ -3,6 +3,10 @@ import type {
KikuFieldGroupingRequestData,
KikuMergePreviewRequest,
KikuMergePreviewResponse,
MediaTimingReviewActionResult,
MediaTimingReviewOpenPayload,
MediaTimingReviewPreviewRequest,
MediaTimingReviewResolveRequest,
} from './anki';
import type { ChangelogSnapshot } from './changelog';
import type { ResolvedConfig, ShortcutsConfig } from './config';
@@ -516,6 +520,14 @@ export interface ElectronAPI {
onOpenJimaku: (callback: () => void) => void;
onOpenTsukihime: (callback: () => void) => void;
onOpenYoutubeTrackPicker: (callback: (payload: YoutubePickerOpenPayload) => void) => void;
onOpenMediaTimingReview: (callback: (payload: MediaTimingReviewOpenPayload) => void) => void;
previewMediaTimingReview: (
request: MediaTimingReviewPreviewRequest,
) => Promise<MediaTimingReviewActionResult>;
stopMediaTimingReviewPreview: (reviewId: string) => Promise<MediaTimingReviewActionResult>;
resolveMediaTimingReview: (
request: MediaTimingReviewResolveRequest,
) => Promise<MediaTimingReviewActionResult>;
onOpenPlaylistBrowser: (callback: () => void) => void;
onOpenCharacterDictionaryManager: (callback: () => void) => void;
onSubtitleSidebarToggle: (callback: () => void) => void;
@@ -561,6 +573,7 @@ export interface ElectronAPI {
| 'jimaku'
| 'tsukihime'
| 'youtube-track-picker'
| 'media-timing-review'
| 'playlist-browser'
| 'kiku'
| 'controller-select'
@@ -577,6 +590,7 @@ export interface ElectronAPI {
| 'jimaku'
| 'tsukihime'
| 'youtube-track-picker'
| 'media-timing-review'
| 'playlist-browser'
| 'kiku'
| 'controller-select'
+2
View File
@@ -243,6 +243,8 @@ export interface SubtitleMiningContext {
startTime: number;
endTime: number;
capturedAtMs?: number;
/** Explicit generator padding. Confirmed timing-review ranges set this to zero. */
mediaPaddingSeconds?: number;
}
export interface SubtitleHoverTokenPayload {