""" 音频处理辅助工具 支持格式转换、音频切片等 """ import os import subprocess from typing import Tuple, Optional import numpy as np def get_audio_info(audio_path: str) -> dict: """ 获取音频文件信息 Returns: dict: { "duration": float, # 秒 "sample_rate": int, "channels": int, "format": str } """ try: import soundfile as sf info = sf.info(audio_path) return { "duration": info.duration, "sample_rate": info.samplerate, "channels": info.channels, "format": info.format } except ImportError: # 降级方案:使用 ffprobe import json cmd = [ "ffprobe", "-v", "quiet", "-print_format", "json", "-show_format", "-show_streams", audio_path ] result = subprocess.run(cmd, capture_output=True, text=True) data = json.loads(result.stdout) audio_stream = next((s for s in data.get("streams", []) if s.get("codec_type") == "audio"), {}) duration = float(data.get("format", {}).get("duration", 0)) return { "duration": duration, "sample_rate": int(audio_stream.get("sample_rate", 16000)), "channels": int(audio_stream.get("channels", 1)), "format": audio_stream.get("codec_name", "unknown") } def convert_to_wav(input_path: str, output_path: Optional[str] = None, sample_rate: int = 16000) -> str: """ 转换音频为 WAV 格式(16kHz 单声道) Args: input_path: 输入文件路径 output_path: 输出文件路径(可选) sample_rate: 采样率,默认 16000 Returns: 输出文件路径 """ if output_path is None: output_path = tempfile.NamedTemporaryFile(suffix=".wav", delete=False).name cmd = [ "ffmpeg", "-y", # 覆盖已存在的文件 "-i", input_path, "-ar", str(sample_rate), # 采样率 "-ac", "1", # 单声道 "-acodec", "pcm_s16le", # 16bit PCM output_path ] subprocess.run(cmd, capture_output=True, check=True) return output_path def slice_audio(audio_path: str, start: float, end: float, output_path: Optional[str] = None) -> str: """ 切片音频 Args: audio_path: 输入文件路径 start: 开始时间(秒) end: 结束时间(秒) output_path: 输出文件路径 Returns: 输出文件路径 """ if output_path is None: output_path = tempfile.NamedTemporaryFile(suffix=".wav", delete=False).name cmd = [ "ffmpeg", "-y", "-i", audio_path, "-ss", str(start), "-to", str(end), "-c", "copy", # 无损复制 output_path ] subprocess.run(cmd, capture_output=True, check=True) return output_path def merge_audio(audio_paths: list, output_path: Optional[str] = None) -> str: """ 合并多个音频文件 Args: audio_paths: 音频文件路径列表 output_path: 输出文件路径 Returns: 输出文件路径 """ if output_path is None: output_path = tempfile.NamedTemporaryFile(suffix=".wav", delete=False).name # 创建临时文件列表 list_file = tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False) for path in audio_paths: list_file.write(f"file '{path}'\n") list_file.close() cmd = [ "ffmpeg", "-y", "-f", "concat", "-safe", "0", "-i", list_file.name, "-c", "copy", output_path ] subprocess.run(cmd, capture_output=True, check=True) # 清理临时文件 os.unlink(list_file.name) return output_path def is_ffmpeg_available() -> bool: """检查 ffmpeg 是否可用""" try: subprocess.run(["ffmpeg", "-version"], capture_output=True, check=True) return True except (subprocess.CalledProcessError, FileNotFoundError): return False if __name__ == "__main__": # 测试 print("FFmpeg 可用:", is_ffmpeg_available())