mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-09-22 17:16:19 -07:00
feat: add subtitle generation and bundle Bun launcher runtime
- Add local subtitle generation and card timing review workflows - Package cross-platform Bun runtimes, launchers, licenses, and source - Consolidate release packaging and refresh v0.19.6 documentation
This commit is contained in:
@@ -11,6 +11,78 @@ 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;
|
||||
}
|
||||
|
||||
/** A subtitle line adjacent to the mined one that the review can pull onto the card. */
|
||||
export interface MediaTimingReviewContextLine {
|
||||
text: string;
|
||||
startTime: number;
|
||||
endTime: number;
|
||||
}
|
||||
|
||||
export type MediaTimingReviewDecision =
|
||||
/** `text` is set when the review combined adjacent lines into the card sentence. */
|
||||
| { action: 'confirm'; startTime: number; endTime: number; text?: string }
|
||||
| { action: 'use-original' }
|
||||
| { action: 'skip-media' }
|
||||
| { action: 'discard' };
|
||||
|
||||
export interface MediaTimingReviewOpenPayload {
|
||||
reviewId: string;
|
||||
kind: MediaTimingReviewKind;
|
||||
text: string;
|
||||
/** Lines before/after the mined one, both chronological: nearest previous line is last, nearest next line is first. */
|
||||
previousLines: MediaTimingReviewContextLine[];
|
||||
nextLines: MediaTimingReviewContextLine[];
|
||||
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 MediaTimingReviewWaveformRequest {
|
||||
reviewId: string;
|
||||
startTime: number;
|
||||
endTime: number;
|
||||
}
|
||||
|
||||
export interface MediaTimingReviewWaveformResult extends MediaTimingReviewActionResult {
|
||||
peaks?: number[];
|
||||
}
|
||||
|
||||
export interface MediaTimingReviewResolveRequest {
|
||||
reviewId: string;
|
||||
decision: MediaTimingReviewDecision;
|
||||
}
|
||||
|
||||
export interface MediaTimingReviewActionResult {
|
||||
ok: boolean;
|
||||
message?: string;
|
||||
/** The review this request targeted has already ended; the renderer should close. */
|
||||
stale?: boolean;
|
||||
}
|
||||
|
||||
export interface NotificationOptions {
|
||||
body?: string;
|
||||
icon?: string;
|
||||
@@ -85,6 +157,7 @@ export interface AnkiConnectConfig {
|
||||
syncAnimatedImageToWordAudio?: boolean;
|
||||
normalizeAudio?: boolean;
|
||||
mirrorMpvVolume?: boolean;
|
||||
reviewTiming?: boolean;
|
||||
audioPadding?: number;
|
||||
fallbackDuration?: number;
|
||||
maxMediaDuration?: number;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { AnkiConnectConfig, WordCardKind } from './anki';
|
||||
import type { SubtitleGenerationConfig } from '../shared/subtitle-generation';
|
||||
import type {
|
||||
AiConfig,
|
||||
AiFeatureConfig,
|
||||
@@ -125,6 +126,7 @@ export interface ShortcutsConfig {
|
||||
openRuntimeOptions?: string | null;
|
||||
openJimaku?: string | null;
|
||||
openTsukihime?: string | null;
|
||||
openSubtitleGeneration?: string | null;
|
||||
openSessionHelp?: string | null;
|
||||
openControllerSelect?: string | null;
|
||||
openControllerDebug?: string | null;
|
||||
@@ -150,6 +152,7 @@ export interface Config {
|
||||
shortcuts?: RawShortcutsConfig;
|
||||
secondarySub?: SecondarySubConfig;
|
||||
subsync?: SubsyncConfig;
|
||||
subtitleGeneration?: Partial<SubtitleGenerationConfig>;
|
||||
startupWarmups?: StartupWarmupsConfig;
|
||||
subtitleStyle?: SubtitleStyleConfig;
|
||||
subtitleSidebar?: SubtitleSidebarConfig;
|
||||
@@ -250,6 +253,7 @@ export interface ResolvedConfig {
|
||||
syncAnimatedImageToWordAudio: boolean;
|
||||
normalizeAudio: boolean;
|
||||
mirrorMpvVolume: boolean;
|
||||
reviewTiming: boolean;
|
||||
audioPadding: number;
|
||||
fallbackDuration: number;
|
||||
maxMediaDuration: number;
|
||||
@@ -299,6 +303,7 @@ export interface ResolvedConfig {
|
||||
shortcuts: Required<ShortcutsConfig>;
|
||||
secondarySub: Required<SecondarySubConfig>;
|
||||
subsync: Required<SubsyncConfig>;
|
||||
subtitleGeneration: SubtitleGenerationConfig;
|
||||
startupWarmups: {
|
||||
lowPowerMode: boolean;
|
||||
mecab: boolean;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export type RuntimeOptionId =
|
||||
| 'anki.autoUpdateNewCards'
|
||||
| 'anki.mediaReviewTiming'
|
||||
| 'subtitle.annotation.knownWords.highlightEnabled'
|
||||
| 'subtitle.annotation.knownWords.maturityEnabled'
|
||||
| 'subtitle.annotation.nPlusOne'
|
||||
|
||||
@@ -1,11 +1,24 @@
|
||||
import type { MpvInputBindingsSnapshot } from './session-bindings';
|
||||
import type {
|
||||
KikuFieldGroupingChoice,
|
||||
KikuFieldGroupingRequestData,
|
||||
KikuMergePreviewRequest,
|
||||
KikuMergePreviewResponse,
|
||||
MediaTimingReviewActionResult,
|
||||
MediaTimingReviewOpenPayload,
|
||||
MediaTimingReviewPreviewRequest,
|
||||
MediaTimingReviewResolveRequest,
|
||||
MediaTimingReviewWaveformRequest,
|
||||
MediaTimingReviewWaveformResult,
|
||||
} from './anki';
|
||||
import type { ChangelogSnapshot } from './changelog';
|
||||
import type { OverlayHostedModal } from '../shared/ipc/contracts';
|
||||
import type { SubtitleGenerationProgress } from '../shared/subtitle-generation';
|
||||
import type { SubtitleGenerationModelId } from '../shared/subtitle-generation-model-catalog';
|
||||
import type {
|
||||
SubtitleGenerationResult,
|
||||
SubtitleGenerationStatus,
|
||||
} from '../shared/subtitle-generation-ipc';
|
||||
import type { ResolvedConfig, ShortcutsConfig } from './config';
|
||||
import type {
|
||||
CompiledSessionBinding,
|
||||
@@ -425,6 +438,20 @@ export interface SessionNumericSelectionStartPayload {
|
||||
}
|
||||
|
||||
export interface ElectronAPI {
|
||||
requestSubtitleGenerationOpen: () => Promise<boolean>;
|
||||
onSubtitleGenerationOpen: (callback: () => void) => void;
|
||||
getSubtitleGenerationStatus: () => Promise<SubtitleGenerationStatus>;
|
||||
selectSubtitleGenerationModel: (
|
||||
model: SubtitleGenerationModelId,
|
||||
) => Promise<SubtitleGenerationStatus>;
|
||||
startSubtitleGeneration: () => Promise<SubtitleGenerationResult>;
|
||||
downloadSubtitleGenerationModel: () => Promise<SubtitleGenerationResult>;
|
||||
downloadSubtitleGenerationVadModel: () => Promise<SubtitleGenerationResult>;
|
||||
setSubtitleGenerationVadEnabled: (enabled: boolean) => Promise<SubtitleGenerationStatus>;
|
||||
cancelSubtitleGeneration: () => Promise<void>;
|
||||
onSubtitleGenerationProgress: (
|
||||
callback: (progress: SubtitleGenerationProgress) => void,
|
||||
) => () => void;
|
||||
getOverlayLayer: () => 'visible' | 'modal' | null;
|
||||
getPathForFile: (file: File) => string;
|
||||
onSubtitle: (callback: (data: SubtitleData) => void) => void;
|
||||
@@ -443,6 +470,7 @@ export interface ElectronAPI {
|
||||
getCurrentSubtitleRaw: () => Promise<string>;
|
||||
getCurrentSubtitleAss: () => Promise<string>;
|
||||
getSubtitleSidebarSnapshot: () => Promise<SubtitleSidebarSnapshot>;
|
||||
copySubtitleSidebarSelection: (text: string) => Promise<void>;
|
||||
getSubtitleSidebarOpen: () => Promise<boolean>;
|
||||
getPlaybackPaused: () => Promise<boolean | null>;
|
||||
onSubtitleAss: (callback: (assText: string) => void) => void;
|
||||
@@ -456,6 +484,7 @@ export interface ElectronAPI {
|
||||
setMecabEnabled: (enabled: boolean) => void;
|
||||
sendMpvCommand: (command: (string | number)[]) => void;
|
||||
getKeybindings: () => Promise<Keybinding[]>;
|
||||
getMpvInputBindings: () => Promise<MpvInputBindingsSnapshot>;
|
||||
getSessionBindings: () => Promise<CompiledSessionBinding[]>;
|
||||
getConfiguredShortcuts: () => Promise<Required<ShortcutsConfig>>;
|
||||
dispatchSessionAction: (
|
||||
@@ -519,6 +548,18 @@ export interface ElectronAPI {
|
||||
onOpenJimaku: (callback: () => void) => void;
|
||||
onOpenTsukihime: (callback: () => void) => void;
|
||||
onOpenYoutubeTrackPicker: (callback: (payload: YoutubePickerOpenPayload) => void) => void;
|
||||
onOpenMediaTimingReview: (callback: (payload: MediaTimingReviewOpenPayload) => void) => void;
|
||||
onMediaTimingReviewPreviewEnded: (callback: (reviewId: string) => void) => void;
|
||||
previewMediaTimingReview: (
|
||||
request: MediaTimingReviewPreviewRequest,
|
||||
) => Promise<MediaTimingReviewActionResult>;
|
||||
getMediaTimingReviewWaveform: (
|
||||
request: MediaTimingReviewWaveformRequest,
|
||||
) => Promise<MediaTimingReviewWaveformResult>;
|
||||
stopMediaTimingReviewPreview: (reviewId: string) => Promise<MediaTimingReviewActionResult>;
|
||||
resolveMediaTimingReview: (
|
||||
request: MediaTimingReviewResolveRequest,
|
||||
) => Promise<MediaTimingReviewActionResult>;
|
||||
onOpenPlaylistBrowser: (callback: () => void) => void;
|
||||
onOpenCharacterDictionaryManager: (callback: () => void) => void;
|
||||
onSubtitleSidebarToggle: (callback: () => void) => void;
|
||||
|
||||
@@ -23,6 +23,7 @@ export type SessionActionId =
|
||||
| 'openControllerDebug'
|
||||
| 'openJimaku'
|
||||
| 'openTsukihime'
|
||||
| 'openSubtitleGeneration'
|
||||
| 'openYoutubePicker'
|
||||
| 'openPlaylistBrowser'
|
||||
| 'openAnimeBrowser'
|
||||
@@ -35,6 +36,11 @@ export interface SessionKeySpec {
|
||||
modifiers: SessionKeyModifier[];
|
||||
}
|
||||
|
||||
export interface MpvInputBindingsSnapshot {
|
||||
keys: string[];
|
||||
blockedKeys: SessionKeySpec[];
|
||||
}
|
||||
|
||||
export interface SessionBindingWarning {
|
||||
kind: 'unsupported' | 'conflict' | 'deprecated-config';
|
||||
path: string;
|
||||
|
||||
@@ -227,6 +227,7 @@ export interface SubtitleData {
|
||||
}
|
||||
|
||||
export interface SubtitleSidebarSnapshot {
|
||||
sourceKey: string | null;
|
||||
cues: SubtitleCue[];
|
||||
currentTimeSec?: number | null;
|
||||
currentSubtitle: {
|
||||
@@ -243,6 +244,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 {
|
||||
|
||||
Reference in New Issue
Block a user