mirror of
https://github.com/mudler/LocalAI.git
synced 2026-10-28 09:08:54 -04:00
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:
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
|
||||
}
|
||||
|
||||
|
||||
@@ -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))
|
||||
})
|
||||
})
|
||||
@@ -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 |
|
||||
|
||||
Reference in new issue
Block a user