diff --git a/changes/fix-anki-configured-word-fields.md b/changes/fix-anki-configured-word-fields.md new file mode 100644 index 00000000..a982ccc5 --- /dev/null +++ b/changes/fix-anki-configured-word-fields.md @@ -0,0 +1,4 @@ +type: fixed +area: anki + +- Word-card enrichment now always writes sentence text and audio to the configured AnkiConnect fields, while dedicated Lapis/Kiku sentence-card and audio-card actions keep their compatible field names. diff --git a/docs-site/anki-integration.md b/docs-site/anki-integration.md index d64c8c02..59b39946 100644 --- a/docs-site/anki-integration.md +++ b/docs-site/anki-integration.md @@ -136,6 +136,8 @@ SubMiner maps its data to your Anki note fields. Configure these under `ankiConn Field names are matched against your Anki note type case-insensitively (an exact match wins, then a lowercase comparison). If a configured field does not exist on the note type, SubMiner skips it without error. +These mappings always control normal word-card enrichment, including Yomitan proxy/polling updates and manual clipboard updates. Enabling Lapis or Kiku does not replace the configured word-card sentence and audio fields with `Sentence` and `SentenceAudio`. The dedicated sentence-card and audio-card shortcuts still use those Lapis/Kiku field names. + Two related options live alongside `fields`: `ankiConnect.deck` (target deck; empty falls back as described above) and `ankiConnect.tags` (tags added to mined cards, default `["SubMiner"]`; set `[]` to disable tagging). The `miscInfo` content is controlled by `ankiConnect.metadata.pattern` (default `[SubMiner] %f (%t)`; tokens: `%f` filename, `%F` filename with extension, `%t` timestamp, `%T` timestamp with milliseconds, `
` newline). ### Minimal Config @@ -233,7 +235,7 @@ Animated AVIF requires an AV1 encoder (`libaom-av1`, `libsvtav1`, or `librav1e`) When media is available, mined-card overlay and system notifications include the same current-frame thumbnail. -`overwriteAudio` applies to automatic card updates and duplicate-card enrichment. Manual clipboard subtitle updates (`Ctrl/Cmd+C`, then `Ctrl/Cmd+V`) always replace generated sentence audio, while leaving the word audio field unchanged. +`overwriteAudio` applies to automatic card updates and duplicate-card enrichment. Manual clipboard subtitle updates (`Ctrl/Cmd+C`, then `Ctrl/Cmd+V`) always replace generated sentence audio in `ankiConnect.fields.audio`, even when `overwriteAudio` is disabled. ## AI Translation @@ -287,6 +289,8 @@ Sentence card creation and audio card marking require a non-empty `ankiConnect.i Trigger with the mine sentence shortcut (`Ctrl/Cmd+S` by default). The card is created directly via AnkiConnect with the sentence, audio, and image filled in. +The dedicated sentence-card and audio-card shortcuts use the Lapis/Kiku-compatible `Sentence` and `SentenceAudio` fields. This does not affect the configured fields used to enrich normal word cards. + To mine multiple subtitle lines as one sentence card, use `Ctrl/Cmd+Shift+S` followed by a digit (1–9) to select how many recent lines to combine. ## Word Card Type (Kiku/Lapis) diff --git a/docs-site/mining-workflow.md b/docs-site/mining-workflow.md index ea0d9d42..20470598 100644 --- a/docs-site/mining-workflow.md +++ b/docs-site/mining-workflow.md @@ -41,7 +41,7 @@ If you prefer a hands-on approach (animecards-style), you can copy the current s - For multiple lines: press `Ctrl/Cmd+Shift+C`, then a digit `1`–`9` to select how many recent subtitle lines to combine. The combined text is copied to the clipboard. 3. Press `Ctrl/Cmd+V` to update the last-added card with the clipboard contents plus audio, image, and translation - the same fields auto-update would fill. -Manual clipboard updates always replace generated sentence audio, even when `ankiConnect.behavior.overwriteAudio` is disabled. The word audio field is left unchanged because the word itself does not change in this flow. +Manual clipboard updates always replace generated sentence audio in `ankiConnect.fields.audio`, even when `ankiConnect.behavior.overwriteAudio` is disabled. Normal word-card updates use the configured sentence and audio fields even when Lapis or Kiku support is enabled. This is useful when auto-update is disabled or when you want explicit control over which subtitle line gets attached to the card. diff --git a/src/anki-integration.test.ts b/src/anki-integration.test.ts index 5fae3673..2971fb37 100644 --- a/src/anki-integration.test.ts +++ b/src/anki-integration.test.ts @@ -608,6 +608,7 @@ test('AnkiIntegration applies ready YouTube cache media to every queued note id' const integration = new AnkiIntegration( { fields: { + audio: 'ExpressionAudio', image: 'Picture', }, media: { @@ -661,7 +662,7 @@ test('AnkiIntegration applies ready YouTube cache media to every queued note id' noteIds.map((noteId) => ({ noteId, fields: { - SentenceAudio: { value: '' }, + ExpressionAudio: { value: '' }, Picture: { value: '' }, }, })), @@ -946,7 +947,7 @@ test('AnkiIntegration queues YouTube media updates against recovered source URLs noteInfo: { noteId: 404, fields: { - SentenceAudio: { value: '' }, + ExpressionAudio: { value: '' }, Picture: { value: '' }, }, }, @@ -958,7 +959,8 @@ test('AnkiIntegration queues YouTube media updates against recovered source URLs assert.equal(queued, true); assert.equal(updatedNotes.length, 1); assert.equal(updatedNotes[0]?.noteId, 404); - assert.match(updatedNotes[0]?.fields.SentenceAudio ?? '', /^\[sound:audio_/); + assert.match(updatedNotes[0]?.fields.ExpressionAudio ?? '', /^\[sound:audio_/); + assert.equal(updatedNotes[0]?.fields.SentenceAudio, undefined); assert.match(updatedNotes[0]?.fields.Picture ?? '', /^$/); }); diff --git a/src/anki-integration/pending-youtube-media-queue.ts b/src/anki-integration/pending-youtube-media-queue.ts index 68abd2c9..0cd09195 100644 --- a/src/anki-integration/pending-youtube-media-queue.ts +++ b/src/anki-integration/pending-youtube-media-queue.ts @@ -39,7 +39,6 @@ export interface PendingYoutubeMediaQueueDeps { startTime: number; endTime: number; }; - getResolvedSentenceAudioFieldName: (noteInfo: PendingYoutubeMediaNoteInfo) => string | null; resolveConfiguredFieldName: ( noteInfo: PendingYoutubeMediaNoteInfo, ...preferredNames: (string | undefined)[] @@ -136,7 +135,7 @@ export class PendingYoutubeMediaQueue { startTime: mediaRange.startTime, endTime: mediaRange.endTime, label: job.label, - audioFieldName: this.deps.getResolvedSentenceAudioFieldName(job.noteInfo) ?? undefined, + audioFieldName: this.resolveConfiguredAudioFieldName(job.noteInfo) ?? undefined, imageFieldName: this.deps.resolveConfiguredFieldName( job.noteInfo, @@ -247,6 +246,14 @@ export class PendingYoutubeMediaQueue { return matched; } + private resolveConfiguredAudioFieldName(noteInfo: PendingYoutubeMediaNoteInfo): string | null { + const config = this.deps.getConfig(); + return this.deps.resolveConfiguredFieldName( + noteInfo, + config.fields?.audio ?? DEFAULT_ANKI_CONNECT_CONFIG.fields.audio, + ); + } + private async applyUpdate( job: PendingYoutubeMediaUpdate, cachedPath: string, @@ -283,7 +290,7 @@ export class PendingYoutubeMediaQueue { if (audioBuffer) { await this.deps.client.storeMediaFile(audioFilename, audioBuffer); const audioField = - job.audioFieldName || this.deps.getResolvedSentenceAudioFieldName(noteInfo) || null; + job.audioFieldName || this.resolveConfiguredAudioFieldName(noteInfo) || null; if (audioField) { const existingAudio = noteInfo.fields[audioField]?.value || ''; mediaFields[audioField] = this.deps.mergeFieldValue(