mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-06-11 03:13:32 -07:00
fix(anki): write sentence card audio only to sentence audio field (#118)
This commit is contained in:
@@ -0,0 +1,4 @@
|
|||||||
|
type: fixed
|
||||||
|
area: anki
|
||||||
|
|
||||||
|
- Sentence-card mining now writes generated audio only to the configured sentence audio field instead of also filling expression audio.
|
||||||
+1
-1
@@ -72,7 +72,7 @@
|
|||||||
"test:launcher": "bun run test:launcher:src",
|
"test:launcher": "bun run test:launcher:src",
|
||||||
"test:core": "bun run test:core:src",
|
"test:core": "bun run test:core:src",
|
||||||
"test:subtitle": "bun run test:subtitle:src",
|
"test:subtitle": "bun run test:subtitle:src",
|
||||||
"test:fast": "bun run test:config:src && bun run test:core:src && bun run test:docs:kb && bun test src/main-entry-runtime.test.ts src/anki-integration.test.ts src/anki-integration/card-creation-manual-update.test.ts src/anki-integration/anki-connect-proxy.test.ts src/anki-integration/field-grouping-workflow.test.ts src/anki-integration/field-grouping.test.ts src/anki-integration/field-grouping-merge.test.ts src/release-workflow.test.ts src/prerelease-workflow.test.ts src/ci-workflow.test.ts scripts/docs-versioning.test.ts scripts/docs-versioned-assets.test.ts scripts/build-changelog.test.ts scripts/electron-builder-after-pack.test.ts scripts/get-mpv-window-macos.test.ts scripts/prepare-build-assets.test.ts scripts/mkv-to-readme-video.test.ts scripts/run-coverage-lane.test.ts scripts/update-aur-package.test.ts && bun test src/core/services/immersion-tracker/__tests__/query.test.ts src/core/services/immersion-tracker/__tests__/query-split-modules.test.ts && bun run tsc && bun test dist/main/runtime/registry.test.js",
|
"test:fast": "bun run test:config:src && bun run test:core:src && bun run test:docs:kb && bun test src/main-entry-runtime.test.ts src/anki-integration.test.ts src/anki-integration/card-creation-manual-update.test.ts src/anki-integration/card-creation-sentence-media.test.ts src/anki-integration/anki-connect-proxy.test.ts src/anki-integration/field-grouping-workflow.test.ts src/anki-integration/field-grouping.test.ts src/anki-integration/field-grouping-merge.test.ts src/release-workflow.test.ts src/prerelease-workflow.test.ts src/ci-workflow.test.ts scripts/docs-versioning.test.ts scripts/docs-versioned-assets.test.ts scripts/build-changelog.test.ts scripts/electron-builder-after-pack.test.ts scripts/get-mpv-window-macos.test.ts scripts/prepare-build-assets.test.ts scripts/mkv-to-readme-video.test.ts scripts/run-coverage-lane.test.ts scripts/update-aur-package.test.ts && bun test src/core/services/immersion-tracker/__tests__/query.test.ts src/core/services/immersion-tracker/__tests__/query-split-modules.test.ts && bun run tsc && bun test dist/main/runtime/registry.test.js",
|
||||||
"generate:config-example": "bun run src/generate-config-example.ts",
|
"generate:config-example": "bun run src/generate-config-example.ts",
|
||||||
"verify:config-example": "bun run src/verify-config-example.ts",
|
"verify:config-example": "bun run src/verify-config-example.ts",
|
||||||
"start": "bun run build && electron . --start",
|
"start": "bun run build && electron . --start",
|
||||||
|
|||||||
@@ -0,0 +1,130 @@
|
|||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import test from 'node:test';
|
||||||
|
|
||||||
|
import { CardCreationService } from './card-creation';
|
||||||
|
import type { AnkiConnectConfig } from '../types/anki';
|
||||||
|
|
||||||
|
type CardCreationDeps = ConstructorParameters<typeof CardCreationService>[0];
|
||||||
|
|
||||||
|
test('sentence card writes generated audio only to sentence audio field', async () => {
|
||||||
|
const addedFields: Record<string, string>[] = [];
|
||||||
|
const updatedFields: Record<string, string>[] = [];
|
||||||
|
const storedMedia: string[] = [];
|
||||||
|
|
||||||
|
const deps: CardCreationDeps = {
|
||||||
|
getConfig: () =>
|
||||||
|
({
|
||||||
|
deck: 'Mining',
|
||||||
|
fields: {
|
||||||
|
word: 'Expression',
|
||||||
|
sentence: 'Sentence',
|
||||||
|
audio: 'ExpressionAudio',
|
||||||
|
translation: 'SelectionText',
|
||||||
|
},
|
||||||
|
media: {
|
||||||
|
generateAudio: true,
|
||||||
|
generateImage: false,
|
||||||
|
maxMediaDuration: 30,
|
||||||
|
},
|
||||||
|
behavior: {},
|
||||||
|
ai: false,
|
||||||
|
}) as AnkiConnectConfig,
|
||||||
|
getAiConfig: () => ({}),
|
||||||
|
getTimingTracker: () => ({}) as never,
|
||||||
|
getMpvClient: () =>
|
||||||
|
({
|
||||||
|
currentVideoPath: '/video.mp4',
|
||||||
|
currentSubText: '字幕',
|
||||||
|
currentSubStart: 12,
|
||||||
|
currentSubEnd: 14,
|
||||||
|
currentTimePos: 13,
|
||||||
|
currentAudioStreamIndex: 0,
|
||||||
|
}) as never,
|
||||||
|
client: {
|
||||||
|
addNote: async (_deck, _modelName, fields) => {
|
||||||
|
addedFields.push(fields);
|
||||||
|
return 42;
|
||||||
|
},
|
||||||
|
addTags: async () => undefined,
|
||||||
|
notesInfo: async () => [
|
||||||
|
{
|
||||||
|
noteId: 42,
|
||||||
|
fields: {
|
||||||
|
Expression: { value: '字幕' },
|
||||||
|
Sentence: { value: '字幕' },
|
||||||
|
SelectionText: { value: 'Subtitle' },
|
||||||
|
ExpressionAudio: { value: '' },
|
||||||
|
SentenceAudio: { value: '' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
updateNoteFields: async (_noteId, fields) => {
|
||||||
|
updatedFields.push(fields);
|
||||||
|
},
|
||||||
|
storeMediaFile: async (filename) => {
|
||||||
|
storedMedia.push(filename);
|
||||||
|
},
|
||||||
|
findNotes: async () => [],
|
||||||
|
retrieveMediaFile: async () => '',
|
||||||
|
},
|
||||||
|
mediaGenerator: {
|
||||||
|
generateAudio: async () => Buffer.from('audio'),
|
||||||
|
generateScreenshot: async () => null,
|
||||||
|
generateAnimatedImage: async () => null,
|
||||||
|
},
|
||||||
|
showOsdNotification: () => undefined,
|
||||||
|
showUpdateResult: () => undefined,
|
||||||
|
showStatusNotification: () => undefined,
|
||||||
|
showNotification: async () => undefined,
|
||||||
|
beginUpdateProgress: () => undefined,
|
||||||
|
endUpdateProgress: () => undefined,
|
||||||
|
withUpdateProgress: async (_message, action) => action(),
|
||||||
|
resolveConfiguredFieldName: (noteInfo, ...preferredNames) => {
|
||||||
|
for (const preferredName of preferredNames) {
|
||||||
|
if (preferredName && preferredName in noteInfo.fields) return preferredName;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
resolveNoteFieldName: (noteInfo, preferredName) =>
|
||||||
|
preferredName && preferredName in noteInfo.fields ? preferredName : null,
|
||||||
|
getAnimatedImageLeadInSeconds: async () => 0,
|
||||||
|
extractFields: () => ({}),
|
||||||
|
processSentence: (sentence) => sentence,
|
||||||
|
setCardTypeFields: () => undefined,
|
||||||
|
mergeFieldValue: (_existing, newValue) => newValue,
|
||||||
|
formatMiscInfoPattern: () => '',
|
||||||
|
getEffectiveSentenceCardConfig: () => ({
|
||||||
|
model: 'Sentence',
|
||||||
|
sentenceField: 'Sentence',
|
||||||
|
audioField: 'SentenceAudio',
|
||||||
|
lapisEnabled: true,
|
||||||
|
kikuEnabled: false,
|
||||||
|
kikuFieldGrouping: 'disabled',
|
||||||
|
kikuDeleteDuplicateInAuto: false,
|
||||||
|
}),
|
||||||
|
getFallbackDurationSeconds: () => 10,
|
||||||
|
appendKnownWordsFromNoteInfo: () => undefined,
|
||||||
|
isUpdateInProgress: () => false,
|
||||||
|
setUpdateInProgress: () => undefined,
|
||||||
|
trackLastAddedNoteId: () => undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
const created = await new CardCreationService(deps).createSentenceCard(
|
||||||
|
'字幕',
|
||||||
|
12,
|
||||||
|
14,
|
||||||
|
'Subtitle',
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(created, true);
|
||||||
|
assert.deepEqual(addedFields[0], {
|
||||||
|
Sentence: '字幕',
|
||||||
|
SelectionText: 'Subtitle',
|
||||||
|
IsSentenceCard: 'x',
|
||||||
|
Expression: '字幕',
|
||||||
|
});
|
||||||
|
assert.equal(storedMedia.length, 1);
|
||||||
|
const mediaUpdate = updatedFields.find((fields) => 'SentenceAudio' in fields);
|
||||||
|
assert.equal(mediaUpdate?.SentenceAudio, `[sound:${storedMedia[0]}]`);
|
||||||
|
assert.equal('ExpressionAudio' in mediaUpdate!, false);
|
||||||
|
});
|
||||||
@@ -528,7 +528,6 @@ export class CardCreationService {
|
|||||||
const translationField = this.deps.getConfig().fields?.translation || 'SelectionText';
|
const translationField = this.deps.getConfig().fields?.translation || 'SelectionText';
|
||||||
let resolvedMiscInfoField: string | null = null;
|
let resolvedMiscInfoField: string | null = null;
|
||||||
let resolvedSentenceAudioField: string = audioFieldName;
|
let resolvedSentenceAudioField: string = audioFieldName;
|
||||||
let resolvedExpressionAudioField: string | null = null;
|
|
||||||
|
|
||||||
fields[sentenceField] = sentence;
|
fields[sentenceField] = sentence;
|
||||||
|
|
||||||
@@ -626,10 +625,6 @@ export class CardCreationService {
|
|||||||
this.deps.appendKnownWordsFromNoteInfo(createdNoteInfo);
|
this.deps.appendKnownWordsFromNoteInfo(createdNoteInfo);
|
||||||
resolvedSentenceAudioField =
|
resolvedSentenceAudioField =
|
||||||
this.deps.resolveNoteFieldName(createdNoteInfo, audioFieldName) || audioFieldName;
|
this.deps.resolveNoteFieldName(createdNoteInfo, audioFieldName) || audioFieldName;
|
||||||
resolvedExpressionAudioField = this.deps.resolveConfiguredFieldName(
|
|
||||||
createdNoteInfo,
|
|
||||||
this.deps.getConfig().fields?.audio || 'ExpressionAudio',
|
|
||||||
);
|
|
||||||
resolvedMiscInfoField = this.deps.resolveConfiguredFieldName(
|
resolvedMiscInfoField = this.deps.resolveConfiguredFieldName(
|
||||||
createdNoteInfo,
|
createdNoteInfo,
|
||||||
this.deps.getConfig().fields?.miscInfo,
|
this.deps.getConfig().fields?.miscInfo,
|
||||||
@@ -662,12 +657,6 @@ export class CardCreationService {
|
|||||||
await this.deps.client.storeMediaFile(audioFilename, audioBuffer);
|
await this.deps.client.storeMediaFile(audioFilename, audioBuffer);
|
||||||
const audioValue = `[sound:${audioFilename}]`;
|
const audioValue = `[sound:${audioFilename}]`;
|
||||||
mediaFields[resolvedSentenceAudioField] = audioValue;
|
mediaFields[resolvedSentenceAudioField] = audioValue;
|
||||||
if (
|
|
||||||
resolvedExpressionAudioField &&
|
|
||||||
resolvedExpressionAudioField !== resolvedSentenceAudioField
|
|
||||||
) {
|
|
||||||
mediaFields[resolvedExpressionAudioField] = audioValue;
|
|
||||||
}
|
|
||||||
miscInfoFilename = audioFilename;
|
miscInfoFilename = audioFilename;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user