feature/backend-rewrite #2

Open
kamil wants to merge 236 commits from feature/backend-rewrite into main
Showing only changes of commit c8e0cf2813 - Show all commits

View File

@@ -188,6 +188,10 @@ func (s *Server) ListenAndServe(ctx context.Context) error {
s.log.Info("HTTP server listening", "addr", s.addr)
// Pre-populate voice samples in the background so the UI voice selector
// has playable previews without requiring a manual trigger.
go s.warmVoiceSamples(ctx)
select {
case <-ctx.Done():
shutCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
@@ -1486,6 +1490,78 @@ func voiceSampleKey(voice string) string {
return fmt.Sprintf("_voice-samples/%s.mp3", safe)
}
// warmVoiceSamples runs at startup in a background goroutine.
// It generates a short audio sample for every available Kokoro voice that
// doesn't already have one in MinIO, so the UI voice selector has playable
// previews without requiring a manual trigger.
// It respects ctx cancellation and waits up to 30 s for Kokoro to become
// reachable before giving up.
func (s *Server) warmVoiceSamples(ctx context.Context) {
if s.kokoroURL == "" {
return
}
// Wait for Kokoro to be reachable (it may still be starting up).
deadline := time.Now().Add(30 * time.Second)
for time.Now().Before(deadline) {
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, s.kokoroURL+"/v1/audio/voices", nil)
resp, err := http.DefaultClient.Do(req)
if err == nil {
resp.Body.Close()
if resp.StatusCode == http.StatusOK {
break
}
}
select {
case <-ctx.Done():
return
case <-time.After(3 * time.Second):
}
}
voices := s.voices()
s.log.Info("warming voice samples", "voices", len(voices))
generated, skipped, failed := 0, 0, 0
for _, voice := range voices {
if ctx.Err() != nil {
return
}
key := voiceSampleKey(voice)
if s.store.AudioExists(ctx, key) {
skipped++
continue
}
filename, err := s.generateSpeech(ctx, voiceSampleText, voice, 1.0)
if err != nil {
s.log.Warn("voice sample warmup: generation failed", "voice", voice, "err", err)
failed++
continue
}
audioData, err := s.downloadFromKokoro(ctx, filename)
if err != nil {
s.log.Warn("voice sample warmup: download failed", "voice", voice, "err", err)
failed++
continue
}
if err := s.store.PutAudio(ctx, key, audioData); err != nil {
s.log.Warn("voice sample warmup: upload failed", "voice", voice, "key", key, "err", err)
failed++
continue
}
s.log.Debug("voice sample warmed", "voice", voice)
generated++
}
s.log.Info("voice sample warmup complete",
"generated", generated, "skipped", skipped, "failed", failed)
}
// handleGenerateVoiceSamples handles POST /api/audio/voice-samples.
// It generates short audio samples for each available voice and stores them
// in the audio MinIO bucket so the UI can play them during voice selection.