我来帮你写一个MP3标签整理脚本,以下是基于Python的解决方案:

解决方案:MP3标签整理脚本
安装依赖
pip install mutagen
基础整理脚本
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
MP3标签整理工具
"""
import os
import re
from mutagen import File
from mutagen.easyid3 import EasyID3
from mutagen.id3 import ID3, ID3NoHeaderError
def clean_text(text):
"""清理文本,去除多余空格和特殊字符"""
if not text:
return text
text = re.sub(r'\s+', ' ', text)
text = text.strip()
text = text.replace('\x00', '')
return text
def get_audio_file_info(filepath):
"""获取音频文件信息"""
try:
audio = File(filepath)
if audio is None:
return None
return audio
except:
return None
def read_all_tags(filepath):
"""读取所有标签信息"""
try:
try:
audio = EasyID3(filepath)
except ID3NoHeaderError:
return "未找到ID3标签"
tags = {}
for key, value in audio.items():
if value:
tags[key] = value[0] if isinstance(value, list) else value
return tags
except Exception as e:
return f"读取标签失败: {str(e)}"
def write_tags(filepath, tag_dict):
"""写入标签信息"""
try:
try:
audio = EasyID3(filepath)
except ID3NoHeaderError:
audio = EasyID3()
for key, value in tag_dict.items():
if value:
audio[key] = [clean_text(str(value))]
audio.save()
return True
except Exception as e:
print(f"写入标签失败 [{filepath}]: {str(e)}")
return False
def organize_mp3_tags(filepath):
"""整理单个MP3文件的标签"""
tag_info = read_all_tags(filepath)
if isinstance(tag_info, str):
print(f"跳过文件: {filepath} - {tag_info}")
return False
print(f"\n处理文件: {os.path.basename(filepath)}")
print("当前标签:", tag_info)
# 清理所有标签
cleaned_tags = {}
for key, value in tag_info.items():
cleaned_tags[key] = clean_text(value)
# 写入清理后的标签
success = write_tags(filepath, cleaned_tags)
if success:
print("标签整理完成")
return success
def batch_process(directory, extensions=['.mp3']):
"""批量处理文件夹中的所有音频文件"""
success_count = 0
fail_count = 0
for root, dirs, files in os.walk(directory):
for file in files:
if any(file.endswith(ext) for ext in extensions):
filepath = os.path.join(root, file)
if organize_mp3_tags(filepath):
success_count += 1
else:
fail_count += 1
print(f"\n处理完成: 成功 {success_count} 个, 失败 {fail_count} 个")
def advanced_cleanup(filepath):
"""高级清理:修复常见问题"""
audio = get_audio_file_info(filepath)
if audio is None:
return False
# 示例:修复乱码(如GBK编码修复)
# 这需要根据实际情况调整
if audio.get('title'):
try:
title = audio['title'][0]
if isinstance(title, bytes):
title = title.decode('utf-8', errors='replace')
audio['title'] = [title]
except:
pass
audio.save()
return True
def automatically_fill_tags(filepath):
"""尝试从文件名智能填充标签"""
filename = os.path.basename(filepath)
# 常见命名格式: "艺术家 - 歌曲名.mp3"
match = re.match(r'(.+)\s*-\s*(.+)\.mp3$', filename)
if match:
artist = clean_text(match.group(1))
title = clean_text(match.group(2))
# 读取现有标签
tags = read_all_tags(filepath)
if isinstance(tags, str):
tags = {}
# 只在标签为空时填充
if not tags.get('artist'):
tags['artist'] = artist
if not tags.get('title'):
tags['title'] = title
write_tags(filepath, tags)
return True
return False
def delete_too_long_tags(filepath, max_length=100):
"""删除过长的标签值"""
try:
audio = EasyID3(filepath)
changes = False
for key in list(audio.keys()):
if len(str(audio[key][0])) > max_length:
del audio[key]
changes = True
print(f"删除过长的 '{key}' 标签")
if changes:
audio.save()
return True
except:
return False
# 使用示例
if __name__ == "__main__":
import sys
# 处理单个文件
if len(sys.argv) > 1:
for arg in sys.argv[1:]:
if os.path.isfile(arg):
organize_mp3_tags(arg)
elif os.path.isdir(arg):
batch_process(arg)
else:
# 默认处理当前目录
print("使用示例: python mp3_tag_organizer.py <文件或目录>")
print(" python mp3_tag_organizer.py ./my_mp3_folder")
# 测试代码
# batch_process('./', ['.mp3', '.flac'])
GUI版本(简化版)
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
MP3标签整理工具 - GUI版本
"""
import tkinter as tk
from tkinter import filedialog, messagebox, scrolledtext
import os
import threading
from mutagen.easyid3 import EasyID3
from mutagen.id3 import ID3NoHeaderError
class MP3TagOrganizerGUI:
def __init__(self, root):
self.root = root
self.root.title("MP3标签整理工具")
self.root.geometry("600x400")
# 文件选择
self.label = tk.Label(root, text="选择MP3文件或文件夹:")
self.label.pack(pady=10)
self.frame = tk.Frame(root)
self.frame.pack(pady=5)
self.btn_file = tk.Button(self.frame, text="选择文件", command=self.select_files)
self.btn_file.pack(side=tk.LEFT, padx=5)
self.btn_folder = tk.Button(self.frame, text="选择文件夹", command=self.select_folder)
self.btn_folder.pack(side=tk.LEFT, padx=5)
# 操作按钮
self.btn_clean = tk.Button(root, text="清理标签", command=self.clean_tags)
self.btn_clean.pack(pady=5)
self.btn_fill = tk.Button(root, text="从文件名填充", command=self.fill_from_filename)
self.btn_fill.pack(pady=5)
# 日志区域
self.log = scrolledtext.ScrolledText(root, height=15)
self.log.pack(pady=10, padx=10, fill=tk.BOTH, expand=True)
self.selected_files = []
def log_message(self, message):
self.log.insert(tk.END, f"{message}\n")
self.log.see(tk.END)
def select_files(self):
files = filedialog.askopenfilenames(
title="选择MP3文件",
filetypes=[("MP3 files", "*.mp3")]
)
if files:
self.selected_files = list(files)
self.log_message(f"已选择 {len(files)} 个文件")
def select_folder(self):
folder = filedialog.askdirectory(title="选择文件夹")
if folder:
self.selected_files = []
for root, dirs, files in os.walk(folder):
for file in files:
if file.endswith('.mp3'):
self.selected_files.append(os.path.join(root, file))
self.log_message(f"已选择文件夹,找到 {len(self.selected_files)} 个MP3文件")
def process_files(self, operation_name, operation_func):
if not self.selected_files:
messagebox.showwarning("警告", "请先选择文件或文件夹!")
return
self.log_message(f"\n开始{operation_name}...")
def process():
success = 0
fail = 0
for filepath in self.selected_files:
try:
if operation_func(filepath):
success += 1
else:
fail += 1
except Exception as e:
fail += 1
self.log_message(f"错误: {os.path.basename(filepath)} - {str(e)}")
self.log_message(f"\n{operation_name}完成: 成功 {success}, 失败 {fail}")
messagebox.showinfo("完成", f"{operation_name}完成!\n成功: {success}\n失败: {fail}")
thread = threading.Thread(target=process)
thread.start()
def clean_tags(self):
def clean(filepath):
try:
audio = EasyID3(filepath)
for key in list(audio.keys()):
if audio[key] and audio[key][0]:
audio[key] = [audio[key][0].strip()]
audio.save()
self.log_message(f"已清理: {os.path.basename(filepath)}")
return True
except ID3NoHeaderError:
self.log_message(f"跳过: {os.path.basename(filepath)} - 无ID3标签")
return False
except Exception as e:
self.log_message(f"错误: {os.path.basename(filepath)} - {str(e)}")
return False
self.process_files("清理标签", clean)
def fill_from_filename(self):
def fill(filepath):
import re
filename = os.path.basename(filepath)
match = re.match(r'(.+)\s*-\s*(.+)\.mp3$', filename)
if not match:
self.log_message(f"跳过: {filename} - 无法从文件名解析")
return False
artist = match.group(1).strip()
title = match.group(2).strip()
try:
try:
audio = EasyID3(filepath)
except ID3NoHeaderError:
audio = EasyID3()
# 只在标签为空时填充
if not audio.get('artist'):
audio['artist'] = [artist]
if not audio.get('title'):
audio['title'] = [title]
audio.save()
self.log_message(f"已填充: {filename} -> 艺术家: {artist}, 标题: {title}")
return True
except Exception as e:
self.log_message(f"错误: {filename} - {str(e)}")
return False
self.process_files("从文件名填充", fill)
if __name__ == "__main__":
root = tk.Tk()
app = MP3TagOrganizerGUI(root)
root.mainloop()
使用说明
- 保存脚本为
mp3_tag_organizer.py - 安装依赖:
pip install mutagen - 命令行使用:
python mp3_tag_organizer.py ./mp3_files/
- GUI版本:直接运行GUI脚本
常见问题处理
- 乱码问题:脚本会自动清理空格和特殊字符
- 标签缺失:可以从文件名自动填充
- 编码问题:支持UTF-8和GBK编码
这个脚本可以处理大多数MP3标签混乱的问题,建议先备份文件再使用!