feat(config): unify mpv plugin options under main config and add CSS/Ani

- Replace subminer.conf plugin config with mpv.* fields in config.jsonc
- Add socketPath, backend, autoStartSubMiner, pauseUntilOverlayReady, aniskipEnabled/buttonKey, subminerBinaryPath to mpv config
- Add subtitleSidebar.css field; migrate legacy sidebar appearance fields
- Add paintOrder and WebkitTextStroke to subtitle style options
- Update default subtitle/sidebar fontFamily to CJK-first stack
- Fix overlay visible state surviving mpv y-r restart
- Fix live config saves applying subtitle CSS immediately to open overlays
- Migrate legacy primary/secondary subtitle appearance into subtitleStyle.css on load
- Switch AniSkip button key setting to click-to-learn key capture
This commit is contained in:
2026-05-17 18:01:39 -07:00
parent 81830b3372
commit 6ba91780c1
91 changed files with 2241 additions and 727 deletions
+48 -11
View File
@@ -375,7 +375,6 @@ import {
detectInstalledMpvPlugin,
removeLegacyMpvPluginCandidates,
resolvePackagedRuntimePluginPath,
syncInstalledFirstRunPluginBinaryPath,
} from './main/runtime/first-run-setup-plugin';
import {
applyWindowsMpvShortcuts,
@@ -494,6 +493,7 @@ import { createCharacterDictionaryAutoSyncRuntimeService } from './main/runtime/
import { handleCharacterDictionaryAutoSyncComplete } from './main/runtime/character-dictionary-auto-sync-completion';
import { notifyCharacterDictionaryAutoSyncStatus } from './main/runtime/character-dictionary-auto-sync-notifications';
import { createCurrentMediaTokenizationGate } from './main/runtime/current-media-tokenization-gate';
import { resolveCurrentSubtitleForRenderer } from './main/runtime/current-subtitle-snapshot';
import { createStartupOsdSequencer } from './main/runtime/startup-osd-sequencer';
import {
createElectronAppUpdater,
@@ -664,7 +664,7 @@ const texthookerService = new Texthooker(() => {
yomitanProfilePolicy.isCharacterDictionaryEnabled();
const knownAndNPlusOneEnabled = getRuntimeBooleanOption(
'subtitle.annotation.nPlusOne',
config.ankiConnect.knownWords.highlightEnabled,
config.ankiConnect.nPlusOne.enabled,
);
return {
@@ -1216,6 +1216,17 @@ const youtubePlaybackRuntime = createYoutubePlaybackRuntime({
resolveInstalledPluginBeforeLaunch: (detection, mpvPath) =>
promptForLegacyMpvPluginRemovalBeforeWindowsLaunch(mpvPath, detection),
},
{
socketPath: appState.mpvSocketPath,
binaryPath: getResolvedConfig().mpv.subminerBinaryPath,
backend: getResolvedConfig().mpv.backend,
autoStart: getResolvedConfig().mpv.autoStartSubMiner,
autoStartVisibleOverlay: getResolvedConfig().auto_start_overlay,
autoStartPauseUntilReady: getResolvedConfig().mpv.pauseUntilOverlayReady,
texthookerEnabled: getResolvedConfig().texthooker.launchAtStartup,
aniskipEnabled: getResolvedConfig().mpv.aniskipEnabled,
aniskipButtonKey: getResolvedConfig().mpv.aniskipButtonKey,
},
),
waitForYoutubeMpvConnected: (timeoutMs) => waitForYoutubeMpvConnected(timeoutMs),
prepareYoutubePlaybackInMpv: (request) => prepareYoutubePlaybackInMpv(request),
@@ -1242,12 +1253,6 @@ const createCommandLineLauncherRuntimeOptions = () => ({
resourcesPath: process.resourcesPath,
appExePath: process.execPath,
});
syncInstalledFirstRunPluginBinaryPath({
platform: process.platform,
homeDir: os.homedir(),
xdgConfigHome: process.env.XDG_CONFIG_HOME,
binaryPath: process.execPath,
});
const firstRunSetupService = createFirstRunSetupService({
platform: process.platform,
configDir: CONFIG_DIR,
@@ -1807,6 +1812,7 @@ const configSettingsRuntime = createConfigSettingsRuntime({
getConfig: () => configService.getConfig(),
getWarnings: () => configService.getWarnings(),
reloadConfigStrict: () => configService.reloadConfigStrict(),
onHotReloadApplied: applyConfigHotReloadDiff,
defaultAnkiConnectUrl: DEFAULT_CONFIG.ankiConnect.url,
createAnkiClient: (url) => new AnkiConnectClient(url),
getSettingsWindow: () => appState.configSettingsWindow,
@@ -2618,6 +2624,17 @@ const {
getLaunchMode: () => getResolvedConfig().mpv.launchMode,
platform: process.platform,
execPath: process.execPath,
getPluginRuntimeConfig: () => ({
socketPath: appState.mpvSocketPath,
binaryPath: getResolvedConfig().mpv.subminerBinaryPath,
backend: getResolvedConfig().mpv.backend,
autoStart: getResolvedConfig().mpv.autoStartSubMiner,
autoStartVisibleOverlay: getResolvedConfig().auto_start_overlay,
autoStartPauseUntilReady: getResolvedConfig().mpv.pauseUntilOverlayReady,
texthookerEnabled: getResolvedConfig().texthooker.launchAtStartup,
aniskipEnabled: getResolvedConfig().mpv.aniskipEnabled,
aniskipButtonKey: getResolvedConfig().mpv.aniskipButtonKey,
}),
defaultMpvLogPath: DEFAULT_MPV_LOG_PATH,
defaultMpvArgs: MPV_JELLYFIN_DEFAULT_ARGS,
removeSocketPath: (socketPath) => {
@@ -4017,6 +4034,17 @@ const {
reportJellyfinRemoteStopped: () => {
void reportJellyfinRemoteStopped();
},
onMpvConnected: () => {
if (appState.sessionBindingsInitialized) {
sendMpvCommandRuntime(appState.mpvClient, [
'script-message',
'subminer-reload-session-bindings',
]);
}
if (appState.currentSubText.trim()) {
subtitleProcessingController.refreshCurrentSubtitle(appState.currentSubText);
}
},
maybeRunAnilistPostWatchUpdate: (options) => maybeRunAnilistPostWatchUpdate(options),
recordAnilistMediaDuration: (durationSec) => {
recordAnilistMediaDuration(durationSec);
@@ -5189,7 +5217,11 @@ const { registerIpcRuntimeHandlers } = composeIpcRuntimeHandlers({
quitApp: () => requestAppQuit(),
toggleVisibleOverlay: () => toggleVisibleOverlay(),
tokenizeCurrentSubtitle: async () =>
withCurrentSubtitleTiming(await tokenizeSubtitle(appState.currentSubText)),
resolveCurrentSubtitleForRenderer({
currentSubText: appState.currentSubText,
currentSubtitleData: appState.currentSubtitleData,
withCurrentSubtitleTiming: (payload) => withCurrentSubtitleTiming(payload),
}),
getCurrentSubtitleRaw: () => appState.currentSubText,
getCurrentSubtitleAss: () => appState.currentSubAssText,
getSubtitleSidebarSnapshot: async () => {
@@ -5526,7 +5558,7 @@ const { runAndApplyStartupState } = composeHeadlessStartupHandlers<
enforceUnsupportedWaylandMode(args);
},
shouldStartApp: (args: CliArgs) => shouldStartApp(args),
getDefaultSocketPath: () => getDefaultSocketPath(),
getDefaultSocketPath: () => getResolvedConfig().mpv.socketPath || getDefaultSocketPath(),
defaultTexthookerPort: DEFAULT_TEXTHOOKER_PORT,
configDir: CONFIG_DIR,
defaultConfig: DEFAULT_CONFIG,
@@ -5592,7 +5624,12 @@ const { createMainWindow: createMainWindowHandler, createModalWindow: createModa
overlayShortcutsRuntime.tryHandleOverlayShortcutLocalFallback(input),
forwardTabToMpv: () => sendMpvCommandRuntime(appState.mpvClient, ['keypress', 'TAB']),
onVisibleWindowBlurred: () => scheduleVisibleOverlayBlurRefresh(),
onWindowContentReady: () => overlayVisibilityRuntime.updateVisibleOverlayVisibility(),
onWindowContentReady: () => {
overlayVisibilityRuntime.updateVisibleOverlayVisibility();
if (appState.currentSubText.trim()) {
subtitleProcessingController.refreshCurrentSubtitle(appState.currentSubText);
}
},
onWindowClosed: (windowKind) => {
if (windowKind === 'visible') {
cancelPendingLinuxMpvFullscreenOverlayRefreshBurst();