summaryrefslogtreecommitdiff
path: root/internal/processor
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-04-02 08:16:57 +0300
committerPaul Buetow <paul@buetow.org>2026-04-02 08:16:57 +0300
commite905d0d7435a827825017c5612b6e61d1f1f5da7 (patch)
tree26d9bef15305895c0b1703c629f042800367fb04 /internal/processor
parent0349a83743f8b8c8125a35b93b2271e402352b99 (diff)
Centralize audio sidecar generation
Diffstat (limited to 'internal/processor')
-rw-r--r--internal/processor/processor.go61
-rw-r--r--internal/processor/processor_test.go36
2 files changed, 50 insertions, 47 deletions
diff --git a/internal/processor/processor.go b/internal/processor/processor.go
index 28b2352..92e7b29 100644
--- a/internal/processor/processor.go
+++ b/internal/processor/processor.go
@@ -963,13 +963,7 @@ func (p *Processor) isWordFullyProcessed(word string) bool {
return true // All required files exist
}
func (p *Processor) saveAudioAttribution(word, audioFile string, config *audio.Config) error {
- // Add preprocessing information
- cleanedWord := strings.TrimSpace(word)
- punctuationToRemove := []string{"!", "?", ".", ",", ";", ":", "\"", "'", "(", ")", "[", "]", "{", "}", "-", "—", "–"}
- for _, punct := range punctuationToRemove {
- cleanedWord = strings.ReplaceAll(cleanedWord, punct, "")
- }
- processedText := fmt.Sprintf("%s...", strings.TrimSpace(cleanedWord))
+ processedText := audio.ProcessedTextForWord(word)
var attribution string
switch strings.ToLower(strings.TrimSpace(config.Provider)) {
case "gemini":
@@ -1012,47 +1006,20 @@ func (p *Processor) saveAudioAttribution(word, audioFile string, config *audio.C
}
func (p *Processor) buildAudioMetadata(config *audio.Config, audioFile string) string {
- var b strings.Builder
- provider := strings.ToLower(strings.TrimSpace(config.Provider))
- if provider == "" {
- provider = "openai"
- }
-
- fmt.Fprintf(&b, "provider=%s\n", provider)
- switch provider {
- case "gemini":
- fmt.Fprintf(&b, "model=%s\n", config.GeminiTTSModel)
- voice := strings.TrimSpace(config.GeminiVoice)
- if voice == "" {
- voice = "model-default"
- }
- fmt.Fprintf(&b, "voice=%s\n", voice)
- fmt.Fprintf(&b, "speed=%.2f\n", config.GeminiSpeed)
- default:
- fmt.Fprintf(&b, "model=%s\n", config.OpenAIModel)
- voice := strings.TrimSpace(config.OpenAIVoice)
- if voice != "" {
- fmt.Fprintf(&b, "voice=%s\n", voice)
- }
- fmt.Fprintf(&b, "speed=%.2f\n", config.OpenAISpeed)
- if instruction := strings.TrimSpace(config.OpenAIInstruction); instruction != "" {
- fmt.Fprintf(&b, "instruction=%s\n", instruction)
- }
- }
- format := strings.TrimSpace(config.OutputFormat)
- if format == "" {
- format = p.effectiveAudioFormat()
- }
- fmt.Fprintf(&b, "format=%s\n", format)
audioFileHint, audioFileBackHint := p.audioMetadataFileHints(audioFile)
- if audioFileHint != "" {
- fmt.Fprintf(&b, "audio_file=%s\n", filepath.Base(audioFileHint))
- }
- if audioFileBackHint != "" {
- fmt.Fprintf(&b, "audio_file_back=%s\n", filepath.Base(audioFileBackHint))
- }
-
- return b.String()
+ return audio.BuildSidecarMetadata(audio.SidecarMetadataParams{
+ Provider: config.Provider,
+ OutputFormat: config.OutputFormat,
+ AudioFile: audioFileHint,
+ AudioFileBack: audioFileBackHint,
+ OpenAIModel: config.OpenAIModel,
+ OpenAIVoice: config.OpenAIVoice,
+ OpenAISpeed: config.OpenAISpeed,
+ OpenAIInstruction: config.OpenAIInstruction,
+ GeminiTTSModel: config.GeminiTTSModel,
+ GeminiVoice: config.GeminiVoice,
+ GeminiSpeed: config.GeminiSpeed,
+ })
}
func (p *Processor) audioMetadataFileHints(audioFile string) (string, string) {
diff --git a/internal/processor/processor_test.go b/internal/processor/processor_test.go
index f658713..69250bf 100644
--- a/internal/processor/processor_test.go
+++ b/internal/processor/processor_test.go
@@ -436,6 +436,7 @@ func TestGenerateAudioBgBgUsesSharedOpenAIVoices(t *testing.T) {
metadata := string(metadataData)
for _, want := range []string{
"provider=openai",
+ "cardtype=bg-bg",
"audio_file=audio_front.mp3",
"audio_file_back=audio_back.mp3",
} {
@@ -443,6 +444,19 @@ func TestGenerateAudioBgBgUsesSharedOpenAIVoices(t *testing.T) {
t.Fatalf("metadata = %q, missing %q", metadata, want)
}
}
+
+ for i, outputFile := range fakeProvider.outputFiles {
+ attrPath := audio.AttributionPath(outputFile)
+ attributionData, err := os.ReadFile(attrPath)
+ if err != nil {
+ t.Fatalf("expected attribution file %q: %v", attrPath, err)
+ }
+ attribution := string(attributionData)
+ wantText := []string{"ябълка...", "круша..."}[i]
+ if !strings.Contains(attribution, "Processed text sent to TTS: "+wantText) {
+ t.Fatalf("bg-bg attribution missing processed text %q: %q", wantText, attribution)
+ }
+ }
}
func TestGenerateAudioProviderFactoryError(t *testing.T) {
@@ -548,11 +562,22 @@ func TestGenerateAudioUsesConfiguredGeminiVoiceAndModel(t *testing.T) {
"speed=1.00",
"format=wav",
"audio_file=audio.wav",
+ "cardtype=en-bg",
} {
if !strings.Contains(metadata, want) {
t.Fatalf("metadata = %q, missing %q", metadata, want)
}
}
+
+ attrPath := audio.AttributionPath(fakeProvider.lastOutputFile)
+ attributionData, err := os.ReadFile(attrPath)
+ if err != nil {
+ t.Fatalf("expected attribution file %q: %v", attrPath, err)
+ }
+ attribution := string(attributionData)
+ if !strings.Contains(attribution, "Processed text sent to TTS: ябълка...") {
+ t.Fatalf("gemini attribution missing processed text: %q", attribution)
+ }
}
func TestGenerateAudioUsesConfiguredAudioFormatWhenOpenAIConfigIsSetOnly(t *testing.T) {
@@ -612,11 +637,22 @@ func TestGenerateAudioUsesConfiguredAudioFormatWhenOpenAIConfigIsSetOnly(t *test
"model=gpt-4o-mini-tts",
"voice=alloy",
"format=mp3",
+ "cardtype=en-bg",
} {
if !strings.Contains(metadata, want) {
t.Fatalf("metadata = %q, missing %q", metadata, want)
}
}
+
+ attrPath := audio.AttributionPath(fakeProvider.lastOutputFile)
+ attributionData, err := os.ReadFile(attrPath)
+ if err != nil {
+ t.Fatalf("expected attribution file %q: %v", attrPath, err)
+ }
+ attribution := string(attributionData)
+ if !strings.Contains(attribution, "Processed text sent to TTS: ябълка...") {
+ t.Fatalf("openai attribution missing processed text: %q", attribution)
+ }
}
func TestGenerateAnkiFileUsesEffectiveAudioFormatForGemini(t *testing.T) {