feat(diffusers): add AudioLDM2 generation

Expose diffusers audio pipelines through the existing sound-generation RPC. AudioLDM2 can now return PCM WAV output from the model gallery without a separate backend.

Assisted-by: Codex:gpt-5
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
This commit is contained in:
Ettore Di Giacinto committed 2026-09-11 21:53:37 +00:00
1 parent 783556bc93
commit a15780858e
9 files changed
+198 -3

No files matched your search

+24
View File
@@ -0,0 +1,24 @@
import array
import sys
import wave
def write_pcm_wav(destination, samples, sampling_rate):
"""Write normalized floating-point audio samples as mono 16-bit PCM."""
pcm = array.array(
"h",
(
max(-32768, min(32767, round(float(sample) * 32768)))
for sample in samples
),
)
if pcm.itemsize != 2:
raise RuntimeError("16-bit PCM requires two-byte signed integers")
if sys.byteorder != "little":
pcm.byteswap()
with wave.open(destination, "wb") as output:
output.setnchannels(1)
output.setsampwidth(2)
output.setframerate(sampling_rate)
output.writeframes(pcm.tobytes())
+44
View File
@@ -26,6 +26,7 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'common'))
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'common'))
from grpc_auth import get_auth_interceptors
from model_utils import resolve_model_reference
from audio_utils import write_pcm_wav
# Import dynamic loader for pipeline discovery
@@ -903,6 +904,49 @@ class BackendServicer(backend_pb2_grpc.BackendServicer):
return backend_pb2.Result(message="Media generated", success=True)
def SoundGeneration(self, request, context):
if not request.dst:
return backend_pb2.Result(success=False, message="request.dst is required")
prompt = request.text or request.caption
if not prompt:
return backend_pb2.Result(success=False, message="request.text is required")
try:
generation_options = dict(self.options)
if "num_inference_steps" in generation_options:
generation_options["num_inference_steps"] = int(
generation_options["num_inference_steps"]
)
generation_options["prompt"] = prompt
if request.HasField("duration"):
generation_options["audio_length_in_s"] = request.duration
if request.HasField("temperature"):
generation_options["guidance_scale"] = request.temperature
generated = self.pipe(**generation_options)
if not hasattr(generated, "audios") or len(generated.audios) == 0:
return backend_pb2.Result(
success=False,
message="The diffusers pipeline returned no audio",
)
samples = generated.audios[0]
if hasattr(samples, "reshape"):
samples = samples.reshape(-1)
if hasattr(samples, "tolist"):
samples = samples.tolist()
sampling_rate = getattr(
getattr(getattr(self.pipe, "vae", None), "config", None),
"sampling_rate",
16000,
)
write_pcm_wav(request.dst, samples, sampling_rate)
return backend_pb2.Result(success=True, message="Sound generated successfully")
except Exception as err:
return backend_pb2.Result(success=False, message=f"SoundGeneration error: {err}")
def UpscaleImage(self, request, context):
try:
if not request.src:
+64
View File
@@ -4,6 +4,9 @@ A test script to test the gRPC service and dynamic loader
import unittest
import subprocess
import time
import os
import tempfile
import wave
from unittest.mock import patch, MagicMock
# Import dynamic loader for testing (these don't need gRPC)
@@ -445,3 +448,64 @@ class TestDeviceSelection(unittest.TestCase):
def test_mps_overrides(self):
self.assertEqual(backend.select_device(False, None, True, False, True), "mps")
class TestWritePcmWav(unittest.TestCase):
def test_writes_clipped_float_samples_as_mono_pcm(self):
from audio_utils import write_pcm_wav
destination = tempfile.NamedTemporaryFile(suffix=".wav", delete=False)
destination.close()
try:
write_pcm_wav(destination.name, [0.0, 0.5, -0.5, 2.0], 16000)
with wave.open(destination.name, "rb") as generated:
self.assertEqual(generated.getframerate(), 16000)
self.assertEqual(generated.getnchannels(), 1)
self.assertEqual(generated.getsampwidth(), 2)
self.assertEqual(generated.getnframes(), 4)
self.assertEqual(
generated.readframes(4),
b"\x00\x00\x00@\x00\xc0\xff\x7f",
)
finally:
os.unlink(destination.name)
@unittest.skipUnless(GRPC_AVAILABLE, "gRPC modules not available")
class TestSoundGeneration(unittest.TestCase):
def test_maps_request_options_and_writes_pipeline_audio(self):
from backend import BackendServicer
service = BackendServicer.__new__(BackendServicer)
service.options = {"num_inference_steps": 200.0}
service.pipe = MagicMock()
service.pipe.return_value.audios = [[0.0, 0.5, -0.5]]
service.pipe.vae.config.sampling_rate = 16000
destination = tempfile.NamedTemporaryFile(suffix=".wav", delete=False)
destination.close()
try:
request = backend_pb2.SoundGenerationRequest(
text="ocean waves",
dst=destination.name,
duration=2.5,
temperature=0,
)
result = service.SoundGeneration(request, context=None)
self.assertTrue(result.success, result.message)
service.pipe.assert_called_once_with(
num_inference_steps=200,
prompt="ocean waves",
audio_length_in_s=2.5,
guidance_scale=0,
)
with wave.open(destination.name, "rb") as generated:
self.assertEqual(generated.getframerate(), 16000)
self.assertEqual(generated.getnframes(), 3)
finally:
os.unlink(destination.name)