audioshrink-ai-processor / ffmpeg_utils.py
jobuss's picture
Deploy AudioShrink AI processor
0184358 verified
Raw
History Blame Contribute Delete
2.66 kB
import asyncio
import math
import os
import subprocess
from pathlib import Path
class FfmpegError(RuntimeError):
pass
async def run_ffmpeg(args: list[str]) -> None:
process = await asyncio.create_subprocess_exec(
*args,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
stdout, stderr = await process.communicate()
if process.returncode != 0:
detail = stderr.decode("utf-8", errors="replace")[-4000:] or stdout.decode("utf-8", errors="replace")[-4000:]
raise FfmpegError(detail)
def probe_duration_seconds(path: Path) -> float | None:
result = subprocess.run(
[
"ffprobe",
"-v",
"error",
"-show_entries",
"format=duration",
"-of",
"default=noprint_wrappers=1:nokey=1",
str(path),
],
capture_output=True,
text=True,
check=False,
)
if result.returncode != 0:
return None
try:
return float(result.stdout.strip())
except ValueError:
return None
async def compress_audio(input_path: Path, output_path: Path) -> None:
await run_ffmpeg(
[
"ffmpeg",
"-y",
"-i",
str(input_path),
"-vn",
"-ar",
"16000",
"-ac",
"1",
"-c:a",
"libmp3lame",
"-b:a",
"32k",
str(output_path),
]
)
async def chunk_audio(input_path: Path, output_dir: Path, chunk_duration_seconds: int) -> list[Path]:
output_dir.mkdir(parents=True, exist_ok=True)
output_pattern = output_dir / "chunk_%03d.mp3"
await run_ffmpeg(
[
"ffmpeg",
"-y",
"-i",
str(input_path),
"-f",
"segment",
"-segment_time",
str(chunk_duration_seconds),
"-c",
"copy",
str(output_pattern),
]
)
return sorted(output_dir.glob("chunk_*.mp3"))
def size_mb(path: Path) -> float:
return os.path.getsize(path) / (1024 * 1024)
def needs_chunking(path: Path, target_size_mb: int) -> bool:
return size_mb(path) > target_size_mb
def estimated_chunk_duration(duration_seconds: float | None, final_size_mb: float, target_size_mb: int, fallback_seconds: int) -> int:
if not duration_seconds or final_size_mb <= 0 or final_size_mb <= target_size_mb:
return fallback_seconds
chunks = max(2, math.ceil(final_size_mb / target_size_mb))
return max(60, math.floor(duration_seconds / chunks))