be1z / audio_processor.py
TDN-M's picture
Upload 7 files
43b61a2 verified
import os
import logging
from typing import List, Optional
from pydub import AudioSegment
from pydub.effects import normalize
import uuid
logger = logging.getLogger(__name__)
class AudioProcessor:
def __init__(self):
self.supported_formats = ['mp3', 'wav', 'm4a', 'aac', 'ogg']
async def combine_audios(
self,
audio_paths: List[str],
background_music_path: Optional[str] = None,
output_dir: str = "temp",
background_volume: float = 0.3,
fade_duration: int = 1000 # milliseconds
) -> str:
"""
Tổng hợp các file audio thành một file duy nhất
Args:
audio_paths: Danh sách đường dẫn audio các cảnh
background_music_path: Đường dẫn nhạc nền (optional)
output_dir: Thư mục output
background_volume: Âm lượng nhạc nền (0.0 - 1.0)
fade_duration: Thời gian fade in/out (ms)
Returns:
Đường dẫn file audio đã tổng hợp
"""
try:
logger.info(f"Bắt đầu tổng hợp {len(audio_paths)} file audio")
# Kiểm tra file input
valid_audio_paths = []
for path in audio_paths:
if os.path.exists(path):
valid_audio_paths.append(path)
logger.info(f"Audio hợp lệ: {path}")
else:
logger.warning(f"Audio không tồn tại: {path}")
if not valid_audio_paths:
raise ValueError("Không có file audio hợp lệ nào")
# Load và ghép các audio cảnh
combined_audio = None
total_duration = 0
for i, audio_path in enumerate(valid_audio_paths):
logger.info(f"Xử lý audio {i+1}/{len(valid_audio_paths)}: {audio_path}")
# Load audio với format tự động detect
audio_segment = AudioSegment.from_file(audio_path)
# Normalize audio
audio_segment = normalize(audio_segment)
# Thêm fade in/out cho audio đầu và cuối
if i == 0: # Audio đầu tiên
audio_segment = audio_segment.fade_in(fade_duration)
if i == len(valid_audio_paths) - 1: # Audio cuối cùng
audio_segment = audio_segment.fade_out(fade_duration)
# Ghép audio
if combined_audio is None:
combined_audio = audio_segment
else:
combined_audio = combined_audio + audio_segment
total_duration += len(audio_segment)
logger.info(f"Đã thêm audio {i+1}, tổng thời lượng: {total_duration/1000:.2f}s")
logger.info(f"Hoàn thành ghép audio cảnh, tổng thời lượng: {total_duration/1000:.2f}s")
# Thêm nhạc nền nếu có
if background_music_path and os.path.exists(background_music_path):
logger.info("Đang thêm nhạc nền...")
# Load nhạc nền
background_music = AudioSegment.from_file(background_music_path)
# Điều chỉnh âm lượng nhạc nền
background_music = background_music - (20 - int(background_volume * 20)) # Giảm dB
# Lặp lại nhạc nền nếu cần
if len(background_music) < len(combined_audio):
# Tính số lần lặp cần thiết
repeat_times = (len(combined_audio) // len(background_music)) + 1
background_music = background_music * repeat_times
# Cắt nhạc nền cho khớp với audio chính
background_music = background_music[:len(combined_audio)]
# Thêm fade in/out cho nhạc nền
background_music = background_music.fade_in(fade_duration * 2).fade_out(fade_duration * 2)
# Mix audio chính với nhạc nền
combined_audio = combined_audio.overlay(background_music)
logger.info("Đã thêm nhạc nền thành công")
# Tạo tên file output
output_filename = f"combined_audio_{uuid.uuid4().hex[:8]}.wav"
output_path = os.path.join(output_dir, output_filename)
# Export file kết quả
combined_audio.export(
output_path,
format="wav",
parameters=["-ac", "2", "-ar", "44100"] # Stereo, 44.1kHz
)
logger.info(f"Đã xuất file audio tổng hợp: {output_path}")
logger.info(f"Thời lượng cuối cùng: {len(combined_audio)/1000:.2f}s")
return output_path
except Exception as e:
logger.error(f"Lỗi khi tổng hợp audio: {str(e)}")
raise
def get_audio_info(self, audio_path: str) -> dict:
"""
Lấy thông tin về file audio
"""
try:
audio = AudioSegment.from_file(audio_path)
return {
"duration_seconds": len(audio) / 1000,
"channels": audio.channels,
"frame_rate": audio.frame_rate,
"sample_width": audio.sample_width,
"file_size_mb": os.path.getsize(audio_path) / (1024 * 1024)
}
except Exception as e:
logger.error(f"Lỗi khi lấy thông tin audio {audio_path}: {str(e)}")
return {}
def validate_audio_file(self, audio_path: str) -> bool:
"""
Kiểm tra tính hợp lệ của file audio
"""
try:
if not os.path.exists(audio_path):
return False
# Kiểm tra extension
ext = audio_path.lower().split('.')[-1]
if ext not in self.supported_formats:
return False
# Thử load file
AudioSegment.from_file(audio_path)
return True
except Exception:
return False