fix: write and read audio with soundfile instead of torchaudio
torchaudio 2.11 routes torchaudio.save()/load() through TorchCodec and ignores the `backend` argument, so every generation died at the save step with "ImportError: TorchCodec is required for save_with_torchcodec" after the diffusion had already finished. Reference-audio loading (audio2audio, repaint, extend) and the training dataset loader hit the same wall. soundfile is already a required dependency and covers all four output formats the UI offers, so use it directly rather than pulling in TorchCodec and its native FFmpeg stack: - pipeline_ace_step.save_wav_file(): sf.write(), transposing (channels, samples) -> (samples, channels); drops the now-unused torchaudio import - MusicDCAE.load_audio() and text2music_dataset: sf.read(dtype=float32, always_2d=True), transposed back to (channels, samples) torchaudio is still used for Resample/MelScale transforms, which are unaffected. Verified end to end: 10s generation on an RTX 3060 in 9.7s, output is valid non-silent 48kHz stereo; load_audio round-trips it; wav/mp3/ogg/ flac all write and read back. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
6e3273d049
commit
0584397884
@@ -10,6 +10,7 @@ import os
|
|||||||
import torch
|
import torch
|
||||||
from diffusers import AutoencoderDC
|
from diffusers import AutoencoderDC
|
||||||
import torchaudio
|
import torchaudio
|
||||||
|
import soundfile as sf
|
||||||
import torchvision.transforms as transforms
|
import torchvision.transforms as transforms
|
||||||
from diffusers.models.modeling_utils import ModelMixin
|
from diffusers.models.modeling_utils import ModelMixin
|
||||||
from diffusers.loaders import FromOriginalModelMixin
|
from diffusers.loaders import FromOriginalModelMixin
|
||||||
@@ -60,7 +61,11 @@ class MusicDCAE(ModelMixin, ConfigMixin, FromOriginalModelMixin):
|
|||||||
self.shift_factor = -1.9091
|
self.shift_factor = -1.9091
|
||||||
|
|
||||||
def load_audio(self, audio_path):
|
def load_audio(self, audio_path):
|
||||||
audio, sr = torchaudio.load(audio_path)
|
# Read with soundfile rather than torchaudio.load(): since torchaudio
|
||||||
|
# 2.11 the latter routes I/O through TorchCodec, an extra native
|
||||||
|
# dependency we do not require.
|
||||||
|
data, sr = sf.read(audio_path, dtype="float32", always_2d=True)
|
||||||
|
audio = torch.from_numpy(data.T)
|
||||||
if audio.shape[0] == 1:
|
if audio.shape[0] == 1:
|
||||||
audio = audio.repeat(2, 1)
|
audio = audio.repeat(2, 1)
|
||||||
return audio, sr
|
return audio, sr
|
||||||
@@ -362,7 +367,8 @@ class MusicDCAE(ModelMixin, ConfigMixin, FromOriginalModelMixin):
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
||||||
audio, sr = torchaudio.load("test.wav")
|
_data, sr = sf.read("test.wav", dtype="float32", always_2d=True)
|
||||||
|
audio = torch.from_numpy(_data.T)
|
||||||
audio_lengths = torch.tensor([audio.shape[1]])
|
audio_lengths = torch.tensor([audio.shape[1]])
|
||||||
audios = audio.unsqueeze(0)
|
audios = audio.unsqueeze(0)
|
||||||
|
|
||||||
@@ -378,5 +384,5 @@ if __name__ == "__main__":
|
|||||||
print("latents shape: ", latents.shape)
|
print("latents shape: ", latents.shape)
|
||||||
print("latent_lengths: ", latent_lengths)
|
print("latent_lengths: ", latent_lengths)
|
||||||
print("sr: ", sr)
|
print("sr: ", sr)
|
||||||
torchaudio.save("test_reconstructed.wav", pred_wavs[0], sr)
|
sf.write("test_reconstructed.wav", pred_wavs[0].float().cpu().transpose(0, 1).numpy(), sr)
|
||||||
print("test_reconstructed.wav")
|
print("test_reconstructed.wav")
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import os
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
|
import soundfile as sf
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from tqdm import tqdm
|
from tqdm import tqdm
|
||||||
import json
|
import json
|
||||||
@@ -46,7 +47,6 @@ from acestep.apg_guidance import (
|
|||||||
cfg_zero_star,
|
cfg_zero_star,
|
||||||
cfg_double_condition_forward,
|
cfg_double_condition_forward,
|
||||||
)
|
)
|
||||||
import torchaudio
|
|
||||||
from .cpu_offload import cpu_offload
|
from .cpu_offload import cpu_offload
|
||||||
|
|
||||||
|
|
||||||
@@ -1405,13 +1405,17 @@ class ACEStepPipeline:
|
|||||||
else:
|
else:
|
||||||
output_path_wav = save_path
|
output_path_wav = save_path
|
||||||
|
|
||||||
target_wav = target_wav.float()
|
target_wav = target_wav.float().cpu()
|
||||||
backend = "soundfile"
|
logger.info(f"Saving audio to {output_path_wav}")
|
||||||
if format == "ogg":
|
# Write with soundfile rather than torchaudio.save(): since torchaudio
|
||||||
backend = "sox"
|
# 2.11 the latter ignores the `backend` argument and routes everything
|
||||||
logger.info(f"Saving audio to {output_path_wav} using backend {backend}")
|
# through TorchCodec, an extra native dependency we do not require.
|
||||||
torchaudio.save(
|
# soundfile expects (samples, channels), torch tensors are (channels, samples).
|
||||||
output_path_wav, target_wav, sample_rate=sample_rate, format=format, backend=backend
|
sf.write(
|
||||||
|
output_path_wav,
|
||||||
|
target_wav.transpose(0, 1).numpy(),
|
||||||
|
sample_rate,
|
||||||
|
format=format.upper(),
|
||||||
)
|
)
|
||||||
return output_path_wav
|
return output_path_wav
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from loguru import logger
|
|||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
import torchaudio
|
import torchaudio
|
||||||
|
import soundfile as sf
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import re
|
import re
|
||||||
from acestep.language_segmentation import LangSegment
|
from acestep.language_segmentation import LangSegment
|
||||||
@@ -398,7 +399,10 @@ class Text2MusicDataset(Dataset):
|
|||||||
filename = item["filename"]
|
filename = item["filename"]
|
||||||
sr = 48000
|
sr = 48000
|
||||||
try:
|
try:
|
||||||
audio, sr = torchaudio.load(filename)
|
# soundfile instead of torchaudio.load(): torchaudio 2.11 routes
|
||||||
|
# I/O through TorchCodec, an extra native dependency.
|
||||||
|
_data, sr = sf.read(filename, dtype="float32", always_2d=True)
|
||||||
|
audio = torch.from_numpy(_data.T)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to load audio {item}: {e}")
|
logger.error(f"Failed to load audio {item}: {e}")
|
||||||
return None
|
return None
|
||||||
|
|||||||
Reference in New Issue
Block a user