2025-02-24 22:48:23 +08:00
|
|
|
|
import os
|
2025-03-04 22:21:23 +08:00
|
|
|
|
import time
|
2025-02-24 22:48:23 +08:00
|
|
|
|
from pydub import AudioSegment
|
|
|
|
|
import eyed3
|
|
|
|
|
from mutagen.flac import FLAC
|
2025-03-04 22:21:23 +08:00
|
|
|
|
from concurrent.futures import ThreadPoolExecutor
|
|
|
|
|
|
|
|
|
|
def safe_remove(file_path, max_retries=3):
|
|
|
|
|
"""安全删除文件,带有重试机制"""
|
|
|
|
|
for i in range(max_retries):
|
|
|
|
|
try:
|
|
|
|
|
os.remove(file_path)
|
|
|
|
|
return True
|
|
|
|
|
except PermissionError:
|
|
|
|
|
time.sleep(0.1 * (i+1))
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
def convert_sample_width(audio):
|
|
|
|
|
"""处理非常规sample width"""
|
|
|
|
|
try:
|
|
|
|
|
if audio.sample_width not in [1, 2, 3, 4]:
|
|
|
|
|
return audio.set_sample_width(2)
|
|
|
|
|
return audio
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"采样位数修复失败: {str(e)}")
|
|
|
|
|
return None
|
2025-02-24 22:48:23 +08:00
|
|
|
|
|
|
|
|
|
def convert_and_clean(wav_path):
|
2025-03-04 22:21:23 +08:00
|
|
|
|
flac_path = None
|
2025-02-24 22:48:23 +08:00
|
|
|
|
try:
|
2025-03-04 22:21:23 +08:00
|
|
|
|
audio = AudioSegment.from_file(wav_path, format='wav')
|
|
|
|
|
audio = convert_sample_width(audio)
|
|
|
|
|
if audio is None:
|
|
|
|
|
raise ValueError("不支持的音频格式")
|
|
|
|
|
|
2025-02-24 22:48:23 +08:00
|
|
|
|
tags = {}
|
2025-03-04 22:21:23 +08:00
|
|
|
|
try:
|
|
|
|
|
audiofile = eyed3.load(wav_path)
|
|
|
|
|
if audiofile and audiofile.tag:
|
|
|
|
|
tags = {
|
|
|
|
|
'ARTIST': audiofile.tag.artist or '',
|
|
|
|
|
'ALBUM': audiofile.tag.album or '',
|
|
|
|
|
'TITLE': audiofile.tag.title or '',
|
|
|
|
|
'GENRE': audiofile.tag.genre.name if audiofile.tag.genre else '',
|
|
|
|
|
'DATE': str(audiofile.tag.getBestDate()) or '',
|
|
|
|
|
'TRACKNUMBER': str(audiofile.tag.track_num[0]) if audiofile.tag.track_num else ''
|
|
|
|
|
}
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"元数据读取警告: {str(e)}")
|
2025-02-24 22:48:23 +08:00
|
|
|
|
|
|
|
|
|
flac_path = os.path.splitext(wav_path)[0] + '.flac'
|
2025-03-04 22:21:23 +08:00
|
|
|
|
|
|
|
|
|
with open(flac_path, 'wb') as f:
|
|
|
|
|
audio.export(f, format='flac')
|
2025-02-24 22:48:23 +08:00
|
|
|
|
|
|
|
|
|
if tags:
|
|
|
|
|
flac_audio = FLAC(flac_path)
|
|
|
|
|
flac_audio.delete()
|
|
|
|
|
flac_audio.update(tags)
|
|
|
|
|
flac_audio.save()
|
|
|
|
|
|
|
|
|
|
if os.path.exists(flac_path) and os.path.getsize(flac_path) > 0:
|
2025-03-04 22:21:23 +08:00
|
|
|
|
if safe_remove(wav_path):
|
|
|
|
|
print(f"成功转换并删除: {wav_path}")
|
|
|
|
|
return True
|
2025-02-24 22:48:23 +08:00
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"处理失败 {wav_path}: {str(e)}")
|
2025-03-04 22:21:23 +08:00
|
|
|
|
if flac_path and os.path.exists(flac_path):
|
|
|
|
|
safe_remove(flac_path)
|
2025-02-24 22:48:23 +08:00
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
def batch_convert():
|
|
|
|
|
deleted_count = 0
|
|
|
|
|
error_count = 0
|
|
|
|
|
|
2025-03-04 22:21:23 +08:00
|
|
|
|
# 自动根据CPU核心数设置线程数(核心数*2)
|
|
|
|
|
max_workers = os.cpu_count() * 2 or 4
|
|
|
|
|
|
|
|
|
|
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
|
|
|
futures = []
|
|
|
|
|
for root, _, files in os.walk('.'):
|
|
|
|
|
for file in files:
|
|
|
|
|
if file.lower().endswith('.wav'):
|
|
|
|
|
full_path = os.path.join(root, file)
|
|
|
|
|
futures.append(executor.submit(convert_and_clean, full_path))
|
|
|
|
|
|
|
|
|
|
for future in futures:
|
|
|
|
|
try:
|
|
|
|
|
if future.result():
|
2025-02-24 22:48:23 +08:00
|
|
|
|
deleted_count += 1
|
|
|
|
|
else:
|
|
|
|
|
error_count += 1
|
2025-03-04 22:21:23 +08:00
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"线程执行出错: {str(e)}")
|
|
|
|
|
error_count += 1
|
2025-02-24 22:48:23 +08:00
|
|
|
|
|
|
|
|
|
print(f"\n转换总结:\n成功转换并删除: {deleted_count} 个文件\n失败文件: {error_count} 个")
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
print("=== WAV转FLAC工具 (增强安全版) ===")
|
2025-03-04 22:21:23 +08:00
|
|
|
|
print(f"当前CPU核心数: {os.cpu_count()}")
|
|
|
|
|
print("开始批量转换...\n")
|
|
|
|
|
batch_convert()
|
|
|
|
|
input("\n按任意键退出...")
|