fix(audio): preserve generated sound recordings (#12641)

FLAC normalization renames the backend's WAV destination. The next
request then reuses that apparently vacant path and replaces the first
recording when normalization runs again.

Give each request a UUID filename, and test that successive FLAC outputs
retain distinct paths and their original bytes.

Addresses the overwrite report in #12633. Reference-audio input remains
outside this fix.

Assisted-by: Codex:gpt-6

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Co-authored-by: localai-org-maint-bot <306269227+localai-org-maint-bot@users.noreply.github.com>
This commit is contained in:
localai-org-maint-botandlocalai-org-maint-bot authored and GitHub committed 2026-10-12 18:49:17 +02:00
1 parent 47afc6c7e0
commit a551c83465
4 files changed
+64 -1

No files matched your search

@@ -49,6 +49,7 @@ type recordingBackend struct {
// `ModelIdentity`; the spec below asserts they are populated independently.
soundGenModelField string
soundGenParams map[string]string
soundGenOutput []byte
}
func newRecordingBackend() *recordingBackend {
@@ -73,6 +74,11 @@ func (r *recordingBackend) SoundGeneration(_ context.Context, in *pb.SoundGenera
r.record("SoundGeneration", in)
r.soundGenModelField = in.GetModel()
r.soundGenParams = in.GetParams()
if r.soundGenOutput != nil {
if err := os.WriteFile(in.GetDst(), r.soundGenOutput, 0o600); err != nil {
return nil, err
}
}
return &pb.Result{Success: true}, nil
}
+4 -1
View File
@@ -7,6 +7,7 @@ import (
"path/filepath"
"time"
"github.com/google/uuid"
"github.com/mudler/LocalAI/core/config"
"github.com/mudler/LocalAI/core/trace"
"github.com/mudler/LocalAI/pkg/grpc/proto"
@@ -56,7 +57,9 @@ func SoundGeneration(
return "", nil, fmt.Errorf("failed creating audio directory: %s", err)
}
fileName := utils.GenerateUniqueFileName(audioDir, "sound_generation", ".wav")
// Format normalization can rename .wav to .flac, so existence of the
// .wav path cannot determine whether an earlier recording owns this stem.
fileName := utils.GenerateUniqueFileName(audioDir, "sound_generation_"+uuid.NewString(), ".wav")
filePath := filepath.Join(audioDir, fileName)
if filePath, err = filepath.Abs(filePath); err != nil {
return "", nil, fmt.Errorf("failed resolving sound generation path: %w", err)
@@ -0,0 +1,50 @@
package backend_test
import (
"context"
"os"
"path/filepath"
"github.com/mudler/LocalAI/core/backend"
"github.com/mudler/LocalAI/core/config"
"github.com/mudler/LocalAI/pkg/audio"
"github.com/mudler/LocalAI/pkg/system"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("sound generation history", func() {
It("preserves previous recordings after FLAC extension normalization", func() {
rec := newRecordingBackend()
loader := newRecordingLoader(rec)
appCfg := config.NewApplicationConfig(config.WithSystemState(&system.SystemState{}))
appCfg.GeneratedContentDir = GinkgoT().TempDir()
cfg := identityModelCfg()
generate := func(content []byte) string {
rec.soundGenOutput = content
path, _, err := backend.SoundGeneration(context.Background(), "music", nil, nil, nil, nil, nil, nil, "", "", nil, "", "", "", nil, nil, loader, appCfg, cfg)
Expect(err).ToNot(HaveOccurred())
// The endpoint detects the backend's format and renames its .wav
// destination. Only the FLAC signature is needed for detection.
finalPath, contentType := audio.NormalizeAudioFile(path)
Expect(contentType).To(Equal("audio/flac"))
Expect(filepath.Ext(finalPath)).To(Equal(".flac"))
return finalPath
}
firstAudio := []byte("fLaC first recording fixture")
secondAudio := []byte("fLaC second recording fixture")
firstPath := generate(firstAudio)
secondPath := generate(secondAudio)
Expect(secondPath).ToNot(Equal(firstPath))
first, err := os.ReadFile(firstPath)
Expect(err).ToNot(HaveOccurred())
Expect(first).To(Equal(firstAudio))
second, err := os.ReadFile(secondPath)
Expect(err).ToNot(HaveOccurred())
Expect(second).To(Equal(secondAudio))
})
})
+4
View File
@@ -384,6 +384,10 @@ curl http://localhost:8080/v1/sound-generation -H "Content-Type: application/jso
The `/v1/sound-generation` endpoint is compatible with the [ElevenLabs sound generation API](https://elevenlabs.io/docs/api-reference/sound-generation) and can produce music, sound effects, and other audio content. It responds with a binary audio file and the appropriate `Content-Type` header (for example `audio/wav`, `audio/mpeg`, `audio/flac`, or `audio/ogg`). The request body is JSON and supports two usage modes.
Each generated sound has a unique filename. The filename remains unique when
LocalAI changes its extension to match the audio format, so later generations
do not overwrite earlier recordings in the WebUI history.
**Simple mode:**
| Parameter | Type | Required | Description |