From 0ac862c681fb43a992ec641c945bfa3cca77bc13 Mon Sep 17 00:00:00 2001 From: Worph Date: Mon, 14 Sep 2026 22:29:50 +0200 Subject: [PATCH] improve search and fix languages --- src/plugin.ts | 23 +++++++++++++++++++++-- test/subtitle.test.ts | 11 +++++++++-- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/plugin.ts b/src/plugin.ts index d0193d5..137303c 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -242,9 +242,22 @@ export async function midhashOverWebDAV(client: WebDAVClient, filePath: string, // Record keys // ---------------------------------------------------------------------------- +/** + * Keys on the video record for one linked subtitle (METADATA_KEYS.md §8, §9). + * + * The language lands in `subtitleLanguages/` **and** in the + * `languages/` union — §9 rule #7: "a union field is written, never + * computed". Nothing reconciles them afterwards, and `languages` is the only + * field a query filters on, so a subtitle language recorded in the split alone + * is invisible to every language filter on every peer. This mirrors + * subtitle-extractor's `videoSubtitleKeys`, which has always written both. + */ export function videoSubtitleKeys(subCid: string, lang3: string): Record { const keys: Record = { [`subtitles/${lang3}/${subCid}`]: 'true' }; - if (lang3 !== 'und') keys[`subtitleLanguages/${lang3}`] = 'true'; + if (lang3 !== 'und') { + keys[`subtitleLanguages/${lang3}`] = 'true'; + keys[`languages/${lang3}`] = 'true'; + } return keys; } @@ -299,7 +312,13 @@ async function writeVideoLinks( deletes.push('subtitleLanguages'); for (const code of legacyLangs) { const l = languageToken(code); - if (l && l !== 'und') sets[`subtitleLanguages/${l}`] = 'true'; + if (l && l !== 'und') { + sets[`subtitleLanguages/${l}`] = 'true'; + // The legacy csv never had a union member to migrate, so the + // migration is also the only chance to satisfy §9 rule #7 for + // these records. + sets[`languages/${l}`] = 'true'; + } } } for (const { subCid, lang3 } of links) deletes.push(...staleLanguageLeaves(flat, 'subtitles', subCid, lang3)); diff --git a/test/subtitle.test.ts b/test/subtitle.test.ts index 41d7d7e..6aca9bd 100644 --- a/test/subtitle.test.ts +++ b/test/subtitle.test.ts @@ -220,8 +220,15 @@ describe('midhash256', () => { }); describe('record keys (METADATA_KEYS.md §8)', () => { - it('video: language-nested leaf, plus the facet for a known language', () => { - expect(videoSubtitleKeys('bagsub', 'fre')).toEqual({ 'subtitles/fre/bagsub': 'true', 'subtitleLanguages/fre': 'true' }); + it('video: language-nested leaf, plus the facet AND the union for a known language', () => { + // §9 rule #7 — the split never travels without the union, because + // `languages` is the only field a query filters on. + expect(videoSubtitleKeys('bagsub', 'fre')).toEqual({ + 'subtitles/fre/bagsub': 'true', + 'subtitleLanguages/fre': 'true', + 'languages/fre': 'true', + }); + // `und` is never a member of either set (§9). expect(videoSubtitleKeys('bagsub', 'und')).toEqual({ 'subtitles/und/bagsub': 'true' }); });