mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-05-26 12:55:16 -07:00
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:
@@ -1,126 +1,76 @@
|
||||
import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { log } from '../log.js';
|
||||
import type { LogLevel, PluginRuntimeConfig } from '../types.js';
|
||||
import type { Backend, LogLevel, PluginRuntimeConfig } from '../types.js';
|
||||
import { DEFAULT_SOCKET_PATH } from '../types.js';
|
||||
import { buildSubminerPluginRuntimeScriptOptParts } from '../../src/shared/subminer-plugin-script-opts.js';
|
||||
import { parseLauncherMpvConfig } from './mpv-config.js';
|
||||
import { readLauncherMainConfigObject } from './shared-config-reader.js';
|
||||
|
||||
function getPlatformPath(platform: NodeJS.Platform): typeof path.posix | typeof path.win32 {
|
||||
return platform === 'win32' ? path.win32 : path.posix;
|
||||
function rootObject(root: Record<string, unknown> | null, key: string): Record<string, unknown> {
|
||||
const value = root?.[key];
|
||||
return value && typeof value === 'object' && !Array.isArray(value)
|
||||
? (value as Record<string, unknown>)
|
||||
: {};
|
||||
}
|
||||
|
||||
export function getPluginConfigCandidates(options?: {
|
||||
platform?: NodeJS.Platform;
|
||||
homeDir?: string;
|
||||
xdgConfigHome?: string;
|
||||
appDataDir?: string;
|
||||
}): string[] {
|
||||
const platform = options?.platform ?? process.platform;
|
||||
const homeDir = options?.homeDir ?? os.homedir();
|
||||
const platformPath = getPlatformPath(platform);
|
||||
function booleanOrDefault(value: unknown, fallback: boolean): boolean {
|
||||
return typeof value === 'boolean' ? value : fallback;
|
||||
}
|
||||
|
||||
if (platform === 'win32') {
|
||||
const appDataDir =
|
||||
options?.appDataDir?.trim() ||
|
||||
process.env.APPDATA?.trim() ||
|
||||
platformPath.join(homeDir, 'AppData', 'Roaming');
|
||||
return [platformPath.join(appDataDir, 'mpv', 'script-opts', 'subminer.conf')];
|
||||
function nonEmptyStringOrDefault(value: unknown, fallback: string): string {
|
||||
if (typeof value !== 'string') return fallback;
|
||||
const trimmed = value.trim();
|
||||
return trimmed.length > 0 ? trimmed : fallback;
|
||||
}
|
||||
|
||||
function validBackendOrDefault(value: unknown, fallback: Backend): Backend {
|
||||
if (typeof value !== 'string') return fallback;
|
||||
const normalized = value.trim().toLowerCase();
|
||||
if (
|
||||
normalized === 'auto' ||
|
||||
normalized === 'hyprland' ||
|
||||
normalized === 'sway' ||
|
||||
normalized === 'x11' ||
|
||||
normalized === 'macos' ||
|
||||
normalized === 'windows'
|
||||
) {
|
||||
return normalized;
|
||||
}
|
||||
|
||||
const xdgConfigHome =
|
||||
options?.xdgConfigHome?.trim() ||
|
||||
process.env.XDG_CONFIG_HOME ||
|
||||
platformPath.join(homeDir, '.config');
|
||||
return Array.from(
|
||||
new Set([
|
||||
platformPath.join(xdgConfigHome, 'mpv', 'script-opts', 'subminer.conf'),
|
||||
platformPath.join(homeDir, '.config', 'mpv', 'script-opts', 'subminer.conf'),
|
||||
]),
|
||||
);
|
||||
return fallback;
|
||||
}
|
||||
|
||||
export function parsePluginRuntimeConfigContent(
|
||||
content: string,
|
||||
logLevel: LogLevel = 'warn',
|
||||
export function parsePluginRuntimeConfigFromMainConfig(
|
||||
root: Record<string, unknown> | null,
|
||||
): PluginRuntimeConfig {
|
||||
const runtimeConfig: PluginRuntimeConfig = {
|
||||
socketPath: DEFAULT_SOCKET_PATH,
|
||||
autoStart: true,
|
||||
autoStartVisibleOverlay: true,
|
||||
autoStartPauseUntilReady: true,
|
||||
const mpvConfig = root ? parseLauncherMpvConfig(root) : {};
|
||||
const texthooker = rootObject(root, 'texthooker');
|
||||
|
||||
return {
|
||||
socketPath: mpvConfig.socketPath ?? DEFAULT_SOCKET_PATH,
|
||||
binaryPath: mpvConfig.subminerBinaryPath ?? '',
|
||||
backend: validBackendOrDefault(mpvConfig.backend, 'auto'),
|
||||
autoStart: booleanOrDefault(mpvConfig.autoStartSubMiner, true),
|
||||
autoStartVisibleOverlay: booleanOrDefault(root?.auto_start_overlay, false),
|
||||
autoStartPauseUntilReady: booleanOrDefault(mpvConfig.pauseUntilOverlayReady, true),
|
||||
texthookerEnabled: booleanOrDefault(texthooker.launchAtStartup, false),
|
||||
aniskipEnabled: booleanOrDefault(mpvConfig.aniskipEnabled, true),
|
||||
aniskipButtonKey: nonEmptyStringOrDefault(mpvConfig.aniskipButtonKey, 'TAB'),
|
||||
};
|
||||
}
|
||||
|
||||
const parseBooleanValue = (key: string, value: string): boolean => {
|
||||
const normalized = value.trim().toLowerCase();
|
||||
if (['yes', 'true', '1', 'on'].includes(normalized)) return true;
|
||||
if (['no', 'false', '0', 'off'].includes(normalized)) return false;
|
||||
log('warn', logLevel, `Invalid boolean value for ${key}: "${value}". Using false.`);
|
||||
return false;
|
||||
};
|
||||
|
||||
for (const line of content.split(/\r?\n/)) {
|
||||
const trimmed = line.trim();
|
||||
if (trimmed.length === 0 || trimmed.startsWith('#')) continue;
|
||||
const keyValueMatch = trimmed.match(/^([a-z0-9_-]+)\s*=\s*(.+)$/i);
|
||||
if (!keyValueMatch) continue;
|
||||
const key = (keyValueMatch[1] || '').toLowerCase();
|
||||
const value = (keyValueMatch[2] || '').split('#', 1)[0]?.trim() || '';
|
||||
if (!value) continue;
|
||||
|
||||
if (key === 'socket_path') {
|
||||
runtimeConfig.socketPath = value;
|
||||
continue;
|
||||
}
|
||||
if (key === 'auto_start') {
|
||||
runtimeConfig.autoStart = parseBooleanValue('auto_start', value);
|
||||
continue;
|
||||
}
|
||||
if (key === 'auto_start_visible_overlay') {
|
||||
runtimeConfig.autoStartVisibleOverlay = parseBooleanValue(
|
||||
'auto_start_visible_overlay',
|
||||
value,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if (key === 'auto_start_pause_until_ready') {
|
||||
runtimeConfig.autoStartPauseUntilReady = parseBooleanValue(
|
||||
'auto_start_pause_until_ready',
|
||||
value,
|
||||
);
|
||||
}
|
||||
}
|
||||
return runtimeConfig;
|
||||
export function buildPluginRuntimeScriptOptParts(
|
||||
runtimeConfig: PluginRuntimeConfig,
|
||||
fallbackAppPath: string,
|
||||
): string[] {
|
||||
return buildSubminerPluginRuntimeScriptOptParts(runtimeConfig, fallbackAppPath);
|
||||
}
|
||||
|
||||
export function readPluginRuntimeConfig(logLevel: LogLevel): PluginRuntimeConfig {
|
||||
const candidates = getPluginConfigCandidates();
|
||||
const defaults: PluginRuntimeConfig = {
|
||||
socketPath: DEFAULT_SOCKET_PATH,
|
||||
autoStart: true,
|
||||
autoStartVisibleOverlay: true,
|
||||
autoStartPauseUntilReady: true,
|
||||
};
|
||||
|
||||
for (const configPath of candidates) {
|
||||
if (!fs.existsSync(configPath)) continue;
|
||||
try {
|
||||
const parsed = parsePluginRuntimeConfigContent(fs.readFileSync(configPath, 'utf8'));
|
||||
log(
|
||||
'debug',
|
||||
logLevel,
|
||||
`Using mpv plugin settings from ${configPath}: socket_path=${parsed.socketPath}, auto_start=${parsed.autoStart}, auto_start_visible_overlay=${parsed.autoStartVisibleOverlay}, auto_start_pause_until_ready=${parsed.autoStartPauseUntilReady}`,
|
||||
);
|
||||
return parsed;
|
||||
} catch {
|
||||
log('warn', logLevel, `Failed to read ${configPath}; using launcher defaults`);
|
||||
return defaults;
|
||||
}
|
||||
}
|
||||
const parsed = parsePluginRuntimeConfigFromMainConfig(readLauncherMainConfigObject());
|
||||
|
||||
log(
|
||||
'debug',
|
||||
logLevel,
|
||||
`No mpv subminer.conf found; using launcher defaults (socket_path=${defaults.socketPath}, auto_start=${defaults.autoStart}, auto_start_visible_overlay=${defaults.autoStartVisibleOverlay}, auto_start_pause_until_ready=${defaults.autoStartPauseUntilReady})`,
|
||||
`Using mpv plugin settings from SubMiner config: socket_path=${parsed.socketPath}, backend=${parsed.backend}, auto_start=${parsed.autoStart}, auto_start_visible_overlay=${parsed.autoStartVisibleOverlay}, auto_start_pause_until_ready=${parsed.autoStartPauseUntilReady}, texthooker_enabled=${parsed.texthookerEnabled}, aniskip_enabled=${parsed.aniskipEnabled}, aniskip_button_key=${parsed.aniskipButtonKey}`,
|
||||
);
|
||||
return defaults;
|
||||
return parsed;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user