summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-01-21 22:41:08 +0200
committerPaul Buetow <paul@buetow.org>2026-01-21 22:41:08 +0200
commit5f9f32f089fcc3cd827db4d707003acefa2a8cca (patch)
tree5b295dbc88ac8fefb9661fffd442ebc846af269f
parent962dd1f77c0b38bf5333e3328ba136ff6c456285 (diff)
Fix: Auto-play only regenerated audio (front or back, not both) for bg-bg cardsv0.8.0
- Removed duplicate fyne.KeyA handler that was triggering both front and back audio - Added SetAudioFileNoAutoPlay() method for controlled playback - Front audio (a key) now auto-plays only front audio - Back audio (A key) now auto-plays only back audio - Refactored startPlayback to use startPlaybackForFile for better control - Fixed icon reset when playback finishes for each audio type separately
-rw-r--r--PLAN.md147
-rw-r--r--TROUBLESHOOTING.md165
-rw-r--r--cmd/test_audio_regen/main.go251
-rw-r--r--internal/gui/app.go100
-rw-r--r--internal/gui/audio_player.go95
-rw-r--r--internal/gui/generator.go14
-rw-r--r--internal/gui/navigation.go35
-rw-r--r--internal/version.go2
-rw-r--r--output239
9 files changed, 699 insertions, 349 deletions
diff --git a/PLAN.md b/PLAN.md
deleted file mode 100644
index 35b7f6e..0000000
--- a/PLAN.md
+++ /dev/null
@@ -1,147 +0,0 @@
-# Plan: Bulgarian-Bulgarian Flashcard Support
-
-## Status: ✅ IMPLEMENTED
-
-All features have been implemented and tested.
-
-## Overview
-Add support for Bulgarian-Bulgarian flashcards alongside the existing English-Bulgarian mode. This enables monolingual learning where both sides of the flashcard are in Bulgarian (e.g., word and definition, or word and synonym).
-
-## Current State
-- ✅ English-Bulgarian and Bulgarian-Bulgarian flashcards are supported
-- ✅ File-based storage uses `translation.txt` and `cardtype.txt`
-- ✅ Audio is generated for both sides of bg-bg cards
-- ✅ Batch import supports: `english=bulgarian` (en-bg) and `bulgarian1==bulgarian2` (bg-bg)
-
----
-
-## 1. Internal Database/Storage Changes ✅
-
-### 1.1 Add Card Type Indicator
-**File:** `internal/cardtype.go` (NEW)
-
-- Created new `CardType` type with `CardTypeEnBg` and `CardTypeBgBg` constants
-- Added `SaveCardType()` and `LoadCardType()` functions
-- Backwards compatible: missing `cardtype.txt` defaults to `en-bg`
-
-### 1.2 Update Structs ✅
-**Files:** `internal/batch/processor.go`, `internal/anki/generator.go`, `internal/gui/queue.go`
-
-Added `CardType` field to:
-- `WordEntry` struct
-- `Card` struct (with `AudioFileBack` for bg-bg)
-- `WordJob` struct (with `AudioFileBack` and `CardType`)
-
-### 1.3 Second Audio File ✅
-**File:** `internal/processor/processor.go`, `internal/gui/generator.go`
-
-For `bg-bg` cards, stores two audio files:
-- `audio_front.mp3` - pronunciation of first Bulgarian term
-- `audio_back.mp3` - pronunciation of second Bulgarian term
-
----
-
-## 2. Audio Generation Changes ✅
-
-### 2.1 Generate Audio for Both Sides
-**Files:** `internal/processor/processor.go`, `internal/gui/generator.go`
-
-- Added `generateAudioBgBg()` function for CLI processor
-- Added `generateAudioBgBg()` function for GUI
-- Both sides use the same voice for consistency
-
-### 2.2 Update Processor ✅
-- Detects card type and calls appropriate audio generation
-- Saves to `audio_front.mp3` and `audio_back.mp3` for `bg-bg`
-
----
-
-## 3. GUI Support ✅
-
-### 3.1 Card Type Selector
-**File:** `internal/gui/app.go`
-
-Added dropdown selector:
-- "English → Bulgarian" (default)
-- "Bulgarian → Bulgarian"
-
-### 3.2 Update Input Labels ✅
-When `bg-bg` is selected:
-- Translation placeholder changes to "Bulgarian definition..."
-
-### 3.3 Audio Preview ✅
-**File:** `internal/gui/audio_player.go`
-
-For `bg-bg` cards:
-- Added "Play Back Audio" button (skip next icon)
-- Button only visible for bg-bg cards
-
-### 3.4 Navigation Updates ✅
-**File:** `internal/gui/navigation.go`
-
-- Loads card type when navigating to existing cards
-- Updates card type selector based on loaded card
-- Loads both front and back audio for bg-bg cards
-
----
-
-## 4. Batch Importer Support ✅
-
-### 4.1 Input Format Detection
-**File:** `internal/batch/processor.go`
-
-Detects card type from line format:
-- `bulgarian1==bulgarian2` → `bg-bg` mode (double equals)
-- `english=bulgarian` or `bulgarian=english` → `en-bg` mode (single equals)
-
-### 4.2 Parsing Logic ✅
-```
-Line contains "==" → Split on "==" → bg-bg card
-Line contains "=" (single) → Split on "=" → en-bg card
-```
-
-### 4.3 Processing Pipeline ✅
-- Parses and detects card type per line
-- Passes card type through the processing pipeline
-- Generates appropriate audio files based on type
-
----
-
-## 5. Anki Export Changes ✅
-
-### 5.1 Update Export Format
-**File:** `internal/anki/apkg_generator.go`
-
-For `bg-bg` cards:
-- Created separate note type "Bulgarian-Bulgarian from TotalRecall"
-- Fields: BulgarianFront, BulgarianBack, Image, AudioFront, AudioBack, Notes
-- Includes both audio files in the Anki package
-
-### 5.2 Card Templates ✅
-- Forward: Shows front Bulgarian word + front audio, answer shows back + back audio
-- Reverse: Shows back Bulgarian word + back audio, answer shows front + front audio
-- Different CSS styling for front/back Bulgarian text
-
----
-
-## Test Coverage ✅
-
-Added new tests in `internal/batch/processor_test.go`:
-- `bulgarian-bulgarian_format_with_double_equals`
-- `mixed_en-bg_and_bg-bg_formats`
-
-All tests pass:
-```
-=== RUN TestReadBatchFile/bulgarian-bulgarian_format_with_double_equals
---- PASS: TestReadBatchFile/bulgarian-bulgarian_format_with_double_equals
-=== RUN TestReadBatchFile/mixed_en-bg_and_bg-bg_formats
---- PASS: TestReadBatchFile/mixed_en-bg_and_bg-bg_formats
-```
-
----
-
-## Migration/Compatibility ✅
-
-- Existing cards without `cardtype.txt` default to `en-bg`
-- No migration needed for existing data
-- Batch files can mix `en-bg` and `bg-bg` entries in the same file
diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md
deleted file mode 100644
index 2ff017c..0000000
--- a/TROUBLESHOOTING.md
+++ /dev/null
@@ -1,165 +0,0 @@
-# TotalRecall Troubleshooting Guide
-
-## Audio Regeneration Issue - Bulgarian-Bulgarian Cards
-
-### Issue Description
-
-When using keyboard shortcuts to regenerate audio on Bulgarian-Bulgarian (bg-bg) cards:
-- **Expected**: `a` regenerates front audio, `A` regenerates back audio
-- **Problem**: User reports that `A` is regenerating the "other side" instead of the back audio
-
-### What Should Happen
-
-For a bg-bg card with:
-- Front word: "котка" (cat)
-- Back definition: "домашно животно" (domestic animal)
-
-**Playback:**
-- `p` (lowercase p) should play audio_front.mp3 (pronunciation of "котка")
-- `P` (uppercase P) should play audio_back.mp3 (pronunciation of "домашно животно")
-
-**Regeneration:**
-- `a` (lowercase a) should regenerate audio_front.mp3 for "котка"
-- `A` (uppercase A) should regenerate audio_back.mp3 for "домашно животно"
-
-### Current Implementation
-
-#### File Structure (Correct as of Jan 21, 2025)
-```
-~/.local/state/totalrecall/cards/[CARD_ID]/
-├── word.txt # "котка"
-├── translation.txt # "котка = домашно животно"
-├── cardtype.txt # "bg-bg"
-├── audio_front.mp3 # Audio for "котка"
-├── audio_back.mp3 # Audio for "домашно животно"
-└── [other files...]
-```
-
-#### Code Logic
-
-**Playback** (working correctly):
-- `SetOnTypedRune()` in app.go (lines 2445-2452)
- - `p/п` → calls `a.audioPlayer.Play()` → plays `audio_front.mp3`
- - `P/П` → calls `a.audioPlayer.PlayBack()` → plays `audio_back.mp3`
-
-**Regeneration** (potentially buggy):
-- `onRegenerateAudio()` (app.go lines 1034-1125)
- - For bg-bg: calls `generateAudioFront(cardCtx, wordForGeneration, cardDir)`
- - `wordForGeneration = a.currentWord` (the front word)
- - Generates audio for the front word ✓ CORRECT
-
-- `onRegenerateBackAudio()` (app.go lines 1128-1191)
- - Extracts: `translation = a.currentTranslation`
- - Calls: `generateAudioBack(cardCtx, translation, cardDir)`
- - Should generate audio for the back definition
- - **VERIFY**: Is `a.currentTranslation` actually the back definition?
-
-### Debugging Steps
-
-#### 1. Check Card Files
-```bash
-# Navigate to an existing bg-bg card directory
-cd ~/.local/state/totalrecall/cards/[CARD_ID]/
-
-# Verify card type
-cat cardtype.txt # Should be "bg-bg"
-
-# Verify translation file format
-cat translation.txt # Should be "front_word = back_definition"
-
-# Verify audio files exist
-ls -lh audio_*.mp3 # Should see audio_front.mp3 and audio_back.mp3
-```
-
-#### 2. Check UI State When Loading Card
-When you open a bg-bg card in the GUI:
-1. Check the "Card Type" dropdown - should show "Bulgarian → Bulgarian"
-2. Check the Bulgarian input field - should show the front word
-3. Check the translation field - should show the back definition (not English!)
-4. The placeholder text should say "Bulgarian definition..." not "English translation..."
-
-#### 3. Test Playback (Working Baseline)
-Open a bg-bg card and test:
-- Press `p` (lowercase) - which audio plays? (Front word or definition?)
-- Press `P` (uppercase) - which audio plays? (Front word or definition?)
-
-#### 4. Test Regeneration
-After confirming playback works correctly:
-- Press `a` (lowercase) - which TEXT is sent for audio generation? Check console output for:
- ```
- Generating front audio for '[TEXT]'
- ```
-- Press `A` (uppercase) - which TEXT is sent for audio generation? Check console output for:
- ```
- Generating back audio for '[TEXT]'
- ```
-
-### Data Flow for `A` (Regenerate Back Audio)
-
-```
-User presses 'A'
- ↓
-SetOnTypedRune() case 'A', 'А'
- ↓
-onRegenerateBackAudio()
- ↓
-Get translation:
- - translation = a.currentTranslation
- - IF empty: translation = a.translationEntry.Text
- ↓
-generateAudioBack(cardCtx, translation, cardDir)
- ↓
-Generate audio for [translation] → audio_back.mp3
-```
-
-### Potential Issues to Investigate
-
-1. **`a.currentTranslation` not set correctly**
- - Check if `LoadExistingFiles()` properly extracts back definition from translation.txt
- - Verify the split on "=" correctly extracts the part after the equals sign
- - Confirm the value is actually the Bulgarian definition, not English
-
-2. **Translation field contains wrong value**
- - When bg-bg card loads, does `a.translationEntry.SetText()` set the back definition or something else?
- - Is the UI field showing the correct value?
-
-3. **Card type not detected correctly**
- - Confirm `cardtype.txt` exists and contains "bg-bg"
- - Verify `internal.LoadCardType()` works correctly
-
-4. **Field names confusion**
- - The `translationEntry` field is named misleadingly
- - For en-bg: it contains English translation
- - For bg-bg: it contains Bulgarian definition
- - Placeholder text should change to reflect this
-
-### Related Code Files
-
-- `internal/gui/app.go` - Hotkey handlers (lines 1034-1191 for audio, 2405-2453 for playback)
-- `internal/gui/navigation.go` - Card loading logic (lines 364-460)
-- `internal/gui/generator.go` - Audio generation functions (lines 156-220)
-- `internal/gui/audio_player.go` - Audio playback implementation
-- `internal/processor/processor.go` - Batch processing audio generation
-
-### Test Case
-
-Use this batch input to create a test card:
-```
-котка == домашно животно
-```
-
-Then in the GUI:
-1. Navigate to the "котка" card
-2. Verify UI shows correct values
-3. Press `p` and listen - should hear "котка"
-4. Press `P` and listen - should hear "домашно животно"
-5. Press `a` - check console for "Generating front audio for 'котка'"
-6. Press `A` - check console for "Generating back audio for 'домашно животно'" (NOT "котка")
-
-### Notes
-
-- Batch processing works correctly (both audio files generated in correct locations as of Jan 21, 2025)
-- GUI playback buttons work correctly and show proper labels ("Front" / "Back")
-- The issue appears to be isolated to the `A` key regeneration logic
-- Audio file generation itself is not the problem - the issue is which TEXT is being passed to the generator
-
diff --git a/cmd/test_audio_regen/main.go b/cmd/test_audio_regen/main.go
new file mode 100644
index 0000000..7f0367c
--- /dev/null
+++ b/cmd/test_audio_regen/main.go
@@ -0,0 +1,251 @@
+package main
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "time"
+)
+
+// TestState mimics the Application state for a bg-bg card
+type TestState struct {
+ currentWord string
+ currentCardType string
+ currentTranslation string
+ cardDir string
+}
+
+// SimulateKey_a simulates pressing 'a' (regenerate front audio)
+func (ts *TestState) SimulateKey_a() {
+ fmt.Println("\n" + repeatStr("=", 80))
+ fmt.Println("SIMULATING: Key pressed 'a' (regenerate front audio)")
+ fmt.Println(repeatStr("=", 80))
+
+ fmt.Printf("Conditions check:\n")
+ fmt.Printf(" - currentWord: %s\n", ts.currentWord)
+ fmt.Printf(" - currentCardType: %s\n", ts.currentCardType)
+
+ if ts.currentCardType != "bg-bg" {
+ fmt.Printf(" ✗ NOT a bg-bg card, would return\n")
+ return
+ }
+
+ fmt.Printf(" ✓ Is bg-bg card, proceeding...\n")
+
+ // Simulate generateAudioFront
+ frontFile := filepath.Join(ts.cardDir, "audio_front.mp3")
+ fmt.Printf("\nGenerating front audio for '%s'\n", ts.currentWord)
+ fmt.Printf(" Output file: %s\n", frontFile)
+
+ // Create file
+ if err := createAudioFile(frontFile, "FRONT_"+ts.currentWord); err != nil {
+ fmt.Printf(" ✗ Error: %v\n", err)
+ return
+ }
+
+ fmt.Printf(" ✓ Successfully wrote front audio\n")
+ fmt.Printf(" 📁 File: audio_front.mp3\n")
+}
+
+// SimulateKey_A simulates pressing 'A' (regenerate back audio)
+func (ts *TestState) SimulateKey_A() {
+ fmt.Println("\n" + repeatStr("=", 80))
+ fmt.Println("SIMULATING: Key pressed 'A' (regenerate back audio)")
+ fmt.Println(repeatStr("=", 80))
+
+ fmt.Printf("Conditions check:\n")
+ fmt.Printf(" - currentWord: %s\n", ts.currentWord)
+ fmt.Printf(" - currentCardType: %s\n", ts.currentCardType)
+ fmt.Printf(" - currentTranslation: %s\n", ts.currentTranslation)
+
+ if ts.currentCardType != "bg-bg" {
+ fmt.Printf(" ✗ NOT a bg-bg card, would return\n")
+ return
+ }
+
+ fmt.Printf(" ✓ Is bg-bg card, proceeding...\n")
+
+ translation := ts.currentTranslation
+ if translation == "" {
+ fmt.Printf(" ⚠ Translation empty, should fallback to UI field\n")
+ return
+ }
+
+ fmt.Printf("\nGenerating back audio for '%s'\n", translation)
+
+ // Simulate generateAudioBack
+ backFile := filepath.Join(ts.cardDir, "audio_back.mp3")
+ fmt.Printf(" Output file: %s\n", backFile)
+
+ // Create file
+ if err := createAudioFile(backFile, "BACK_"+translation); err != nil {
+ fmt.Printf(" ✗ Error: %v\n", err)
+ return
+ }
+
+ fmt.Printf(" ✓ Successfully wrote back audio\n")
+ fmt.Printf(" 📁 File: audio_back.mp3\n")
+}
+
+// createAudioFile creates a fake audio file with content marker
+func createAudioFile(path, marker string) error {
+ content := fmt.Sprintf("FAKE_AUDIO[%s]_GENERATED_AT_%d", marker, time.Now().Unix())
+ return os.WriteFile(path, []byte(content), 0644)
+}
+
+// VerifyResult checks which files were actually created/modified
+func (ts *TestState) VerifyResult() {
+ fmt.Println("\n" + repeatStr("=", 80))
+ fmt.Println("VERIFICATION: Which audio files were regenerated?")
+ fmt.Println(repeatStr("=", 80))
+
+ frontFile := filepath.Join(ts.cardDir, "audio_front.mp3")
+ backFile := filepath.Join(ts.cardDir, "audio_back.mp3")
+
+ // Check which files exist and their content
+ frontContent, _ := os.ReadFile(frontFile)
+ backContent, _ := os.ReadFile(backFile)
+
+ fmt.Printf("\nFront audio (audio_front.mp3):\n")
+ if len(frontContent) > 0 {
+ fmt.Printf(" ✓ Exists: %s\n", string(frontContent))
+ if containsString(string(frontContent), "FRONT_"+ts.currentWord) {
+ fmt.Printf(" ✓ CORRECT: Contains front word '%s'\n", ts.currentWord)
+ } else if containsString(string(frontContent), "BACK_") {
+ fmt.Printf(" ✗ WRONG: Contains back definition (should be front!)\n")
+ }
+ } else {
+ fmt.Printf(" ✗ File does not exist or is empty\n")
+ }
+
+ fmt.Printf("\nBack audio (audio_back.mp3):\n")
+ if len(backContent) > 0 {
+ fmt.Printf(" ✓ Exists: %s\n", string(backContent))
+ if containsString(string(backContent), "BACK_"+ts.currentTranslation) {
+ fmt.Printf(" ✓ CORRECT: Contains back definition '%s'\n", ts.currentTranslation)
+ } else if containsString(string(backContent), "FRONT_") {
+ fmt.Printf(" ✗ WRONG: Contains front word (should be back!)\n")
+ }
+ } else {
+ fmt.Printf(" ✗ File does not exist or is empty\n")
+ }
+}
+
+// TestSequence runs a complete test sequence
+func (ts *TestState) TestSequence() {
+ fmt.Println("\n" + "╔" + repeatStr("=", 78) + "╗")
+ fmt.Println("║" + centerText("TEST: Audio Regeneration for bg-bg Cards", 78) + "║")
+ fmt.Println("╚" + repeatStr("=", 78) + "╝")
+
+ fmt.Printf("\nInitial State:\n")
+ fmt.Printf(" Word: %s\n", ts.currentWord)
+ fmt.Printf(" Card Type: %s\n", ts.currentCardType)
+ fmt.Printf(" Translation: %s\n", ts.currentTranslation)
+ fmt.Printf(" Card Dir: %s\n", ts.cardDir)
+
+ // Simulate pressing 'a' then 'A'
+ ts.SimulateKey_a()
+ fmt.Printf("\n[Simulating 'a' completing...]\n")
+ time.Sleep(500 * time.Millisecond)
+
+ ts.SimulateKey_A()
+ fmt.Printf("\n[Simulating 'A' completing...]\n")
+ time.Sleep(500 * time.Millisecond)
+
+ // Verify results
+ ts.VerifyResult()
+
+ // Summary
+ printSummary(ts)
+}
+
+func printSummary(ts *TestState) {
+ fmt.Println("\n" + repeatStr("=", 80))
+ fmt.Println("SUMMARY")
+ fmt.Println(repeatStr("=", 80))
+
+ frontFile := filepath.Join(ts.cardDir, "audio_front.mp3")
+ backFile := filepath.Join(ts.cardDir, "audio_back.mp3")
+
+ frontContent, _ := os.ReadFile(frontFile)
+ backContent, _ := os.ReadFile(backFile)
+
+ frontCorrect := containsString(string(frontContent), "FRONT_"+ts.currentWord)
+ backCorrect := containsString(string(backContent), "BACK_"+ts.currentTranslation)
+
+ fmt.Printf("\nTest Result: ")
+ if frontCorrect && backCorrect {
+ fmt.Printf("✓ PASS - Both sides regenerated correctly!\n\n")
+ fmt.Printf(" ✓ Front audio contains: %s\n", ts.currentWord)
+ fmt.Printf(" ✓ Back audio contains: %s\n", ts.currentTranslation)
+ } else if !frontCorrect && !backCorrect {
+ fmt.Printf("✗ FAIL - Both sides regenerated incorrectly!\n\n")
+ if containsString(string(frontContent), "BACK_") {
+ fmt.Printf(" ✗ Front audio contains BACK definition (wrong!)\n")
+ }
+ if containsString(string(backContent), "FRONT_") {
+ fmt.Printf(" ✗ Back audio contains FRONT word (wrong!)\n")
+ }
+ } else if !frontCorrect {
+ fmt.Printf("✗ FAIL - Front audio is wrong!\n\n")
+ fmt.Printf(" ✗ Front audio: %s\n", string(frontContent))
+ } else if !backCorrect {
+ fmt.Printf("✗ FAIL - Back audio is wrong!\n\n")
+ fmt.Printf(" ✗ Back audio: %s\n", string(backContent))
+ }
+}
+
+func containsString(haystack, needle string) bool {
+ return len(haystack) > 0 && len(needle) > 0 && (
+ containsSubstring(haystack, needle))
+}
+
+func containsSubstring(s, substr string) bool {
+ for i := 0; i <= len(s)-len(substr); i++ {
+ if s[i:i+len(substr)] == substr {
+ return true
+ }
+ }
+ return false
+}
+
+func centerText(text string, width int) string {
+ padding := (width - len(text)) / 2
+ left := ""
+ for i := 0; i < padding; i++ {
+ left += " "
+ }
+ right := ""
+ for i := 0; i < width-len(text)-padding; i++ {
+ right += " "
+ }
+ return left + text + right
+}
+
+func repeatStr(s string, count int) string {
+ result := ""
+ for i := 0; i < count; i++ {
+ result += s
+ }
+ return result
+}
+
+func main() {
+ // Create temp directory for test
+ tmpDir := "/tmp/totalrecall_test"
+ os.MkdirAll(tmpDir, 0755)
+
+ // Test case: котка == домашно животно
+ state := &TestState{
+ currentWord: "котка",
+ currentCardType: "bg-bg",
+ currentTranslation: "домашно животно",
+ cardDir: tmpDir,
+ }
+
+ state.TestSequence()
+
+ fmt.Println("\n" + repeatStr("=", 80))
+ fmt.Printf("Test files created in: %s\n", tmpDir)
+ fmt.Println(repeatStr("=", 80) + "\n")
+}
diff --git a/internal/gui/app.go b/internal/gui/app.go
index 7d48f75..ea4d840 100644
--- a/internal/gui/app.go
+++ b/internal/gui/app.go
@@ -1032,14 +1032,23 @@ func (a *Application) onRegenerateRandomImage() {
// onRegenerateAudio regenerates front audio (or single audio for en-bg cards)
func (a *Application) onRegenerateAudio() {
+ fmt.Printf("DEBUG: ████████████████████████████████████████████████████████████████\n")
+ fmt.Printf("DEBUG: ████ ENTERED onRegenerateAudio() - REGENERATING FRONT AUDIO\n")
+ fmt.Printf("DEBUG: ████████████████████████████████████████████████████████████████\n")
+ fmt.Printf("DEBUG (onRegenerateAudio): Starting front audio regeneration\n")
+ fmt.Printf(" - currentWord: %s\n", a.currentWord)
+ fmt.Printf(" - currentCardType: %s\n", a.currentCardType)
+
// Only disable the audio-related buttons
a.regenerateAudioBtn.Disable()
a.regenerateAllBtn.Disable()
isBgBg := a.currentCardType == "bg-bg"
if isBgBg {
+ fmt.Printf("DEBUG (onRegenerateAudio): Card type is bg-bg, regenerating FRONT audio only\n")
a.showProgress("Regenerating front audio...")
} else {
+ fmt.Printf("DEBUG (onRegenerateAudio): Card type is en-bg, regenerating single audio\n")
a.showProgress("Regenerating audio...")
}
@@ -1052,6 +1061,7 @@ func (a *Application) onRegenerateAudio() {
// Store the word we're generating for
wordForGeneration := a.currentWord
+ fmt.Printf("DEBUG (onRegenerateAudio): In goroutine - wordForGeneration: %s\n", wordForGeneration)
a.startOperation(wordForGeneration)
defer a.endOperation(wordForGeneration)
@@ -1083,7 +1093,10 @@ func (a *Application) onRegenerateAudio() {
fyne.Do(func() {
a.mu.Lock()
if a.currentWord == wordForGeneration {
- a.audioPlayer.SetAudioFile(audioFile)
+ // Set front audio WITHOUT auto-play initially
+ a.audioPlayer.SetAudioFileNoAutoPlay(audioFile)
+ // Then explicitly play ONLY the front audio
+ a.audioPlayer.Play()
}
a.mu.Unlock()
})
@@ -1126,7 +1139,17 @@ func (a *Application) onRegenerateAudio() {
// onRegenerateBackAudio regenerates back audio for bg-bg cards
func (a *Application) onRegenerateBackAudio() {
+ fmt.Printf("DEBUG: ████████████████████████████████████████████████████████████████\n")
+ fmt.Printf("DEBUG: ████ ENTERED onRegenerateBackAudio() - REGENERATING BACK AUDIO\n")
+ fmt.Printf("DEBUG: ████████████████████████████████████████████████████████████████\n")
+ fmt.Printf("DEBUG (onRegenerateBackAudio): Starting back audio regeneration\n")
+ fmt.Printf(" - currentWord: %s\n", a.currentWord)
+ fmt.Printf(" - currentCardType: %s\n", a.currentCardType)
+ fmt.Printf(" - currentTranslation (state var): %s\n", a.currentTranslation)
+ fmt.Printf(" - translationEntry.Text (UI field): %s\n", a.translationEntry.Text)
+
if a.currentCardType != "bg-bg" {
+ fmt.Printf("DEBUG (onRegenerateBackAudio): Not a bg-bg card, returning\n")
return
}
@@ -1141,17 +1164,30 @@ func (a *Application) onRegenerateBackAudio() {
defer a.wg.Done()
defer a.decrementProcessing()
+ // CRITICAL: Get translation from state variable first
translation := a.currentTranslation
+ fmt.Printf("DEBUG (onRegenerateBackAudio): In goroutine - translation from a.currentTranslation: %s\n", translation)
+ fmt.Printf("DEBUG (onRegenerateBackAudio): In goroutine - translation UI field: %s\n", a.translationEntry.Text)
+
if translation == "" {
+ fmt.Printf("DEBUG (onRegenerateBackAudio): WARNING - translation state was empty, falling back to UI field\n")
translation = strings.TrimSpace(a.translationEntry.Text)
+ fmt.Printf("DEBUG (onRegenerateBackAudio): Using UI field translation: %s\n", translation)
}
+
wordForGeneration := a.currentWord
-
+ fmt.Printf("DEBUG (onRegenerateBackAudio): Final decision - will generate back audio for: %s\n", translation)
+ fmt.Printf("DEBUG (onRegenerateBackAudio): (NOT for word: %s)\n", wordForGeneration)
+
+ // For back audio, we need to use the main context, not create a new card context
+ // because the front audio regeneration already has an active context for this word.
+ // Creating a new context would cancel the front audio operation.
+ fmt.Printf("DEBUG (onRegenerateBackAudio): Using main context (not creating new card context)\n")
+ fmt.Printf("DEBUG (onRegenerateBackAudio): This prevents cancelling ongoing front audio operation\n")
+
a.startOperation(wordForGeneration)
defer a.endOperation(wordForGeneration)
- cardCtx, _ := a.getOrCreateCardContext(wordForGeneration)
-
cardDir, err := a.ensureCardDirectory(wordForGeneration)
if err != nil {
fyne.Do(func() {
@@ -1160,7 +1196,17 @@ func (a *Application) onRegenerateBackAudio() {
return
}
- audioFile, err := a.generateAudioBack(cardCtx, translation, cardDir)
+ fmt.Printf("DEBUG (onRegenerateBackAudio): Calling generateAudioBack with:\n")
+ fmt.Printf(" - ctx: a.ctx (main app context)\n")
+ fmt.Printf(" - translation: %s\n", translation)
+ fmt.Printf(" - cardDir: %s\n", cardDir)
+
+ audioFile, err := a.generateAudioBack(a.ctx, translation, cardDir)
+
+ fmt.Printf("DEBUG (onRegenerateBackAudio): generateAudioBack returned:\n")
+ fmt.Printf(" - err: %v\n", err)
+ fmt.Printf(" - audioFile: %s\n", audioFile)
+
if err != nil {
fyne.Do(func() {
a.showError(fmt.Errorf("Back audio regeneration failed: %w", err))
@@ -1174,6 +1220,8 @@ func (a *Application) onRegenerateBackAudio() {
a.mu.Lock()
if a.currentWord == wordForGeneration {
a.audioPlayer.SetBackAudioFile(audioFile)
+ // Auto-play the regenerated back audio
+ a.audioPlayer.PlayBack()
}
a.mu.Unlock()
})
@@ -2427,12 +2475,30 @@ func (a *Application) setupKeyboardShortcuts() {
a.onRegenerateRandomImage()
}
case 'a', 'а': // a = regenerate front audio
+ fmt.Printf("DEBUG: ╔════════════════════════════════════════════════════════════════\n")
+ fmt.Printf("DEBUG: ║ KEY PRESSED: 'a' (lowercase, regenerate FRONT audio)\n")
+ fmt.Printf("DEBUG: ╚════════════════════════════════════════════════════════════════\n")
+ fmt.Printf(" - currentWord: %s\n", a.currentWord)
+ fmt.Printf(" - currentCardType: %s\n", a.currentCardType)
+ fmt.Printf(" - regenerateAudioBtn.Disabled(): %v\n", a.regenerateAudioBtn.Disabled())
+ fmt.Printf(" - CALLING: onRegenerateAudio() for FRONT audio\n")
if !a.regenerateAudioBtn.Disabled() {
a.onRegenerateAudio()
}
case 'A', 'А': // A = regenerate back audio (for bg-bg cards)
- if a.currentCardType == "bg-bg" && !a.regenerateAudioBtn.Disabled() {
+ fmt.Printf("DEBUG: ╔════════════════════════════════════════════════════════════════\n")
+ fmt.Printf("DEBUG: ║ KEY PRESSED: 'A' (uppercase, regenerate BACK audio)\n")
+ fmt.Printf("DEBUG: ╚════════════════════════════════════════════════════════════════\n")
+ fmt.Printf(" - currentWord: %s\n", a.currentWord)
+ fmt.Printf(" - currentCardType: %s\n", a.currentCardType)
+ fmt.Printf(" - currentTranslation: %s\n", a.currentTranslation)
+ fmt.Printf(" - translationEntry.Text: %s\n", a.translationEntry.Text)
+ fmt.Printf(" - regenerateAudioBtn.Disabled(): %v\n", a.regenerateAudioBtn.Disabled())
+ if a.currentCardType == "bg-bg" {
+ fmt.Printf(" - CALLING: onRegenerateBackAudio() for BACK audio\n")
a.onRegenerateBackAudio()
+ } else {
+ fmt.Printf("DEBUG: Skipping back audio regen - not a bg-bg card\n")
}
case 'р', 'Р': // р = r
if !a.regenerateAllBtn.Disabled() {
@@ -2443,12 +2509,24 @@ func (a *Application) setupKeyboardShortcuts() {
a.onDelete()
}
case 'p', 'п': // p = play front audio
+ fmt.Printf("DEBUG: Key pressed 'p' (play front audio)\n")
+ fmt.Printf(" - currentWord: %s\n", a.currentWord)
+ fmt.Printf(" - currentCardType: %s\n", a.currentCardType)
+ fmt.Printf(" - currentAudioFile: %s\n", a.currentAudioFile)
if a.currentAudioFile != "" {
a.audioPlayer.Play()
+ } else {
+ fmt.Printf("DEBUG: No front audio file to play\n")
}
case 'P', 'П': // P = play back audio (for bg-bg cards)
+ fmt.Printf("DEBUG: Key pressed 'P' (play back audio)\n")
+ fmt.Printf(" - currentWord: %s\n", a.currentWord)
+ fmt.Printf(" - currentCardType: %s\n", a.currentCardType)
+ fmt.Printf(" - currentAudioFileBack: %s\n", a.currentAudioFileBack)
if a.currentAudioFileBack != "" {
a.audioPlayer.PlayBack()
+ } else {
+ fmt.Printf("DEBUG: No back audio file to play\n")
}
case 'ж', 'Ж': // ж = x
a.onExportToAnki()
@@ -2562,11 +2640,11 @@ func (a *Application) handleShortcutKey(key fyne.KeyName) {
}
a.onRegenerateRandomImage()
- case fyne.KeyA: // Regenerate Audio
- if a.regenerateAudioBtn.Disabled() {
- return
- }
- a.onRegenerateAudio()
+ case fyne.KeyA: // Regenerate Audio (handled by custom OnTypedRune for proper case sensitivity)
+ // NOTE: This handler is disabled to use character-based handler instead
+ // For bg-bg cards: shift+A = back audio, a = front audio
+ // For en-bg cards: a/A = regenerate audio
+ // See handleTypedRune for actual implementation
case fyne.KeyR: // Regenerate All
if a.regenerateAllBtn.Disabled() {
diff --git a/internal/gui/audio_player.go b/internal/gui/audio_player.go
index 4846615..8ab7f76 100644
--- a/internal/gui/audio_player.go
+++ b/internal/gui/audio_player.go
@@ -96,8 +96,19 @@ func (p *AudioPlayer) CreateRenderer() fyne.WidgetRenderer {
return widget.NewSimpleRenderer(p.container)
}
-// SetAudioFile sets the audio file to play
+// SetAudioFile sets the audio file to play and optionally auto-plays it
func (p *AudioPlayer) SetAudioFile(audioFile string) {
+ p.setAudioFileInternal(audioFile, true) // Enable auto-play
+}
+
+// SetAudioFileNoAutoPlay sets the audio file without auto-playing
+// Used when regenerating audio on bg-bg cards - we only want to play when explicitly requested
+func (p *AudioPlayer) SetAudioFileNoAutoPlay(audioFile string) {
+ p.setAudioFileInternal(audioFile, false) // Disable auto-play
+}
+
+// setAudioFileInternal is the internal implementation for setting audio files
+func (p *AudioPlayer) setAudioFileInternal(audioFile string, allowAutoPlay bool) {
p.audioFile = audioFile
p.isPlaying = false
@@ -139,8 +150,8 @@ func (p *AudioPlayer) SetAudioFile(audioFile string) {
statusText := fmt.Sprintf("Audio: %s%s", filepath.Base(audioFile), p.voiceInfo)
p.statusLabel.SetText(statusText)
- // Auto-play if enabled
- if p.autoPlayEnabled != nil && *p.autoPlayEnabled {
+ // Auto-play if enabled and allowed
+ if allowAutoPlay && p.autoPlayEnabled != nil && *p.autoPlayEnabled {
// Small delay to ensure UI is ready
go func() {
// Wait a tiny bit for UI to be ready
@@ -166,6 +177,9 @@ func (p *AudioPlayer) SetBackAudioFile(audioFile string) {
p.playBackLabel.Show()
// Update front label now that we know it's bg-bg
p.playButtonLabel.SetText("Front")
+ // NOTE: Do NOT auto-play back audio
+ // Back audio regeneration just prepares the file
+ // User should press 'P' to listen to it
} else {
p.isBgBg = false
p.playBackButton.Disable()
@@ -221,18 +235,28 @@ func (p *AudioPlayer) SetAutoPlayEnabled(autoPlayEnabled *bool) {
// onPlay handles play button click
func (p *AudioPlayer) onPlay() {
+ fmt.Printf("DEBUG (onPlay): Starting playback\n")
+ fmt.Printf(" - audioFile: %s\n", p.audioFile)
+ fmt.Printf(" - audioFileBack: %s\n", p.audioFileBack)
+ fmt.Printf(" - isBgBg: %v\n", p.isBgBg)
+ fmt.Printf(" - isPlaying: %v\n", p.isPlaying)
+
if p.audioFile == "" {
+ fmt.Printf("DEBUG (onPlay): No audioFile set, returning\n")
return
}
if p.isPlaying {
// Pause functionality - just stop for now
+ fmt.Printf("DEBUG (onPlay): Already playing, stopping\n")
p.onStop()
return
}
// Start playing
+ fmt.Printf("DEBUG (onPlay): About to start playback for: %s\n", filepath.Base(p.audioFile))
if err := p.startPlayback(); err != nil {
+ fmt.Printf("DEBUG (onPlay): Error starting playback: %v\n", err)
p.statusLabel.SetText(fmt.Sprintf("Error: %v", err))
return
}
@@ -241,35 +265,40 @@ func (p *AudioPlayer) onPlay() {
p.playButton.SetIcon(theme.MediaPauseIcon())
p.stopButton.Enable()
p.statusLabel.SetText(fmt.Sprintf("Playing: %s%s", filepath.Base(p.audioFile), p.voiceInfo))
+ fmt.Printf("DEBUG (onPlay): Playback started successfully\n")
}
// onPlayBack handles back audio button click (for bg-bg cards)
func (p *AudioPlayer) onPlayBack() {
+ fmt.Printf("DEBUG (onPlayBack): Starting back audio playback\n")
+ fmt.Printf(" - audioFile: %s\n", p.audioFile)
+ fmt.Printf(" - audioFileBack: %s\n", p.audioFileBack)
+ fmt.Printf(" - isBgBg: %v\n", p.isBgBg)
+ fmt.Printf(" - isPlaying: %v\n", p.isPlaying)
+
if p.audioFileBack == "" {
+ fmt.Printf("DEBUG (onPlayBack): No audioFileBack set, returning\n")
return
}
if p.isPlaying {
+ fmt.Printf("DEBUG (onPlayBack): Already playing, stopping first\n")
p.onStop()
}
- // Temporarily swap audio files to play the back audio
- originalFile := p.audioFile
- p.audioFile = p.audioFileBack
-
- if err := p.startPlayback(); err != nil {
+ // Start playback using back audio file directly
+ fmt.Printf("DEBUG (onPlayBack): About to start playback for back audio: %s\n", filepath.Base(p.audioFileBack))
+ if err := p.startPlaybackForFile(p.audioFileBack); err != nil {
+ fmt.Printf("DEBUG (onPlayBack): Error starting playback: %v\n", err)
p.statusLabel.SetText(fmt.Sprintf("Error: %v", err))
- p.audioFile = originalFile
return
}
p.isPlaying = true
- p.playButton.SetIcon(theme.MediaPauseIcon())
+ p.playBackButton.SetIcon(theme.MediaPauseIcon()) // Back button, not front
p.stopButton.Enable()
p.statusLabel.SetText(fmt.Sprintf("Playing back audio: %s", filepath.Base(p.audioFileBack)))
-
- // Restore original file after playback starts
- p.audioFile = originalFile
+ fmt.Printf("DEBUG (onPlayBack): Back audio playback started successfully\n")
}
// onStop handles stop button click
@@ -280,7 +309,12 @@ func (p *AudioPlayer) onStop() {
}
p.isPlaying = false
- p.playButton.SetIcon(theme.MediaPlayIcon())
+ // Set correct button icon based on which audio was playing
+ if p.isBgBg && p.audioFileBack != "" {
+ p.playBackButton.SetIcon(theme.MediaPlayIcon()) // Back button if it was playing
+ } else {
+ p.playButton.SetIcon(theme.MediaPlayIcon()) // Front button otherwise
+ }
p.stopButton.Disable()
p.statusLabel.SetText(fmt.Sprintf("Stopped: %s%s", filepath.Base(p.audioFile), p.voiceInfo))
}
@@ -304,32 +338,39 @@ func (p *AudioPlayer) PlayBack() {
}
// startPlayback starts audio playback using platform-specific commands
+// This plays the front audio file (p.audioFile)
func (p *AudioPlayer) startPlayback() error {
+ return p.startPlaybackForFile(p.audioFile)
+}
+
+// startPlaybackForFile starts playback of a specific audio file
+// This allows playing either front or back audio without modifying state
+func (p *AudioPlayer) startPlaybackForFile(audioFile string) error {
var cmd *exec.Cmd
switch runtime.GOOS {
case "darwin": // macOS
- cmd = exec.Command("afplay", p.audioFile)
+ cmd = exec.Command("afplay", audioFile)
case "linux":
// Try multiple commands in order of preference
// mpg123 first since it handles MP3 files best
if _, err := exec.LookPath("mpg123"); err == nil {
- cmd = exec.Command("mpg123", "-q", p.audioFile) // -q for quiet mode
+ cmd = exec.Command("mpg123", "-q", audioFile) // -q for quiet mode
} else if _, err := exec.LookPath("ffplay"); err == nil {
- cmd = exec.Command("ffplay", "-nodisp", "-autoexit", "-loglevel", "quiet", p.audioFile)
+ cmd = exec.Command("ffplay", "-nodisp", "-autoexit", "-loglevel", "quiet", audioFile)
} else if _, err := exec.LookPath("play"); err == nil {
// SoX play command
- cmd = exec.Command("play", "-q", p.audioFile)
+ cmd = exec.Command("play", "-q", audioFile)
} else if _, err := exec.LookPath("paplay"); err == nil {
- cmd = exec.Command("paplay", p.audioFile)
+ cmd = exec.Command("paplay", audioFile)
} else if _, err := exec.LookPath("aplay"); err == nil {
- cmd = exec.Command("aplay", "-q", p.audioFile)
+ cmd = exec.Command("aplay", "-q", audioFile)
} else {
return fmt.Errorf("no audio player found. Install mpg123, ffplay, sox, paplay, or aplay")
}
case "windows":
// Use Windows Media Player
- cmd = exec.Command("cmd", "/c", "start", "/min", p.audioFile)
+ cmd = exec.Command("cmd", "/c", "start", "/min", audioFile)
default:
return fmt.Errorf("unsupported platform: %s", runtime.GOOS)
}
@@ -338,15 +379,23 @@ func (p *AudioPlayer) startPlayback() error {
p.playCmd = cmd
// Start playback in background
+ // Capture whether this is playing back audio or front audio for proper icon reset
+ isPlayingBack := audioFile == p.audioFileBack
go func() {
err := cmd.Run()
if err == nil {
// Playback finished normally
fyne.Do(func() {
p.isPlaying = false
- p.playButton.SetIcon(theme.MediaPlayIcon())
+ // Reset correct button icon based on which audio was playing
+ if isPlayingBack {
+ p.playBackButton.SetIcon(theme.MediaPlayIcon())
+ p.statusLabel.SetText(fmt.Sprintf("Finished: %s", filepath.Base(p.audioFileBack)))
+ } else {
+ p.playButton.SetIcon(theme.MediaPlayIcon())
+ p.statusLabel.SetText(fmt.Sprintf("Finished: %s%s", filepath.Base(p.audioFile), p.voiceInfo))
+ }
p.stopButton.Disable()
- p.statusLabel.SetText(fmt.Sprintf("Finished: %s%s", filepath.Base(p.audioFile), p.voiceInfo))
})
}
}()
diff --git a/internal/gui/generator.go b/internal/gui/generator.go
index a3a0a4d..4569191 100644
--- a/internal/gui/generator.go
+++ b/internal/gui/generator.go
@@ -154,7 +154,10 @@ func (a *Application) generateAudio(ctx context.Context, word string, cardDir st
// generateAudioFront generates front audio for a bg-bg card
func (a *Application) generateAudioFront(ctx context.Context, word string, cardDir string) (string, error) {
+ fmt.Printf("DEBUG (generateAudioFront): Called with word: %s, cardDir: %s\n", word, cardDir)
+
if cardDir == "" {
+ fmt.Printf("DEBUG (generateAudioFront): Card directory not provided, returning error\n")
return "", fmt.Errorf("card directory not provided")
}
@@ -170,14 +173,18 @@ func (a *Application) generateAudioFront(ctx context.Context, word string, cardD
provider, err := audio.NewProvider(&audioConfig)
if err != nil {
+ fmt.Printf("DEBUG (generateAudioFront): Failed to create audio provider: %v\n", err)
return "", err
}
+ fmt.Printf("DEBUG (generateAudioFront): Generating front audio for '%s' with voice: %s, speed: %.2f\n", word, voice, speed)
fmt.Printf("Generating front audio for '%s' with voice: %s, speed: %.2f\n", word, voice, speed)
frontFile := filepath.Join(cardDir, fmt.Sprintf("audio_front.%s", a.config.AudioFormat))
+ fmt.Printf("DEBUG (generateAudioFront): Will write to: %s\n", frontFile)
if err := provider.GenerateAudio(ctx, word, frontFile); err != nil {
return "", fmt.Errorf("failed to generate front audio: %w", err)
}
+ fmt.Printf("DEBUG (generateAudioFront): Successfully wrote front audio to: %s\n", frontFile)
// Update metadata
metadataFile := filepath.Join(cardDir, "audio_metadata.txt")
@@ -191,7 +198,10 @@ func (a *Application) generateAudioFront(ctx context.Context, word string, cardD
// generateAudioBack generates back audio for a bg-bg card
func (a *Application) generateAudioBack(ctx context.Context, text string, cardDir string) (string, error) {
+ fmt.Printf("DEBUG (generateAudioBack): Called with text: %s, cardDir: %s\n", text, cardDir)
+
if cardDir == "" {
+ fmt.Printf("DEBUG (generateAudioBack): Card directory not provided, returning error\n")
return "", fmt.Errorf("card directory not provided")
}
@@ -207,14 +217,18 @@ func (a *Application) generateAudioBack(ctx context.Context, text string, cardDi
provider, err := audio.NewProvider(&audioConfig)
if err != nil {
+ fmt.Printf("DEBUG (generateAudioBack): Failed to create audio provider: %v\n", err)
return "", err
}
+ fmt.Printf("DEBUG (generateAudioBack): Generating back audio for '%s' with voice: %s, speed: %.2f\n", text, voice, speed)
fmt.Printf("Generating back audio for '%s' with voice: %s, speed: %.2f\n", text, voice, speed)
backFile := filepath.Join(cardDir, fmt.Sprintf("audio_back.%s", a.config.AudioFormat))
+ fmt.Printf("DEBUG (generateAudioBack): Will write to: %s\n", backFile)
if err := provider.GenerateAudio(ctx, text, backFile); err != nil {
return "", fmt.Errorf("failed to generate back audio: %w", err)
}
+ fmt.Printf("DEBUG (generateAudioBack): Successfully wrote back audio to: %s\n", backFile)
return backFile, nil
}
diff --git a/internal/gui/navigation.go b/internal/gui/navigation.go
index 1755397..79703de 100644
--- a/internal/gui/navigation.go
+++ b/internal/gui/navigation.go
@@ -377,13 +377,29 @@ func (a *Application) loadExistingFiles(word string) {
if data, err := os.ReadFile(translationFile); err == nil {
// Parse translation from "word = translation" format
content := string(data)
+ fmt.Printf("DEBUG (loadExistingFiles): Read translation.txt: %s\n", content)
parts := strings.Split(content, "=")
+ fmt.Printf("DEBUG (loadExistingFiles): Split into %d parts\n", len(parts))
if len(parts) >= 2 {
- a.currentTranslation = strings.TrimSpace(parts[1])
+ translation := strings.TrimSpace(parts[1])
+ fmt.Printf("DEBUG (loadExistingFiles): Extracted translation (part 1, after '='): %s\n", translation)
+
+ // CRITICAL: Set the state BEFORE SetText so it's available when needed
+ a.currentTranslation = translation
+ fmt.Printf("DEBUG (loadExistingFiles): Set a.currentTranslation state variable to: %s\n", a.currentTranslation)
+
fyne.Do(func() {
- a.translationEntry.SetText(a.currentTranslation)
+ a.translationEntry.SetText(translation)
+ fmt.Printf("DEBUG (loadExistingFiles): Set translationEntry UI field to: %s\n", translation)
+
+ // CRITICAL: After SetText, verify the state is correct
+ fmt.Printf("DEBUG (loadExistingFiles): After SetText, a.currentTranslation is: %s\n", a.currentTranslation)
})
+ } else {
+ fmt.Printf("DEBUG (loadExistingFiles): Translation file did not have '=' separator\n")
}
+ } else {
+ fmt.Printf("DEBUG (loadExistingFiles): Could not read translation.txt: %v\n", err)
}
// Load image prompt file
@@ -413,46 +429,61 @@ func (a *Application) loadExistingFiles(word string) {
// Load card type and audio files
cardType := internal.LoadCardType(wordDir)
a.currentCardType = string(cardType)
+ fmt.Printf("DEBUG (loadExistingFiles): Loaded card type: %s (isBgBg: %v)\n", cardType, cardType.IsBgBg())
// Update UI card type selector
fyne.Do(func() {
if cardType.IsBgBg() {
+ fmt.Printf("DEBUG (loadExistingFiles): Setting UI to bg-bg (Bulgarian → Bulgarian)\n")
a.cardTypeSelect.SetSelected("Bulgarian → Bulgarian")
} else {
+ fmt.Printf("DEBUG (loadExistingFiles): Setting UI to en-bg (English → Bulgarian)\n")
a.cardTypeSelect.SetSelected("English → Bulgarian")
}
})
// Load audio file(s)
if cardType.IsBgBg() {
+ fmt.Printf("DEBUG (loadExistingFiles): Loading audio files for bg-bg card\n")
// For bg-bg cards, load both front and back audio
frontAudio := filepath.Join(wordDir, fmt.Sprintf("audio_front.%s", a.config.AudioFormat))
backAudio := filepath.Join(wordDir, fmt.Sprintf("audio_back.%s", a.config.AudioFormat))
if _, err := os.Stat(frontAudio); err == nil {
a.currentAudioFile = frontAudio
+ fmt.Printf("DEBUG (loadExistingFiles): Found front audio: %s\n", frontAudio)
fyne.Do(func() {
a.audioPlayer.SetAudioFile(frontAudio)
})
+ } else {
+ fmt.Printf("DEBUG (loadExistingFiles): Front audio not found: %s\n", frontAudio)
}
if _, err := os.Stat(backAudio); err == nil {
a.currentAudioFileBack = backAudio
+ fmt.Printf("DEBUG (loadExistingFiles): Found back audio: %s\n", backAudio)
fyne.Do(func() {
a.audioPlayer.SetBackAudioFile(backAudio)
})
+ } else {
+ fmt.Printf("DEBUG (loadExistingFiles): Back audio not found: %s\n", backAudio)
}
} else {
+ fmt.Printf("DEBUG (loadExistingFiles): Loading audio files for en-bg card\n")
// For en-bg cards, load standard audio file
audioFile := filepath.Join(wordDir, fmt.Sprintf("audio.%s", a.config.AudioFormat))
if _, err := os.Stat(audioFile); err == nil {
a.currentAudioFile = audioFile
+ fmt.Printf("DEBUG (loadExistingFiles): Found audio: %s\n", audioFile)
fyne.Do(func() {
a.audioPlayer.SetAudioFile(audioFile)
})
+ } else {
+ fmt.Printf("DEBUG (loadExistingFiles): Audio not found: %s\n", audioFile)
}
// Hide back audio button for en-bg cards
a.currentAudioFileBack = ""
+ fmt.Printf("DEBUG (loadExistingFiles): Clearing back audio for en-bg card\n")
fyne.Do(func() {
a.audioPlayer.SetBackAudioFile("")
})
diff --git a/internal/version.go b/internal/version.go
index 17deabe..7f93254 100644
--- a/internal/version.go
+++ b/internal/version.go
@@ -1,3 +1,3 @@
package internal
-const Version = "0.7.5"
+const Version = "0.8.0"
diff --git a/output b/output
new file mode 100644
index 0000000..c1a2dbc
--- /dev/null
+++ b/output
@@ -0,0 +1,239 @@
+Loading files from directory: /home/paul/.local/state/totalrecall/cards/1769025513854_d6abfb17
+DEBUG (loadExistingFiles): Read translation.txt: котка = домашно животно
+
+DEBUG (loadExistingFiles): Split into 2 parts
+DEBUG (loadExistingFiles): Extracted translation (part 1, after '='): домашно животно
+DEBUG (loadExistingFiles): Set a.currentTranslation state variable to: домашно животно
+DEBUG (loadExistingFiles): Set translationEntry UI field to: домашно животно
+DEBUG (loadExistingFiles): After SetText, a.currentTranslation is: домашно животно
+Loaded prompt from file: /home/paul/.local/state/totalrecall/cards/1769025513854_d6abfb17/image_prompt.txt
+Loaded phonetic info from file: /home/paul/.local/state/totalrecall/cards/1769025513854_d6abfb17/phonetic.txt
+DEBUG (loadExistingFiles): Loaded card type: bg-bg (isBgBg: true)
+DEBUG (loadExistingFiles): Setting UI to bg-bg (Bulgarian → Bulgarian)
+DEBUG (loadExistingFiles): Loading audio files for bg-bg card
+DEBUG (loadExistingFiles): Found front audio: /home/paul/.local/state/totalrecall/cards/1769025513854_d6abfb17/audio_front.mp3
+DEBUG (loadExistingFiles): Found back audio: /home/paul/.local/state/totalrecall/cards/1769025513854_d6abfb17/audio_back.mp3
+DEBUG (onPlay): Starting playback
+ - audioFile: /home/paul/.local/state/totalrecall/cards/1769025513854_d6abfb17/audio_front.mp3
+ - audioFileBack: /home/paul/.local/state/totalrecall/cards/1769025513854_d6abfb17/audio_back.mp3
+ - isBgBg: true
+ - isPlaying: false
+DEBUG (onPlay): About to start playback for: audio_front.mp3
+DEBUG (onPlay): Playback started successfully
+DEBUG: ╔════════════════════════════════════════════════════════════════
+DEBUG: ║ KEY PRESSED: 'a' (lowercase, regenerate FRONT audio)
+DEBUG: ╚════════════════════════════════════════════════════════════════
+ - currentWord: котка
+ - currentCardType: bg-bg
+ - regenerateAudioBtn.Disabled(): false
+ - CALLING: onRegenerateAudio() for FRONT audio
+DEBUG: ████████████████████████████████████████████████████████████████
+DEBUG: ████ ENTERED onRegenerateAudio() - REGENERATING FRONT AUDIO
+DEBUG: ████████████████████████████████████████████████████████████████
+DEBUG (onRegenerateAudio): Starting front audio regeneration
+ - currentWord: котка
+ - currentCardType: bg-bg
+DEBUG (onRegenerateAudio): Card type is bg-bg, regenerating FRONT audio only
+DEBUG (onRegenerateAudio): In goroutine - wordForGeneration: котка
+DEBUG (generateAudioFront): Called with word: котка, cardDir: /home/paul/.local/state/totalrecall/cards/1769025513854_d6abfb17
+DEBUG (generateAudioFront): Generating front audio for 'котка' with voice: onyx, speed: 0.92
+Generating front audio for 'котка' with voice: onyx, speed: 0.92
+DEBUG (generateAudioFront): Will write to: /home/paul/.local/state/totalrecall/cards/1769025513854_d6abfb17/audio_front.mp3
+OpenAI TTS: Using model 'gpt-4o-mini-tts' with voice 'onyx' at speed 0.92
+OpenAI TTS Instruction: 'You are speaking Bulgarian language (български език). Pronounce the Bulgarian text with authentic Bulgarian phonetics, not Russian. Speak slowly and clearly for language learners.'
+OpenAI TTS Input: 'котка'
+DEBUG (generateAudioFront): Successfully wrote front audio to: /home/paul/.local/state/totalrecall/cards/1769025513854_d6abfb17/audio_front.mp3
+DEBUG (onPlay): Starting playback
+ - audioFile: /home/paul/.local/state/totalrecall/cards/1769025513854_d6abfb17/audio_front.mp3
+ - audioFileBack: /home/paul/.local/state/totalrecall/cards/1769025513854_d6abfb17/audio_back.mp3
+ - isBgBg: true
+ - isPlaying: false
+DEBUG (onPlay): About to start playback for: audio_front.mp3
+DEBUG (onPlay): Playback started successfully
+DEBUG: ╔════════════════════════════════════════════════════════════════
+DEBUG: ║ KEY PRESSED: 'A' (uppercase, regenerate BACK audio)
+DEBUG: ╚════════════════════════════════════════════════════════════════
+ - currentWord: котка
+ - currentCardType: bg-bg
+ - currentTranslation: домашно животно
+ - translationEntry.Text: домашно животно
+ - regenerateAudioBtn.Disabled(): false
+ - CALLING: onRegenerateBackAudio() for BACK audio
+DEBUG: ████████████████████████████████████████████████████████████████
+DEBUG: ████ ENTERED onRegenerateBackAudio() - REGENERATING BACK AUDIO
+DEBUG: ████████████████████████████████████████████████████████████████
+DEBUG (onRegenerateBackAudio): Starting back audio regeneration
+ - currentWord: котка
+ - currentCardType: bg-bg
+ - currentTranslation (state var): домашно животно
+ - translationEntry.Text (UI field): домашно животно
+DEBUG (onRegenerateBackAudio): In goroutine - translation from a.currentTranslation: домашно животно
+DEBUG (onRegenerateBackAudio): In goroutine - translation UI field: домашно животно
+DEBUG (onRegenerateBackAudio): Final decision - will generate back audio for: домашно животно
+DEBUG (onRegenerateBackAudio): (NOT for word: котка)
+DEBUG (onRegenerateBackAudio): Using main context (not creating new card context)
+DEBUG (onRegenerateBackAudio): This prevents cancelling ongoing front audio operation
+DEBUG (onRegenerateBackAudio): Calling generateAudioBack with:
+ - ctx: a.ctx (main app context)
+ - translation: домашно животно
+ - cardDir: /home/paul/.local/state/totalrecall/cards/1769025513854_d6abfb17
+DEBUG (generateAudioBack): Called with text: домашно животно, cardDir: /home/paul/.local/state/totalrecall/cards/1769025513854_d6abfb17
+DEBUG (generateAudioBack): Generating back audio for 'домашно животно' with voice: ash, speed: 1.00
+Generating back audio for 'домашно животно' with voice: ash, speed: 1.00
+DEBUG (generateAudioBack): Will write to: /home/paul/.local/state/totalrecall/cards/1769025513854_d6abfb17/audio_back.mp3
+OpenAI TTS: Using model 'gpt-4o-mini-tts' with voice 'ash' at speed 1.00
+OpenAI TTS Instruction: 'You are speaking Bulgarian language (български език). Pronounce the Bulgarian text with authentic Bulgarian phonetics, not Russian. Speak slowly and clearly for language learners.'
+OpenAI TTS Input: 'домашно животно'
+DEBUG (generateAudioBack): Successfully wrote back audio to: /home/paul/.local/state/totalrecall/cards/1769025513854_d6abfb17/audio_back.mp3
+DEBUG (onRegenerateBackAudio): generateAudioBack returned:
+ - err: <nil>
+ - audioFile: /home/paul/.local/state/totalrecall/cards/1769025513854_d6abfb17/audio_back.mp3
+DEBUG (onPlayBack): Starting back audio playback
+ - audioFile: /home/paul/.local/state/totalrecall/cards/1769025513854_d6abfb17/audio_front.mp3
+ - audioFileBack: /home/paul/.local/state/totalrecall/cards/1769025513854_d6abfb17/audio_back.mp3
+ - isBgBg: true
+ - isPlaying: false
+DEBUG (onPlayBack): About to start playback for back audio: audio_back.mp3
+DEBUG (onPlayBack): Back audio playback started successfully
+DEBUG: ╔════════════════════════════════════════════════════════════════
+DEBUG: ║ KEY PRESSED: 'A' (uppercase, regenerate BACK audio)
+DEBUG: ╚════════════════════════════════════════════════════════════════
+ - currentWord: котка
+ - currentCardType: bg-bg
+ - currentTranslation: домашно животно
+ - translationEntry.Text: домашно животно
+ - regenerateAudioBtn.Disabled(): false
+ - CALLING: onRegenerateBackAudio() for BACK audio
+DEBUG: ████████████████████████████████████████████████████████████████
+DEBUG: ████ ENTERED onRegenerateBackAudio() - REGENERATING BACK AUDIO
+DEBUG: ████████████████████████████████████████████████████████████████
+DEBUG (onRegenerateBackAudio): Starting back audio regeneration
+ - currentWord: котка
+ - currentCardType: bg-bg
+ - currentTranslation (state var): домашно животно
+ - translationEntry.Text (UI field): домашно животно
+DEBUG (onRegenerateBackAudio): In goroutine - translation from a.currentTranslation: домашно животно
+DEBUG (onRegenerateBackAudio): In goroutine - translation UI field: домашно животно
+DEBUG (onRegenerateBackAudio): Final decision - will generate back audio for: домашно животно
+DEBUG (onRegenerateBackAudio): (NOT for word: котка)
+DEBUG (onRegenerateBackAudio): Using main context (not creating new card context)
+DEBUG (onRegenerateBackAudio): This prevents cancelling ongoing front audio operation
+DEBUG (onRegenerateBackAudio): Calling generateAudioBack with:
+ - ctx: a.ctx (main app context)
+ - translation: домашно животно
+ - cardDir: /home/paul/.local/state/totalrecall/cards/1769025513854_d6abfb17
+DEBUG (generateAudioBack): Called with text: домашно животно, cardDir: /home/paul/.local/state/totalrecall/cards/1769025513854_d6abfb17
+DEBUG (generateAudioBack): Generating back audio for 'домашно животно' with voice: echo, speed: 0.93
+Generating back audio for 'домашно животно' with voice: echo, speed: 0.93
+DEBUG (generateAudioBack): Will write to: /home/paul/.local/state/totalrecall/cards/1769025513854_d6abfb17/audio_back.mp3
+OpenAI TTS: Using model 'gpt-4o-mini-tts' with voice 'echo' at speed 0.93
+OpenAI TTS Instruction: 'You are speaking Bulgarian language (български език). Pronounce the Bulgarian text with authentic Bulgarian phonetics, not Russian. Speak slowly and clearly for language learners.'
+OpenAI TTS Input: 'домашно животно'
+DEBUG (generateAudioBack): Successfully wrote back audio to: /home/paul/.local/state/totalrecall/cards/1769025513854_d6abfb17/audio_back.mp3
+DEBUG (onRegenerateBackAudio): generateAudioBack returned:
+ - err: <nil>
+ - audioFile: /home/paul/.local/state/totalrecall/cards/1769025513854_d6abfb17/audio_back.mp3
+DEBUG (onPlayBack): Starting back audio playback
+ - audioFile: /home/paul/.local/state/totalrecall/cards/1769025513854_d6abfb17/audio_front.mp3
+ - audioFileBack: /home/paul/.local/state/totalrecall/cards/1769025513854_d6abfb17/audio_back.mp3
+ - isBgBg: true
+ - isPlaying: false
+DEBUG (onPlayBack): About to start playback for back audio: audio_back.mp3
+DEBUG (onPlayBack): Back audio playback started successfully
+DEBUG: Key pressed 'p' (play front audio)
+ - currentWord: котка
+ - currentCardType: bg-bg
+ - currentAudioFile: /home/paul/.local/state/totalrecall/cards/1769025513854_d6abfb17/audio_front.mp3
+DEBUG (onPlay): Starting playback
+ - audioFile: /home/paul/.local/state/totalrecall/cards/1769025513854_d6abfb17/audio_front.mp3
+ - audioFileBack: /home/paul/.local/state/totalrecall/cards/1769025513854_d6abfb17/audio_back.mp3
+ - isBgBg: true
+ - isPlaying: false
+DEBUG (onPlay): About to start playback for: audio_front.mp3
+DEBUG (onPlay): Playback started successfully
+DEBUG: ╔════════════════════════════════════════════════════════════════
+DEBUG: ║ KEY PRESSED: 'A' (uppercase, regenerate BACK audio)
+DEBUG: ╚════════════════════════════════════════════════════════════════
+ - currentWord: котка
+ - currentCardType: bg-bg
+ - currentTranslation: домашно животно
+ - translationEntry.Text: домашно животно
+ - regenerateAudioBtn.Disabled(): false
+ - CALLING: onRegenerateBackAudio() for BACK audio
+DEBUG: ████████████████████████████████████████████████████████████████
+DEBUG: ████ ENTERED onRegenerateBackAudio() - REGENERATING BACK AUDIO
+DEBUG: ████████████████████████████████████████████████████████████████
+DEBUG (onRegenerateBackAudio): Starting back audio regeneration
+ - currentWord: котка
+ - currentCardType: bg-bg
+ - currentTranslation (state var): домашно животно
+ - translationEntry.Text (UI field): домашно животно
+DEBUG (onRegenerateBackAudio): In goroutine - translation from a.currentTranslation: домашно животно
+DEBUG (onRegenerateBackAudio): In goroutine - translation UI field: домашно животно
+DEBUG (onRegenerateBackAudio): Final decision - will generate back audio for: домашно животно
+DEBUG (onRegenerateBackAudio): (NOT for word: котка)
+DEBUG (onRegenerateBackAudio): Using main context (not creating new card context)
+DEBUG (onRegenerateBackAudio): This prevents cancelling ongoing front audio operation
+DEBUG (onRegenerateBackAudio): Calling generateAudioBack with:
+ - ctx: a.ctx (main app context)
+ - translation: домашно животно
+ - cardDir: /home/paul/.local/state/totalrecall/cards/1769025513854_d6abfb17
+DEBUG (generateAudioBack): Called with text: домашно животно, cardDir: /home/paul/.local/state/totalrecall/cards/1769025513854_d6abfb17
+DEBUG (generateAudioBack): Generating back audio for 'домашно животно' with voice: shimmer, speed: 0.98
+Generating back audio for 'домашно животно' with voice: shimmer, speed: 0.98
+DEBUG (generateAudioBack): Will write to: /home/paul/.local/state/totalrecall/cards/1769025513854_d6abfb17/audio_back.mp3
+OpenAI TTS: Using model 'gpt-4o-mini-tts' with voice 'shimmer' at speed 0.98
+OpenAI TTS Instruction: 'You are speaking Bulgarian language (български език). Pronounce the Bulgarian text with authentic Bulgarian phonetics, not Russian. Speak slowly and clearly for language learners.'
+OpenAI TTS Input: 'домашно животно'
+DEBUG (generateAudioBack): Successfully wrote back audio to: /home/paul/.local/state/totalrecall/cards/1769025513854_d6abfb17/audio_back.mp3
+DEBUG (onRegenerateBackAudio): generateAudioBack returned:
+ - err: <nil>
+ - audioFile: /home/paul/.local/state/totalrecall/cards/1769025513854_d6abfb17/audio_back.mp3
+DEBUG (onPlayBack): Starting back audio playback
+ - audioFile: /home/paul/.local/state/totalrecall/cards/1769025513854_d6abfb17/audio_front.mp3
+ - audioFileBack: /home/paul/.local/state/totalrecall/cards/1769025513854_d6abfb17/audio_back.mp3
+ - isBgBg: true
+ - isPlaying: false
+DEBUG (onPlayBack): About to start playback for back audio: audio_back.mp3
+DEBUG (onPlayBack): Back audio playback started successfully
+DEBUG: Key pressed 'p' (play front audio)
+ - currentWord: котка
+ - currentCardType: bg-bg
+ - currentAudioFile: /home/paul/.local/state/totalrecall/cards/1769025513854_d6abfb17/audio_front.mp3
+DEBUG (onPlay): Starting playback
+ - audioFile: /home/paul/.local/state/totalrecall/cards/1769025513854_d6abfb17/audio_front.mp3
+ - audioFileBack: /home/paul/.local/state/totalrecall/cards/1769025513854_d6abfb17/audio_back.mp3
+ - isBgBg: true
+ - isPlaying: false
+DEBUG (onPlay): About to start playback for: audio_front.mp3
+DEBUG (onPlay): Playback started successfully
+DEBUG: ╔════════════════════════════════════════════════════════════════
+DEBUG: ║ KEY PRESSED: 'a' (lowercase, regenerate FRONT audio)
+DEBUG: ╚════════════════════════════════════════════════════════════════
+ - currentWord: котка
+ - currentCardType: bg-bg
+ - regenerateAudioBtn.Disabled(): false
+ - CALLING: onRegenerateAudio() for FRONT audio
+DEBUG: ████████████████████████████████████████████████████████████████
+DEBUG: ████ ENTERED onRegenerateAudio() - REGENERATING FRONT AUDIO
+DEBUG: ████████████████████████████████████████████████████████████████
+DEBUG (onRegenerateAudio): Starting front audio regeneration
+ - currentWord: котка
+ - currentCardType: bg-bg
+DEBUG (onRegenerateAudio): Card type is bg-bg, regenerating FRONT audio only
+DEBUG (onRegenerateAudio): In goroutine - wordForGeneration: котка
+DEBUG (generateAudioFront): Called with word: котка, cardDir: /home/paul/.local/state/totalrecall/cards/1769025513854_d6abfb17
+DEBUG (generateAudioFront): Generating front audio for 'котка' with voice: coral, speed: 0.91
+Generating front audio for 'котка' with voice: coral, speed: 0.91
+DEBUG (generateAudioFront): Will write to: /home/paul/.local/state/totalrecall/cards/1769025513854_d6abfb17/audio_front.mp3
+OpenAI TTS: Using model 'gpt-4o-mini-tts' with voice 'coral' at speed 0.91
+OpenAI TTS Instruction: 'You are speaking Bulgarian language (български език). Pronounce the Bulgarian text with authentic Bulgarian phonetics, not Russian. Speak slowly and clearly for language learners.'
+OpenAI TTS Input: 'котка'
+DEBUG (generateAudioFront): Successfully wrote front audio to: /home/paul/.local/state/totalrecall/cards/1769025513854_d6abfb17/audio_front.mp3
+DEBUG (onPlay): Starting playback
+ - audioFile: /home/paul/.local/state/totalrecall/cards/1769025513854_d6abfb17/audio_front.mp3
+ - audioFileBack: /home/paul/.local/state/totalrecall/cards/1769025513854_d6abfb17/audio_back.mp3
+ - isBgBg: true
+ - isPlaying: false
+DEBUG (onPlay): About to start playback for: audio_front.mp3
+DEBUG (onPlay): Playback started successfully