diff options
| author | Paul Buetow <paul@buetow.org> | 2026-04-02 07:27:48 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-04-02 07:27:48 +0300 |
| commit | c1a2a5a1df54611437d36e1b2f448f78b32e8eb1 (patch) | |
| tree | 504407e236c5192f0e2fb8eb3045669eccc6add0 | |
| parent | e96cc2578595a0ec22a07819cd0eb1b0b0c711f7 (diff) | |
Fix GUI voice-suffixed audio discovery
| -rw-r--r-- | internal/gui/audio_paths.go | 59 | ||||
| -rw-r--r-- | internal/gui/audio_paths_test.go | 86 | ||||
| -rw-r--r-- | internal/gui/navigation.go | 32 |
3 files changed, 89 insertions, 88 deletions
diff --git a/internal/gui/audio_paths.go b/internal/gui/audio_paths.go index baee232..42402da 100644 --- a/internal/gui/audio_paths.go +++ b/internal/gui/audio_paths.go @@ -1,26 +1,13 @@ package gui -import ( - "os" - "path/filepath" - "strings" - "time" -) - -var supportedAudioExtensions = map[string]struct{}{ - ".aac": {}, - ".flac": {}, - ".mp3": {}, - ".opus": {}, - ".wav": {}, -} +import "codeberg.org/snonux/totalrecall/internal/anki" func (a *Application) resolveSingleAudioFile(wordDir string) string { - return resolveAudioFileByBaseName(wordDir, "audio") + return anki.ResolveAudioFile(wordDir, "audio", "") } func (a *Application) resolveBgBgAudioFiles(wordDir string) (string, string) { - return resolveAudioFileByBaseName(wordDir, "audio_front"), resolveAudioFileByBaseName(wordDir, "audio_back") + return anki.ResolveAudioFile(wordDir, "audio_front", ""), anki.ResolveAudioFile(wordDir, "audio_back", "") } func (a *Application) hasAnyAudioFile(wordDir string) bool { @@ -32,43 +19,3 @@ func (a *Application) hasAnyAudioFile(wordDir string) bool { front, back := a.resolveBgBgAudioFiles(wordDir) return front != "" || back != "" } - -func resolveAudioFileByBaseName(wordDir, baseName string) string { - entries, err := os.ReadDir(wordDir) - if err != nil { - return "" - } - - prefix := baseName + "." - var resolved string - var resolvedModTime time.Time - - for _, entry := range entries { - if entry.IsDir() { - continue - } - - name := entry.Name() - if !strings.HasPrefix(name, prefix) { - continue - } - - ext := strings.ToLower(filepath.Ext(name)) - if _, ok := supportedAudioExtensions[ext]; !ok { - continue - } - - info, err := entry.Info() - if err != nil { - continue - } - - candidate := filepath.Join(wordDir, name) - if resolved == "" || info.ModTime().After(resolvedModTime) || (info.ModTime().Equal(resolvedModTime) && candidate < resolved) { - resolved = candidate - resolvedModTime = info.ModTime() - } - } - - return resolved -} diff --git a/internal/gui/audio_paths_test.go b/internal/gui/audio_paths_test.go index 1251c00..a61a24e 100644 --- a/internal/gui/audio_paths_test.go +++ b/internal/gui/audio_paths_test.go @@ -1,10 +1,15 @@ package gui import ( + "context" "os" "path/filepath" "testing" "time" + + fyneapp "fyne.io/fyne/v2/app" + "fyne.io/fyne/v2/widget" + ttwidget "github.com/dweymouth/fyne-tooltip/widget" ) func TestResolveSingleAudioFileFindsLegacyMp3WhenGuiDefaultIsWav(t *testing.T) { @@ -29,38 +34,76 @@ func TestResolveSingleAudioFileFindsLegacyMp3WhenGuiDefaultIsWav(t *testing.T) { } } -func TestResolveSingleAudioFilePrefersNewerOnDiskAudio(t *testing.T) { +func TestGUIDiscoveryAndLoadingRecognizeVoiceSuffixedAudio(t *testing.T) { + fyneApp := fyneapp.New() + t.Cleanup(func() { + fyneApp.Quit() + }) + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + tempDir := t.TempDir() - wordDir := filepath.Join(tempDir, "word") + wordDir := filepath.Join(tempDir, "word-card") if err := os.MkdirAll(wordDir, 0755); err != nil { t.Fatalf("failed to create word dir: %v", err) } - mp3Path := filepath.Join(wordDir, "audio.mp3") - wavPath := filepath.Join(wordDir, "audio.wav") - if err := os.WriteFile(mp3Path, []byte("mp3"), 0644); err != nil { - t.Fatalf("failed to write mp3 file: %v", err) + word := "ябълка" + if err := os.WriteFile(filepath.Join(wordDir, "word.txt"), []byte(word), 0644); err != nil { + t.Fatalf("failed to write word file: %v", err) + } + + voiceAudioPath := filepath.Join(wordDir, "audio_sentinel-gemini-voice.mp3") + if err := os.WriteFile(voiceAudioPath, []byte("voice-audio"), 0644); err != nil { + t.Fatalf("failed to write voice-suffixed audio file: %v", err) } - if err := os.WriteFile(wavPath, []byte("wav"), 0644); err != nil { - t.Fatalf("failed to write wav file: %v", err) + + app := &Application{ + config: &Config{ + OutputDir: tempDir, + AudioFormat: "wav", + }, + ctx: ctx, + cancel: cancel, + queue: NewWordQueue(context.Background()), + wordInput: NewCustomEntry(), + audioPlayer: NewAudioPlayer(), + imageDisplay: NewImageDisplay(), + translationEntry: NewCustomEntry(), + cardTypeSelect: widget.NewSelect([]string{"English → Bulgarian", "Bulgarian → Bulgarian"}, nil), + imagePromptEntry: NewCustomMultiLineEntry(), + statusLabel: widget.NewLabel(""), + prevWordBtn: ttwidget.NewButton("", nil), + nextWordBtn: ttwidget.NewButton("", nil), + keepButton: ttwidget.NewButton("", nil), + regenerateImageBtn: ttwidget.NewButton("", nil), + regenerateRandomImageBtn: ttwidget.NewButton("", nil), + regenerateAudioBtn: ttwidget.NewButton("", nil), + regenerateAllBtn: ttwidget.NewButton("", nil), + deleteButton: ttwidget.NewButton("", nil), } - older := time.Now().Add(-time.Hour) - newer := time.Now() - if err := os.Chtimes(mp3Path, older, older); err != nil { - t.Fatalf("failed to set mp3 file time: %v", err) + app.scanExistingWords() + if len(app.existingWords) != 1 || app.existingWords[0] != word { + t.Fatalf("scanExistingWords() = %v, want %q", app.existingWords, word) } - if err := os.Chtimes(wavPath, newer, newer); err != nil { - t.Fatalf("failed to set wav file time: %v", err) + + app.loadExistingFiles(word) + time.Sleep(50 * time.Millisecond) + + if app.currentAudioFile != voiceAudioPath { + t.Fatalf("currentAudioFile = %q, want voice-suffixed path %q", app.currentAudioFile, voiceAudioPath) } - app := &Application{ - config: &Config{AudioFormat: "wav"}, + if app.audioPlayer == nil { + t.Fatal("expected audio player to be initialized") + } + if app.audioPlayer.audioFile != voiceAudioPath { + t.Fatalf("audioPlayer.audioFile = %q, want %q", app.audioPlayer.audioFile, voiceAudioPath) } - got := app.resolveSingleAudioFile(wordDir) - if got != wavPath { - t.Fatalf("resolveSingleAudioFile() = %q, want newer wav %q", got, wavPath) + if app.currentCardType != "en-bg" { + t.Fatalf("currentCardType = %q, want %q", app.currentCardType, "en-bg") } } @@ -80,11 +123,6 @@ func TestResolveBgBgAudioFilesFindLegacyMp3Files(t *testing.T) { t.Fatalf("failed to write back file: %v", err) } - older := time.Now().Add(-time.Hour) - if err := os.Chtimes(frontPath, older, older); err != nil { - t.Fatalf("failed to set front file time: %v", err) - } - app := &Application{ config: &Config{AudioFormat: "wav"}, } diff --git a/internal/gui/navigation.go b/internal/gui/navigation.go index cc1d9e6..4acbb50 100644 --- a/internal/gui/navigation.go +++ b/internal/gui/navigation.go @@ -444,9 +444,13 @@ func (a *Application) loadExistingFiles(word string) { if frontAudio != "" { a.currentAudioFile = frontAudio fmt.Printf("DEBUG (loadExistingFiles): Found front audio: %s\n", frontAudio) - fyne.Do(func() { + if a.window == nil { a.audioPlayer.SetAudioFile(frontAudio) - }) + } else { + fyne.Do(func() { + a.audioPlayer.SetAudioFile(frontAudio) + }) + } } else { fmt.Printf("DEBUG (loadExistingFiles): Front audio not found: %s\n", frontAudio) } @@ -454,9 +458,13 @@ func (a *Application) loadExistingFiles(word string) { if backAudio != "" { a.currentAudioFileBack = backAudio fmt.Printf("DEBUG (loadExistingFiles): Found back audio: %s\n", backAudio) - fyne.Do(func() { + if a.window == nil { a.audioPlayer.SetBackAudioFile(backAudio) - }) + } else { + fyne.Do(func() { + a.audioPlayer.SetBackAudioFile(backAudio) + }) + } } else { fmt.Printf("DEBUG (loadExistingFiles): Back audio not found: %s\n", backAudio) } @@ -467,18 +475,26 @@ func (a *Application) loadExistingFiles(word string) { if audioFile != "" { a.currentAudioFile = audioFile fmt.Printf("DEBUG (loadExistingFiles): Found audio: %s\n", audioFile) - fyne.Do(func() { + if a.window == nil { a.audioPlayer.SetAudioFile(audioFile) - }) + } else { + 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() { + if a.window == nil { a.audioPlayer.SetBackAudioFile("") - }) + } else { + fyne.Do(func() { + a.audioPlayer.SetBackAudioFile("") + }) + } } // Load image file |
