/* * SubMiner - All-in-one sentence mining overlay * Copyright (C) 2024 sudacode * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ export enum PartOfSpeech { noun = "noun", verb = "verb", i_adjective = "i_adjective", na_adjective = "na_adjective", particle = "particle", bound_auxiliary = "bound_auxiliary", symbol = "symbol", other = "other", } export interface Token { word: string; partOfSpeech: PartOfSpeech; pos1: string; pos2: string; pos3: string; pos4: string; inflectionType: string; inflectionForm: string; headword: string; katakanaReading: string; pronunciation: string; } export interface MergedToken { surface: string; reading: string; headword: string; startPos: number; endPos: number; partOfSpeech: PartOfSpeech; isMerged: boolean; isKnown: boolean; } export interface WindowGeometry { x: number; y: number; width: number; height: number; } export interface SubtitlePosition { yPercent: number; invisibleOffsetXPx?: number; invisibleOffsetYPx?: number; } export interface SubtitleStyle { fontSize: number; } export interface Keybinding { key: string; command: (string | number)[] | null; } export type SecondarySubMode = "hidden" | "visible" | "hover"; export interface SecondarySubConfig { secondarySubLanguages?: string[]; autoLoadSecondarySub?: boolean; defaultMode?: SecondarySubMode; } export type SubsyncMode = "auto" | "manual"; export interface SubsyncConfig { defaultMode?: SubsyncMode; alass_path?: string; ffsubsync_path?: string; ffmpeg_path?: string; } export interface WebSocketConfig { enabled?: boolean | "auto"; port?: number; } export interface TexthookerConfig { openBrowser?: boolean; } export interface NotificationOptions { body?: string; icon?: string; } export interface MpvClient { currentSubText: string; currentVideoPath: string; currentTimePos: number; currentSubStart: number; currentSubEnd: number; currentAudioStreamIndex: number | null; send(command: { command: unknown[]; request_id?: number }): boolean; } export interface KikuDuplicateCardInfo { noteId: number; expression: string; sentencePreview: string; hasAudio: boolean; hasImage: boolean; isOriginal: boolean; } export interface KikuFieldGroupingRequestData { original: KikuDuplicateCardInfo; duplicate: KikuDuplicateCardInfo; } export interface KikuFieldGroupingChoice { keepNoteId: number; deleteNoteId: number; deleteDuplicate: boolean; cancelled: boolean; } export interface KikuMergePreviewRequest { keepNoteId: number; deleteNoteId: number; deleteDuplicate: boolean; } export interface KikuMergePreviewResponse { ok: boolean; compact?: Record; full?: Record; error?: string; } export type RuntimeOptionId = | "anki.autoUpdateNewCards" | "anki.kikuFieldGrouping" | "anki.nPlusOneMatchMode"; export type RuntimeOptionScope = "ankiConnect"; export type RuntimeOptionValueType = "boolean" | "enum"; export type RuntimeOptionValue = boolean | string; export type NPlusOneMatchMode = "headword" | "surface"; export interface RuntimeOptionState { id: RuntimeOptionId; label: string; scope: RuntimeOptionScope; valueType: RuntimeOptionValueType; value: RuntimeOptionValue; allowedValues: RuntimeOptionValue[]; requiresRestart: boolean; } export interface RuntimeOptionApplyResult { ok: boolean; option?: RuntimeOptionState; osdMessage?: string; requiresRestart?: boolean; error?: string; } export interface AnkiConnectConfig { enabled?: boolean; url?: string; pollingRate?: number; fields?: { audio?: string; image?: string; sentence?: string; miscInfo?: string; translation?: string; }; ai?: { enabled?: boolean; alwaysUseAiTranslation?: boolean; apiKey?: string; model?: string; baseUrl?: string; targetLanguage?: string; systemPrompt?: string; }; openRouter?: { enabled?: boolean; alwaysUseAiTranslation?: boolean; apiKey?: string; model?: string; baseUrl?: string; targetLanguage?: string; systemPrompt?: string; }; media?: { generateAudio?: boolean; generateImage?: boolean; imageType?: "static" | "avif"; imageFormat?: "jpg" | "png" | "webp"; imageQuality?: number; imageMaxWidth?: number; imageMaxHeight?: number; animatedFps?: number; animatedMaxWidth?: number; animatedMaxHeight?: number; animatedCrf?: number; audioPadding?: number; fallbackDuration?: number; maxMediaDuration?: number; }; nPlusOne?: { highlightEnabled?: boolean; refreshMinutes?: number; matchMode?: NPlusOneMatchMode; decks?: string[]; }; behavior?: { overwriteAudio?: boolean; overwriteImage?: boolean; mediaInsertMode?: "append" | "prepend"; highlightWord?: boolean; notificationType?: "osd" | "system" | "both" | "none"; autoUpdateNewCards?: boolean; }; metadata?: { pattern?: string; }; deck?: string; isLapis?: { enabled?: boolean; sentenceCardModel?: string; sentenceCardSentenceField?: string; sentenceCardAudioField?: string; }; isKiku?: { enabled?: boolean; fieldGrouping?: "auto" | "manual" | "disabled"; deleteDuplicateInAuto?: boolean; }; } export interface SubtitleStyleConfig { fontFamily?: string; fontSize?: number; fontColor?: string; fontWeight?: string; fontStyle?: string; backgroundColor?: string; secondary?: { fontFamily?: string; fontSize?: number; fontColor?: string; fontWeight?: string; fontStyle?: string; backgroundColor?: string; }; } export interface ShortcutsConfig { toggleVisibleOverlayGlobal?: string | null; toggleInvisibleOverlayGlobal?: string | null; copySubtitle?: string | null; copySubtitleMultiple?: string | null; updateLastCardFromClipboard?: string | null; triggerFieldGrouping?: string | null; triggerSubsync?: string | null; mineSentence?: string | null; mineSentenceMultiple?: string | null; multiCopyTimeoutMs?: number; toggleSecondarySub?: string | null; markAudioCard?: string | null; openRuntimeOptions?: string | null; openJimaku?: string | null; } export type JimakuLanguagePreference = "ja" | "en" | "none"; export interface JimakuConfig { apiKey?: string; apiKeyCommand?: string; apiBaseUrl?: string; languagePreference?: JimakuLanguagePreference; maxEntryResults?: number; } export interface InvisibleOverlayConfig { startupVisibility?: "platform-default" | "visible" | "hidden"; } export type YoutubeSubgenMode = "automatic" | "preprocess" | "off"; export interface YoutubeSubgenConfig { mode?: YoutubeSubgenMode; whisperBin?: string; whisperModel?: string; primarySubLanguages?: string[]; } export interface Config { subtitlePosition?: SubtitlePosition; keybindings?: Keybinding[]; websocket?: WebSocketConfig; texthooker?: TexthookerConfig; ankiConnect?: AnkiConnectConfig; shortcuts?: ShortcutsConfig; secondarySub?: SecondarySubConfig; subsync?: SubsyncConfig; subtitleStyle?: SubtitleStyleConfig; auto_start_overlay?: boolean; bind_visible_overlay_to_mpv_sub_visibility?: boolean; jimaku?: JimakuConfig; invisibleOverlay?: InvisibleOverlayConfig; youtubeSubgen?: YoutubeSubgenConfig; } export type RawConfig = Config; export interface ResolvedConfig { subtitlePosition: SubtitlePosition; keybindings: Keybinding[]; websocket: Required; texthooker: Required; ankiConnect: AnkiConnectConfig & { enabled: boolean; url: string; pollingRate: number; fields: { audio: string; image: string; sentence: string; miscInfo: string; translation: string; }; ai: { enabled: boolean; alwaysUseAiTranslation: boolean; apiKey: string; model: string; baseUrl: string; targetLanguage: string; systemPrompt: string; }; media: { generateAudio: boolean; generateImage: boolean; imageType: "static" | "avif"; imageFormat: "jpg" | "png" | "webp"; imageQuality: number; imageMaxWidth?: number; imageMaxHeight?: number; animatedFps: number; animatedMaxWidth: number; animatedMaxHeight?: number; animatedCrf: number; audioPadding: number; fallbackDuration: number; maxMediaDuration: number; }; nPlusOne: { highlightEnabled: boolean; refreshMinutes: number; matchMode: NPlusOneMatchMode; decks: string[]; }; behavior: { overwriteAudio: boolean; overwriteImage: boolean; mediaInsertMode: "append" | "prepend"; highlightWord: boolean; notificationType: "osd" | "system" | "both" | "none"; autoUpdateNewCards: boolean; }; metadata: { pattern: string; }; isLapis: { enabled: boolean; sentenceCardModel: string; sentenceCardSentenceField: string; sentenceCardAudioField: string; }; isKiku: { enabled: boolean; fieldGrouping: "auto" | "manual" | "disabled"; deleteDuplicateInAuto: boolean; }; }; shortcuts: Required; secondarySub: Required; subsync: Required; subtitleStyle: Required> & { secondary: Required>; }; auto_start_overlay: boolean; bind_visible_overlay_to_mpv_sub_visibility: boolean; jimaku: JimakuConfig & { apiBaseUrl: string; languagePreference: JimakuLanguagePreference; maxEntryResults: number; }; invisibleOverlay: Required; youtubeSubgen: YoutubeSubgenConfig & { mode: YoutubeSubgenMode; whisperBin: string; whisperModel: string; primarySubLanguages: string[]; }; } export interface ConfigValidationWarning { path: string; value: unknown; fallback: unknown; message: string; } export interface SubsyncSourceTrack { id: number; label: string; } export interface SubsyncManualPayload { sourceTracks: SubsyncSourceTrack[]; } export interface SubsyncManualRunRequest { engine: "alass" | "ffsubsync"; sourceTrackId?: number | null; } export interface SubsyncResult { ok: boolean; message: string; } export interface SubtitleData { text: string; tokens: MergedToken[] | null; } export interface MpvSubtitleRenderMetrics { subPos: number; subFontSize: number; subScale: number; subMarginY: number; subMarginX: number; subFont: string; subSpacing: number; subBold: boolean; subItalic: boolean; subBorderSize: number; subShadowOffset: number; subAssOverride: string; subScaleByWindow: boolean; subUseMargins: boolean; osdHeight: number; osdDimensions: { w: number; h: number; ml: number; mr: number; mt: number; mb: number; } | null; } export type OverlayLayer = "visible" | "invisible"; export interface OverlayContentRect { x: number; y: number; width: number; height: number; } export interface OverlayContentMeasurement { layer: OverlayLayer; measuredAtMs: number; viewport: { width: number; height: number; }; contentRect: OverlayContentRect | null; } export interface MecabStatus { available: boolean; enabled: boolean; path: string | null; } export type JimakuConfidence = "high" | "medium" | "low"; export interface JimakuMediaInfo { title: string; season: number | null; episode: number | null; confidence: JimakuConfidence; filename: string; rawTitle: string; } export interface JimakuSearchQuery { query: string; } export interface JimakuEntryFlags { anime?: boolean; movie?: boolean; adult?: boolean; external?: boolean; unverified?: boolean; } export interface JimakuEntry { id: number; name: string; english_name?: string | null; japanese_name?: string | null; flags?: JimakuEntryFlags; last_modified?: string; } export interface JimakuFilesQuery { entryId: number; episode?: number | null; } export interface JimakuFileEntry { name: string; url: string; size: number; last_modified: string; } export interface JimakuDownloadQuery { entryId: number; url: string; name: string; } export interface JimakuApiError { error: string; code?: number; retryAfter?: number; } export type JimakuApiResponse = | { ok: true; data: T } | { ok: false; error: JimakuApiError }; export type JimakuDownloadResult = | { ok: true; path: string } | { ok: false; error: JimakuApiError }; export interface ElectronAPI { getOverlayLayer: () => "visible" | "invisible" | null; onSubtitle: (callback: (data: SubtitleData) => void) => void; onVisibility: (callback: (visible: boolean) => void) => void; onSubtitlePosition: ( callback: (position: SubtitlePosition | null) => void, ) => void; getOverlayVisibility: () => Promise; getCurrentSubtitle: () => Promise; getCurrentSubtitleAss: () => Promise; getMpvSubtitleRenderMetrics: () => Promise; onMpvSubtitleRenderMetrics: ( callback: (metrics: MpvSubtitleRenderMetrics) => void, ) => void; onSubtitleAss: (callback: (assText: string) => void) => void; onOverlayDebugVisualization: (callback: (enabled: boolean) => void) => void; setIgnoreMouseEvents: ( ignore: boolean, options?: { forward?: boolean }, ) => void; openYomitanSettings: () => void; getSubtitlePosition: () => Promise; saveSubtitlePosition: (position: SubtitlePosition) => void; getMecabStatus: () => Promise; setMecabEnabled: (enabled: boolean) => void; sendMpvCommand: (command: (string | number)[]) => void; getKeybindings: () => Promise; getJimakuMediaInfo: () => Promise; jimakuSearchEntries: ( query: JimakuSearchQuery, ) => Promise>; jimakuListFiles: ( query: JimakuFilesQuery, ) => Promise>; jimakuDownloadFile: ( query: JimakuDownloadQuery, ) => Promise; quitApp: () => void; toggleDevTools: () => void; toggleOverlay: () => void; getAnkiConnectStatus: () => Promise; setAnkiConnectEnabled: (enabled: boolean) => void; clearAnkiConnectHistory: () => void; onSecondarySub: (callback: (text: string) => void) => void; onSecondarySubMode: (callback: (mode: SecondarySubMode) => void) => void; getSecondarySubMode: () => Promise; getCurrentSecondarySub: () => Promise; getSubtitleStyle: () => Promise; onSubsyncManualOpen: ( callback: (payload: SubsyncManualPayload) => void, ) => void; runSubsyncManual: ( request: SubsyncManualRunRequest, ) => Promise; onKikuFieldGroupingRequest: ( callback: (data: KikuFieldGroupingRequestData) => void, ) => void; kikuBuildMergePreview: ( request: KikuMergePreviewRequest, ) => Promise; kikuFieldGroupingRespond: (choice: KikuFieldGroupingChoice) => void; getRuntimeOptions: () => Promise; setRuntimeOptionValue: ( id: RuntimeOptionId, value: RuntimeOptionValue, ) => Promise; cycleRuntimeOption: ( id: RuntimeOptionId, direction: 1 | -1, ) => Promise; onRuntimeOptionsChanged: ( callback: (options: RuntimeOptionState[]) => void, ) => void; onOpenRuntimeOptions: (callback: () => void) => void; onOpenJimaku: (callback: () => void) => void; notifyOverlayModalClosed: (modal: "runtime-options" | "subsync" | "jimaku") => void; reportOverlayContentBounds: (measurement: OverlayContentMeasurement) => void; } declare global { interface Window { electronAPI: ElectronAPI; } }