firered-tts3 / tests /test_reference_asr.py
LPDoctor's picture
fix: decode M4A reference audio with FFmpeg
82c62b3
Raw History Blame Contribute Delete
5.51 kB
import shutil
import subprocess
import tempfile
import unittest
from pathlib import Path
import numpy as np
import soundfile as sf
import torch
from fireredtts3.reference_asr import (
QWEN3_ASR_MODEL_ID,
transcribe_audio,
transcribe_reference_audio,
)
REFERENCE_TRANSCRIPT = "The reference transcript is filled automatically."
class _FakeBatch(dict):
def to(self, device, dtype):
self.to_args = (device, dtype)
return self
class _FakeProcessor:
def __init__(self, decoded_text=REFERENCE_TRANSCRIPT):
self.decoded_text = decoded_text
self.audio = None
self.sampling_rate = None
self.decode_args = None
def apply_transcription_request(self, audio, processor_kwargs):
self.audio = audio
self.sampling_rate = processor_kwargs["sampling_rate"]
return _FakeBatch({"input_ids": torch.tensor([[10, 11, 12]])})
def decode(self, generated_ids, return_format):
self.decode_args = (generated_ids, return_format)
return [self.decoded_text]
class _FakeModel:
device = torch.device("cpu")
dtype = torch.float32
def __init__(self):
self.generate_kwargs = None
def generate(self, **kwargs):
self.generate_kwargs = kwargs
return torch.tensor([[10, 11, 12, 20, 21]])
class TranscribeAudioUnitTests(unittest.TestCase):
def test_returns_only_the_stripped_reference_transcript(self):
processor = _FakeProcessor(f" {REFERENCE_TRANSCRIPT} ")
model = _FakeModel()
transcript = transcribe_audio(
np.zeros(16000, dtype=np.float32),
16000,
processor=processor,
model=model,
)
self.assertEqual(transcript, REFERENCE_TRANSCRIPT)
self.assertEqual(processor.decode_args[1], "transcription_only")
self.assertEqual(processor.decode_args[0].tolist(), [[20, 21]])
self.assertEqual(model.generate_kwargs["max_new_tokens"], 256)
self.assertFalse(model.generate_kwargs["do_sample"])
def test_rejects_an_empty_model_transcript(self):
with self.assertRaisesRegex(RuntimeError, "empty transcript"):
transcribe_audio(
np.zeros(16000, dtype=np.float32),
16000,
processor=_FakeProcessor(" "),
model=_FakeModel(),
)
def test_rejects_invalid_audio_input(self):
with self.assertRaisesRegex(ValueError, "mono waveform"):
transcribe_audio(
np.zeros((1, 16000), dtype=np.float32),
16000,
processor=_FakeProcessor(),
model=_FakeModel(),
)
class ReferenceTranscriptIntegrationTests(unittest.TestCase):
def test_uploaded_audio_is_transcribed_into_reference_transcript(self):
processor = _FakeProcessor()
model = _FakeModel()
with tempfile.TemporaryDirectory() as tmpdir:
audio_path = Path(tmpdir) / "reference.wav"
sf.write(audio_path, np.zeros(8000, dtype=np.float32), 8000)
transcript = transcribe_reference_audio(
audio_path,
processor=processor,
model=model,
)
self.assertEqual(transcript, REFERENCE_TRANSCRIPT)
self.assertEqual(processor.sampling_rate, 16000)
self.assertEqual(processor.audio.shape, (16000,))
def test_reference_audio_is_limited_to_the_clone_prompt_duration(self):
processor = _FakeProcessor()
with tempfile.TemporaryDirectory() as tmpdir:
audio_path = Path(tmpdir) / "long-reference.wav"
sf.write(audio_path, np.zeros(25 * 8000, dtype=np.float32), 8000)
transcribe_reference_audio(
audio_path,
processor=processor,
model=_FakeModel(),
max_seconds=20.0,
)
self.assertEqual(processor.audio.shape, (20 * 16000,))
@unittest.skipUnless(shutil.which("ffmpeg"), "FFmpeg is required for M4A coverage")
def test_m4a_upload_is_transcribed_into_reference_transcript(self):
processor = _FakeProcessor()
model = _FakeModel()
with tempfile.TemporaryDirectory() as tmpdir:
wav_path = Path(tmpdir) / "reference.wav"
m4a_path = Path(tmpdir) / "reference clip.m4a"
time = np.arange(8000, dtype=np.float32) / 8000
sf.write(wav_path, 0.25 * np.sin(2 * np.pi * 440 * time), 8000)
subprocess.run(
[
"ffmpeg",
"-nostdin",
"-loglevel",
"error",
"-y",
"-i",
str(wav_path),
"-c:a",
"aac",
str(m4a_path),
],
check=True,
)
transcript = transcribe_reference_audio(
m4a_path,
processor=processor,
model=model,
)
self.assertEqual(transcript, REFERENCE_TRANSCRIPT)
self.assertEqual(processor.sampling_rate, 16000)
self.assertGreaterEqual(processor.audio.shape[0], 16000)
self.assertLess(processor.audio.shape[0], 17000)
def test_uses_the_official_hugging_face_qwen3_asr_checkpoint(self):
self.assertEqual(QWEN3_ASR_MODEL_ID, "Qwen/Qwen3-ASR-0.6B-hf")
if __name__ == "__main__":
unittest.main()