Skip to content

Commit a77972b

Browse files
abidlabsclaudegradio-pr-bot
authored
Remove pydub dependency (#529)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
1 parent 7d1c0b9 commit a77972b

6 files changed

Lines changed: 71 additions & 26 deletions

File tree

.changeset/solid-sides-look.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"trackio": patch
3+
---
4+
5+
feat:Remove pydub dependency

examples/crash-and-resume-same-run-name.py

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,8 @@
2222
import argparse
2323
import math
2424
import uuid
25-
import warnings
2625

27-
warnings.filterwarnings(
28-
"ignore",
29-
category=SyntaxWarning,
30-
module=r"pydub\.utils",
31-
)
32-
33-
import trackio # noqa: E402
26+
import trackio
3427

3528
DEFAULT_PROJECT = f"crash-and-resume-demo-{uuid.uuid4().hex[:8]}"
3629
DEFAULT_RUN_NAME = "trainer-job-42"

pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ dependencies = [
2121
"numpy<3.0.0",
2222
"pillow<13.0.0",
2323
"orjson>=3.0,<4.0.0",
24-
"pydub<1.0.0",
2524
"tomli>=2.0.0; python_version < '3.11'",
2625
]
2726
classifiers = [

tests/unit/test_audio_writer.py

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
import json
12
import math
23
import shutil
4+
import subprocess
35
import wave
46
from pathlib import Path
57

@@ -34,6 +36,29 @@ def _has_ffmpeg() -> bool:
3436
return shutil.which("ffmpeg") is not None
3537

3638

39+
def _has_ffprobe() -> bool:
40+
return shutil.which("ffprobe") is not None
41+
42+
43+
def _probe_audio(path: Path) -> tuple[int, int]:
44+
out = subprocess.check_output(
45+
[
46+
"ffprobe",
47+
"-v",
48+
"error",
49+
"-select_streams",
50+
"a:0",
51+
"-show_entries",
52+
"stream=sample_rate,channels",
53+
"-of",
54+
"json",
55+
str(path),
56+
]
57+
)
58+
stream = json.loads(out)["streams"][0]
59+
return int(stream["sample_rate"]), int(stream["channels"])
60+
61+
3762
@pytest.mark.parametrize("channels", [1, 2])
3863
def test_write_wav_mono_and_stereo_with_float_normalization(
3964
tmp_path: Path, channels: int
@@ -56,11 +81,11 @@ def test_write_wav_mono_and_stereo_with_float_normalization(
5681
assert 32000 <= max_abs <= 32767
5782

5883

59-
@pytest.mark.skipif(not _has_ffmpeg(), reason="ffmpeg not available")
84+
@pytest.mark.skipif(
85+
not (_has_ffmpeg() and _has_ffprobe()), reason="ffmpeg/ffprobe not available"
86+
)
6087
@pytest.mark.parametrize("channels", [1, 2])
6188
def test_write_mp3_mono_and_stereo(tmp_path: Path, channels: int) -> None:
62-
from pydub import AudioSegment
63-
6489
mono = _tone(0.1, 440.0, SAMPLE_RATE, amp=0.5)
6590
data = mono if channels == 1 else np.stack([mono, mono], axis=1)
6691

@@ -69,9 +94,10 @@ def test_write_mp3_mono_and_stereo(tmp_path: Path, channels: int) -> None:
6994
data=data, sample_rate=SAMPLE_RATE, filename=out, format="mp3"
7095
)
7196

72-
seg = AudioSegment.from_file(str(out), format="mp3")
73-
assert seg.frame_rate == SAMPLE_RATE
74-
assert seg.channels == channels
97+
assert out.exists() and out.stat().st_size > 0
98+
sr, ch = _probe_audio(out)
99+
assert sr == SAMPLE_RATE
100+
assert ch == channels
75101

76102

77103
@pytest.mark.parametrize(

trackio/media/audio.py

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import os
22
import shutil
3+
import subprocess
34
import warnings
5+
import wave
46
from pathlib import Path
57
from typing import Literal
68

79
import numpy as np
8-
from pydub import AudioSegment
910

1011
from trackio.media.media import TrackioMedia
1112
from trackio.media.utils import check_ffmpeg_installed, check_path
@@ -156,12 +157,33 @@ def write_audio(
156157
check_ffmpeg_installed()
157158

158159
channels = 1 if pcm.ndim == 1 else pcm.shape[1]
159-
audio = AudioSegment(
160-
pcm.tobytes(),
161-
frame_rate=sample_rate,
162-
sample_width=2, # int16
163-
channels=channels,
164-
)
165-
166-
file = audio.export(str(filename), format=format)
167-
file.close()
160+
pcm_bytes = pcm.tobytes()
161+
162+
if format == "wav":
163+
with wave.open(str(filename), "wb") as wf:
164+
wf.setnchannels(channels)
165+
wf.setsampwidth(2)
166+
wf.setframerate(sample_rate)
167+
wf.writeframes(pcm_bytes)
168+
else:
169+
cmd = [
170+
"ffmpeg",
171+
"-y",
172+
"-loglevel",
173+
"error",
174+
"-f",
175+
"s16le",
176+
"-ar",
177+
str(sample_rate),
178+
"-ac",
179+
str(channels),
180+
"-i",
181+
"pipe:0",
182+
str(filename),
183+
]
184+
proc = subprocess.run(
185+
cmd, input=pcm_bytes, capture_output=True, check=False
186+
)
187+
if proc.returncode != 0:
188+
stderr = proc.stderr.decode("utf-8", errors="replace").strip()
189+
raise RuntimeError(f"ffmpeg failed to encode {format} audio: {stderr}")

trackio/media/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ def check_ffmpeg_installed() -> None:
2020
"""Raise an error if ffmpeg is not available on the system PATH."""
2121
if shutil.which("ffmpeg") is None:
2222
raise RuntimeError(
23-
"ffmpeg is required to write video but was not found on your system. "
23+
"ffmpeg is required to write this media format but was not found on your system. "
2424
"Please install ffmpeg and ensure it is available on your PATH."
2525
)
2626

0 commit comments

Comments
 (0)