diff --git a/README.md b/README.md index fea1ca1..e7ec356 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,8 @@ This plugin analyzes video files to determine if they are anime content. Detecti | Field | Description | |-------|-------------| | `anime` | Boolean indicating if content is anime | -| `titles/jpn` | Japanese title (if detected) | -| `titles/rom` | Romanized title | +| `titles/jpn/{name}` | `"true"` — Japanese title (if detected) | +| `titles/jpl/{name}` | `"true"` — romaji title | | `genres` | Adds "Anime" to genres set | ## Dependencies diff --git a/src/plugin.ts b/src/plugin.ts index b4e9574..d0da6a5 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -8,8 +8,8 @@ * - "anime" in file path * * Matches old AnimeDetector output: - * - titles/jpn (if kana) - * - titles/{romajiIsoCode} (if romaji) + * - titles/jpn/ (if kana) — key-set member, METADATA_KEYS.md §3 + * - titles/{romajiIsoCode}/ (if romaji) * - anime * - genres (add "Anime") */ @@ -22,6 +22,16 @@ import { MetaCoreClient } from './meta-core-client.js'; // romajiIsoCode from @metazla/meta-interface is just 'jpl' const romajiIsoCode = 'jpl'; +/** + * `titles//` key-set member key (METADATA_KEYS.md §3): trimmed, + * whitespace collapsed, `/` (the key-set separator) written as U+2215 `∕`. + * `undefined` when nothing is left to name. + */ +export function titleMemberKey(lang3: string, name: string | undefined | null): string | undefined { + const clean = (name ?? '').trim().replace(/\s+/g, ' ').replace(/\//g, '\u2215'); + return clean ? `titles/${lang3}/${clean}` : undefined; +} + export const manifest: PluginManifest = { id: 'anime-detector', name: 'Anime Detector', @@ -35,8 +45,9 @@ export const manifest: PluginManifest = { timeout: 30000, schema: { anime: { label: 'Is Anime', type: 'boolean', readonly: true }, - 'titles/jpn': { label: 'Japanese Title', type: 'string' }, - [`titles/${romajiIsoCode}`]: { label: 'Romaji Title', type: 'string' }, + // Key-set members `titles// = "true"` (METADATA_KEYS.md §3). + 'titles/jpn/*': { label: 'Japanese Titles', type: 'boolean', readonly: true }, + [`titles/${romajiIsoCode}/*`]: { label: 'Romaji Titles', type: 'boolean', readonly: true }, }, config: {}, }; @@ -110,10 +121,10 @@ export async function process( // Set Japanese title appropriately if (isJpn && originalTitle) { - if (isKana(originalTitle)) { - await metaCore.setProperty(cid, 'titles/jpn', originalTitle); - } else { - await metaCore.setProperty(cid, `titles/${romajiIsoCode}`, originalTitle); + const lang3 = isKana(originalTitle) ? 'jpn' : romajiIsoCode; + const key = titleMemberKey(lang3, originalTitle); + if (key) { + await metaCore.mergeMetadata(cid, { [key]: 'true' }); } } diff --git a/test/integration.test.ts b/test/integration.test.ts index d0207f9..4adc62f 100644 --- a/test/integration.test.ts +++ b/test/integration.test.ts @@ -12,6 +12,7 @@ import { animeKeywords } from '@metazla/filename-tools'; // Dynamic import of plugin module let manifest: typeof import('../src/plugin.js').manifest; let processFile: typeof import('../src/plugin.js').process; +let titleMemberKey: typeof import('../src/plugin.js').titleMemberKey; // Mock callback collector interface CallbackResult { @@ -33,6 +34,7 @@ describe('Anime Detector Plugin Integration Tests', () => { const plugin = await import('../src/plugin.js'); manifest = plugin.manifest; processFile = plugin.process; + titleMemberKey = plugin.titleMemberKey; }); describe('Manifest', () => { @@ -48,7 +50,14 @@ describe('Anime Detector Plugin Integration Tests', () => { it('declares correct schema', () => { expect(manifest.schema).toHaveProperty('anime'); - expect(manifest.schema).toHaveProperty('titles/jpn'); + expect(manifest.schema).toHaveProperty(['titles/jpn/*']); + }); + + it('builds titles key-set member keys', () => { + expect(titleMemberKey('jpn', ' ナルト ')).toBe('titles/jpn/ナルト'); + expect(titleMemberKey('eng', 'Fate/Zero')).toBe('titles/eng/Fate\u2215Zero'); + expect(titleMemberKey('jpl', 'Sousou no\tFrieren')).toBe('titles/jpl/Sousou no Frieren'); + expect(titleMemberKey('jpn', ' ')).toBeUndefined(); }); });