Enhance AniList character dictionary sync and subtitle features (#15)

This commit is contained in:
2026-03-07 18:30:59 -08:00
committed by GitHub
parent 2f07c3407a
commit e18985fb14
696 changed files with 14297 additions and 173564 deletions

View File

@@ -5,6 +5,7 @@ export const CORE_DEFAULT_CONFIG: Pick<
| 'subtitlePosition'
| 'keybindings'
| 'websocket'
| 'annotationWebsocket'
| 'logging'
| 'texthooker'
| 'shortcuts'
@@ -19,10 +20,15 @@ export const CORE_DEFAULT_CONFIG: Pick<
enabled: 'auto',
port: 6677,
},
annotationWebsocket: {
enabled: true,
port: 6678,
},
logging: {
level: 'info',
},
texthooker: {
launchAtStartup: true,
openBrowser: true,
},
shortcuts: {

View File

@@ -86,6 +86,18 @@ export const INTEGRATIONS_DEFAULT_CONFIG: Pick<
anilist: {
enabled: false,
accessToken: '',
characterDictionary: {
enabled: false,
refreshTtlHours: 168,
maxLoaded: 3,
evictionPolicy: 'delete',
profileScope: 'all',
collapsibleSections: {
description: false,
characterInformation: false,
voicedBy: false,
},
},
},
jellyfin: {
enabled: false,

View File

@@ -8,6 +8,8 @@ export const SUBTITLE_DEFAULT_CONFIG: Pick<ResolvedConfig, 'subtitleStyle'> = {
autoPauseVideoOnYomitanPopup: false,
hoverTokenColor: '#f4dbd6',
hoverTokenBackgroundColor: 'rgba(54, 58, 79, 0.84)',
nameMatchEnabled: true,
nameMatchColor: '#f5bde6',
fontFamily: 'M PLUS 1 Medium, Source Han Sans JP, Noto Sans CJK JP',
fontSize: 35,
fontColor: '#cad3f5',
@@ -37,7 +39,7 @@ export const SUBTITLE_DEFAULT_CONFIG: Pick<ResolvedConfig, 'subtitleStyle'> = {
mode: 'single',
matchMode: 'headword',
singleColor: '#f5a97f',
bandedColors: ['#ed8796', '#f5a97f', '#f9e2af', '#a6e3a1', '#8aadf4'],
bandedColors: ['#ed8796', '#f5a97f', '#f9e2af', '#8bd5ca', '#8aadf4'],
},
secondary: {
fontFamily: 'Inter, Noto Sans, Helvetica Neue, sans-serif',

View File

@@ -18,10 +18,13 @@ test('config option registry includes critical paths and has unique entries', ()
for (const requiredPath of [
'logging.level',
'annotationWebsocket.enabled',
'startupWarmups.lowPowerMode',
'subtitleStyle.enableJlpt',
'subtitleStyle.autoPauseVideoOnYomitanPopup',
'ankiConnect.enabled',
'anilist.characterDictionary.enabled',
'anilist.characterDictionary.collapsibleSections.description',
'immersionTracking.enabled',
]) {
assert.ok(paths.includes(requiredPath), `missing config path: ${requiredPath}`);
@@ -34,6 +37,7 @@ test('config template sections include expected domains and unique keys', () =>
const keys = CONFIG_TEMPLATE_SECTIONS.map((section) => section.key);
const requiredKeys: (typeof keys)[number][] = [
'websocket',
'annotationWebsocket',
'startupWarmups',
'subtitleStyle',
'ankiConnect',

View File

@@ -12,6 +12,12 @@ export function buildCoreConfigOptionRegistry(
defaultValue: defaultConfig.logging.level,
description: 'Minimum log level for runtime logging.',
},
{
path: 'texthooker.launchAtStartup',
kind: 'boolean',
defaultValue: defaultConfig.texthooker.launchAtStartup,
description: 'Launch texthooker server automatically when SubMiner starts.',
},
{
path: 'websocket.enabled',
kind: 'enum',
@@ -25,6 +31,18 @@ export function buildCoreConfigOptionRegistry(
defaultValue: defaultConfig.websocket.port,
description: 'Built-in subtitle websocket server port.',
},
{
path: 'annotationWebsocket.enabled',
kind: 'boolean',
defaultValue: defaultConfig.annotationWebsocket.enabled,
description: 'Annotated subtitle websocket server enabled state.',
},
{
path: 'annotationWebsocket.port',
kind: 'number',
defaultValue: defaultConfig.annotationWebsocket.port,
description: 'Annotated subtitle websocket server port.',
},
{
path: 'subsync.defaultMode',
kind: 'enum',

View File

@@ -135,6 +135,64 @@ export function buildIntegrationConfigOptionRegistry(
description:
'Optional explicit AniList access token override; leave empty to use locally stored token from setup.',
},
{
path: 'anilist.characterDictionary.enabled',
kind: 'boolean',
defaultValue: defaultConfig.anilist.characterDictionary.enabled,
description:
'Enable automatic Yomitan character dictionary sync for currently watched AniList media.',
},
{
path: 'anilist.characterDictionary.refreshTtlHours',
kind: 'number',
defaultValue: defaultConfig.anilist.characterDictionary.refreshTtlHours,
description:
'Legacy setting; merged character dictionary retention is now usage-based and this value is ignored.',
},
{
path: 'anilist.characterDictionary.maxLoaded',
kind: 'number',
defaultValue: defaultConfig.anilist.characterDictionary.maxLoaded,
description:
'Maximum number of most-recently-used anime snapshots included in the merged Yomitan character dictionary.',
},
{
path: 'anilist.characterDictionary.evictionPolicy',
kind: 'enum',
enumValues: ['disable', 'delete'],
defaultValue: defaultConfig.anilist.characterDictionary.evictionPolicy,
description:
'Legacy setting; merged character dictionary eviction is usage-based and this value is ignored.',
},
{
path: 'anilist.characterDictionary.profileScope',
kind: 'enum',
enumValues: ['all', 'active'],
defaultValue: defaultConfig.anilist.characterDictionary.profileScope,
description: 'Yomitan profile scope for dictionary enable/disable updates.',
},
{
path: 'anilist.characterDictionary.collapsibleSections.description',
kind: 'boolean',
defaultValue: defaultConfig.anilist.characterDictionary.collapsibleSections.description,
description:
'Open the Description section by default in character dictionary glossary entries.',
},
{
path: 'anilist.characterDictionary.collapsibleSections.characterInformation',
kind: 'boolean',
defaultValue:
defaultConfig.anilist.characterDictionary.collapsibleSections.characterInformation,
description:
'Open the Character Information section by default in character dictionary glossary entries.',
},
{
path: 'anilist.characterDictionary.collapsibleSections.voicedBy',
kind: 'boolean',
defaultValue: defaultConfig.anilist.characterDictionary.collapsibleSections.voicedBy,
description:
'Open the Voiced by section by default in character dictionary glossary entries.',
},
{
path: 'jellyfin.enabled',
kind: 'boolean',

View File

@@ -47,6 +47,20 @@ export function buildSubtitleConfigOptionRegistry(
defaultValue: defaultConfig.subtitleStyle.hoverTokenBackgroundColor,
description: 'CSS color used for hovered subtitle token background highlight in mpv.',
},
{
path: 'subtitleStyle.nameMatchEnabled',
kind: 'boolean',
defaultValue: defaultConfig.subtitleStyle.nameMatchEnabled,
description:
'Enable subtitle token coloring for matches from the SubMiner character dictionary.',
},
{
path: 'subtitleStyle.nameMatchColor',
kind: 'string',
defaultValue: defaultConfig.subtitleStyle.nameMatchColor,
description:
'Hex color used when a subtitle token matches an entry from the SubMiner character dictionary.',
},
{
path: 'subtitleStyle.frequencyDictionary.enabled',
kind: 'boolean',

View File

@@ -10,7 +10,7 @@ const CORE_TEMPLATE_SECTIONS: ConfigTemplateSection[] = [
},
{
title: 'Texthooker Server',
description: ['Control whether browser opens automatically for texthooker.'],
description: ['Configure texthooker startup launch and browser opening behavior.'],
key: 'texthooker',
},
{
@@ -21,6 +21,14 @@ const CORE_TEMPLATE_SECTIONS: ConfigTemplateSection[] = [
],
key: 'websocket',
},
{
title: 'Annotation WebSocket',
description: [
'Dedicated annotated subtitle websocket for bundled texthooker and token-aware clients.',
'Independent from websocket.auto and defaults to port 6678.',
],
key: 'annotationWebsocket',
},
{
title: 'Logging',
description: ['Controls logging verbosity.', 'Set to debug for full runtime diagnostics.'],
@@ -104,7 +112,11 @@ const INTEGRATION_TEMPLATE_SECTIONS: ConfigTemplateSection[] = [
},
{
title: 'Anilist',
description: ['Anilist API credentials and update behavior.'],
description: [
'Anilist API credentials and update behavior.',
'Includes optional auto-sync for a merged MRU-based character dictionary in bundled Yomitan.',
'Character dictionaries are keyed by AniList media ID (no season/franchise merge).',
],
key: 'anilist',
},
{