AudioRenamer Pro V8 - 专业音乐文件重命名工具

查看 68|回复 10
作者:西北玄天一片云   
AudioRenamer Pro V8 - 专业音乐文件重命名工具
🎵 思路来源
在日常使用电脑的过程中,我们经常会积累大量音乐文件,这些文件往往来自不同的来源,命名格式五花八门。有些文件名带有长长的数字前缀,有些则完全没有序号;有些包含艺术家和歌曲名信息,有些则完全是一串乱码。这给音乐文件的管理和查找带来了很大不便。
我本人也深受其扰,经常需要手动重命名音乐文件,既费时又容易出错。虽然网上有一些批量重命名工具,但大多数要么功能过于简单,要么界面复杂难用,而且很少有专门针对音乐文件的工具能够智能识别音频元数据。
因此,我决定开发一款专门针对音乐文件的智能重命名工具——AudioRenamer Pro。这款工具旨在帮助用户快速、准确地整理音乐文件,让音乐库变得井井有条。
✨ 主要功能
智能音频元数据识别
支持多种音频格式:FLAC、MP3、WAV、APE、DSF
自动读取音频文件的元数据(ID3标签等)
智能提取艺术家、歌曲名、专辑等信息
灵活的重命名模板
可自定义命名模板,支持多种字段组合
支持序号、艺术家、歌曲名、专辑等字段
模板示例:{序号} - {歌手} - {歌名}
直观的可视化操作
双面板设计:左侧为歌曲列表,右侧为重命名预览
支持拖拽排序,轻松调整歌曲顺序
双击预览区域可直接编辑文件名
强大的序号处理
自动识别文件名中的数字序号(支持1-5位数字)
智能去除文件名中的无用后缀(如"【高音质】"等)
序号自动补零,保持文件顺序一致
智能去重机制
自动检测重复文件名
为重复文件添加编号后缀,避免文件覆盖
人性化设计
双主题支持:明亮主题和深色主题
紧凑界面设计,适配1920x1080屏幕
弹窗式操作说明,节省界面空间
完整的操作撤销功能,安全可靠
批量处理能力
一键批量重命名大量音乐文件
自动生成重命名备份CSV文件,便于追溯
实时进度条显示处理进度
💡 使用场景
整理从不同来源下载的音乐文件
规范化音乐文件命名格式
为车载音响或播放器准备有序的音乐库
管理个人音乐收藏,提高查找效率
🎯 设计理念
AudioRenamer Pro的设计理念是"简单、智能、高效"。我们相信,一款好的工具应该让用户能够快速完成任务,而不需要学习复杂的操作。因此,我们在保证功能强大的同时,尽可能简化用户操作流程,让用户能够轻松上手,快速整理音乐文件。
希望这款工具能够帮助到同样有音乐文件整理需求的朋友们!如果您有任何建议或反馈,欢迎在评论区留言讨论。

程序采用Python开发,使用tkinter作为GUI框架,支持Windows、macOS和Linux系统。


74011836d9de3cc4c8826c7d3ddf89ae.png (337.82 KB, 下载次数: 1)
下载附件
2025-10-20 11:21 上传

[Python] 纯文本查看 复制代码#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
AudioRenamer Pro V8
现代化音乐文件重命名工具,支持批量重命名、拖拽排序、模板自定义等功能
"""
import os, re, csv, datetime, pathlib, json, shutil
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import mutagen
from mutagen.flac import FLAC
from mutagen.mp3 import MP3
from mutagen.wave import WAVE
from mutagen.apev2 import APEv2
from mutagen.dsf import DSF
SUPPORT_EXT = ('.flac', '.mp3', '.wav', '.ape', '.dsf')
# ========= 工具箱 =========
def safe_name(s: str) -> str:
    return re.sub(r'[\\/:*?"|]', '', s).strip()
# 序号
_re_idx = re.compile(r'^(\d{1,5})[\.\-\s_·第首]*')
def extract_index(text: str) -> str:
    m = _re_idx.match(text.strip())
    return str(int(m.group(1))) if m else ''
def strip_index(text: str) -> str:
    return _re_idx.sub('', text, count=1).strip()
# 固定后缀
def build_pattern(blacklist):
    if not blacklist: return None
    escaped = [re.escape(b) for b in blacklist if b.strip()]
    return re.compile('|'.join(escaped), flags=re.IGNORECASE) if escaped else None
def strip_fixed(text, pattern):
    return pattern.sub('', text).strip() if pattern else text
# 统一读标签
def read_tags(fp: str, pattern):
    ext = pathlib.Path(fp).suffix.lower()
    try:
        audio = None
        if ext == '.flac':
            audio = FLAC(fp)
        elif ext == '.mp3':
            audio = MP3(fp)
        elif ext == '.wav':
            audio = WAVE(fp)
        elif ext == '.ape':
            audio = APEv2(fp)
        elif ext == '.dsf':
            audio = DSF(fp)
        if not audio:
            return {}, {}
        tags = {k: safe_name(str(v[0])) for k, v in audio.items() if v}
        for k in ('artist', 'title'):
            if k in tags:
                tags[k] = strip_index(strip_fixed(tags[k], pattern))
        return tags, audio
    except Exception:
        return {}, {}
def parse_fallback(name: str, pattern):
    n, _ = os.path.splitext(name)
    n = strip_index(n)
    n = strip_fixed(n, pattern)
    m = re.split(r'[-–—_]', n, maxsplit=1)
    if len(m) == 2:
        return {'歌手': safe_name(m[0]), '歌名': safe_name(m[1])}
    return {'歌名': safe_name(n)}
# ========= 主题配色 =========
THEMES = {
    'light': {'bg': '#f0f8ff', 'fg': '#2c3e50', 'tree': '#ffffff', 'select': '#3498db', 'btn': '#3498db', 'bar': '#bde0fe', 'frame': '#e1f0fa'},
    'dark':  {'bg': '#2c3e50', 'fg': '#ecf0f1', 'tree': '#34495e', 'select': '#3498db', 'btn': '#3498db', 'bar': '#1a2530', 'frame': '#34495e'}
}
BUTTON_COLORS = {
    'primary': '#3498db',
    'secondary': '#9b59b6',
    'success': '#2ecc71',
    'warning': '#f39c12',
    'danger': '#e74c3c',
    'info': '#1abc9c'
}
# ========= 主程序 =========
class App(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title('🎵 AudioRenamer Pro V8 - 音乐文件重命名工具-52pj西北玄天一片云')
        self.geometry('1200x750')
        self.folder = ''
        self.items = []
        self.template = tk.StringVar(value='{序号} - {歌手} - {歌名}')
        self.undo_stack = []  # 撤销用
        self.theme_name = 'light'
        self.colors = THEMES[self.theme_name]
        self.blacklist = tk.Text(self, height=4, width=25, font=('Segoe UI', 9))
        self._set_style()
        self._build_ui()
        self._bind_shortcuts()
        self._init_blacklist()
        self._apply_theme()
    # ----- 样式 -----
    def _set_style(self):
        style = ttk.Style(self)
        style.theme_use('clam')
        btn = {'light': {'bg': BUTTON_COLORS['primary'], 'fg': 'white', 'relief': 'flat', 'padx': 10, 'pady': 5},
               'dark':  {'bg': BUTTON_COLORS['primary'], 'fg': 'white', 'relief': 'flat', 'padx': 10, 'pady': 5}}
        style.configure('TButton', **btn[self.theme_name])
        style.map('TButton', background=[('active', self.colors['select'])])
        style.configure('Treeview', background=self.colors['tree'], fieldbackground=self.colors['tree'],
                        foreground=self.colors['fg'], rowheight=25, borderwidth=0, font=('Segoe UI', 9))
        style.configure('Treeview.Heading', background=self.colors['bar'], foreground=self.colors['fg'],
                        font=('Segoe UI', 9, 'bold'))
        style.configure('TLabel', background=self.colors['bg'], foreground=self.colors['fg'])
        style.configure('TLabelframe', background=self.colors['bg'], foreground=self.colors['fg'],
                        relief='solid')
        style.configure('TLabelframe.Label', background=self.colors['bg'], foreground=self.colors['fg'],
                        font=('Segoe UI', 10, 'bold'))
        style.configure('TEntry', fieldbackground=self.colors['tree'], foreground=self.colors['fg'],
                        font=('Segoe UI', 10))
    def _apply_theme(self):
        self.config(bg=self.colors['bg'])
        for w in [self.blacklist]:
            w.config(bg=self.colors['tree'], fg=self.colors['fg'], insertbackground=self.colors['fg'])
    # ----- 界面 -----
    def _build_ui(self):
        # 顶部标题
        title_frame = tk.Frame(self, bg=self.colors['bg'])
        title_frame.pack(fill='x', padx=10, pady=(5, 2))
        title_label = tk.Label(title_frame, text="🎵 AudioRenamer Pro V8 - 专业音乐文件重命名工具",
                              font=('Segoe UI', 14, 'bold'), bg=self.colors['bg'], fg=BUTTON_COLORS['primary'])
        title_label.pack()
        # 顶部工具栏
        top = ttk.Frame(self)
        top.pack(side='top', fill='x', padx=10, pady=5)
        ttk.Button(top, text='📁 选择文件夹', command=self.select_folder).pack(side='left')
        ttk.Label(top, text='模板:').pack(side='left', padx=(10, 0))
        ent = ttk.Entry(top, textvariable=self.template, width=30, font=('Segoe UI', 10))
        ent.pack(side='left', padx=5)
        ttk.Button(top, text='🎨 主题', command=self.toggle_theme).pack(side='left', padx=5)
        ttk.Button(top, text='↶ 撤销', command=self.undo_rename).pack(side='left', padx=5)
        ttk.Button(top, text='✨ 预览', command=self.apply_template).pack(side='left', padx=5)
        ttk.Button(top, text='▶ 重命名', command=self.rename).pack(side='left', padx=5)
        
        # 帮助按钮
        help_btn = ttk.Button(top, text='操作说明', command=self.show_help)
        help_btn.pack(side='right')
        # 中部 PanedWindow
        paned = ttk.PanedWindow(self, orient='horizontal')
        paned.pack(fill='both', expand=True, padx=10, pady=5)
        # 左侧:排序树
        left = ttk.Labelframe(paned, text='🎵 歌曲列表(可拖拽排序)', padding=3)
        paned.add(left, weight=1)
        self.sort_tree = ttk.Treeview(left, columns=('idx', '序号', '歌手', '歌名'), show='headings', selectmode='extended', height=16)
        for c, w in zip(('idx', '序号', '歌手', '歌名'), (40, 50, 130, 200)):
            self.sort_tree.heading(c, text=c)
            self.sort_tree.column(c, width=w)
        self.sort_tree.pack(fill='both', expand=True)
        self.sort_tree.bind('[B]', self._drag_start)
        self.sort_tree.bind('[B]', self._drag_move)
        button_frame = ttk.Frame(left)
        button_frame.pack(fill='x', pady=(3, 0))
        ttk.Button(button_frame, text='↑', command=lambda: self._move(-1), width=3).pack(side='left', padx=1)
        ttk.Button(button_frame, text='↓', command=lambda: self._move(1), width=3).pack(side='left', padx=1)
        # 右侧:预览树
        right = ttk.Labelframe(paned, text='🔍 重命名预览(双击编辑)', padding=3)
        paned.add(right, weight=1)
        self.preview_tree = ttk.Treeview(right, columns=('old', 'new'), show='headings', height=16)
        for c, w in zip(('old', 'new'), (300, 300)):
            self.preview_tree.heading(c, text=c)
            self.preview_tree.column(c, width=w)
        self.preview_tree.pack(fill='both', expand=True)
        self.preview_tree.bind('', self._on_edit)
        # 底部状态 + 黑名单
        bot = ttk.Frame(self)
        bot.pack(side='bottom', fill='x', padx=10, pady=5)
        self.status = tk.Label(bot, text='就绪', anchor='w', bg=self.colors['bg'], fg=self.colors['fg'], font=('Segoe UI', 8))
        self.status.pack(side='left')
        ttk.Label(bot, text='🗑️ 黑名单:', font=('Segoe UI', 8)).pack(side='left', padx=(10, 3))
        self.blacklist.pack(side='left', padx=3)
        ttk.Button(bot, text='🔄', command=self.apply_template, width=5).pack(side='left', padx=3)
        
        # 进度条
        self.progress = ttk.Progressbar(bot, mode='determinate')
        self.progress.pack(side='right', padx=5, fill='x', expand=True)
    # ----- 快捷键 -----
    def _bind_shortcuts(self):
        self.bind('', lambda e: self.undo_rename())
        self.bind('', lambda e: self.toggle_theme())
        self.bind('', lambda e: self.apply_template())
    # ----- 初始化黑名单 -----
    def _init_blacklist(self):
        default = ['【六倍音质】', 'HQ', '无损', '高音质', 'Official Music Video']
        self.blacklist.insert('1.0', '\n'.join(default))
    def get_pattern(self):
        lines = self.blacklist.get('1.0', tk.END).splitlines()
        return build_pattern(lines)
    # ----- 帮助信息 -----
    def show_help(self):
        help_text = """
操作说明:
1. 点击"📁 选择文件夹"选择音乐目录
2. 程序自动扫描支持的音频文件
3. 在左侧列表中拖拽排序歌曲
4. 使用"↑ ↓"按钮微调歌曲顺序
5. 自定义命名模板,支持字段:
   {序号} {歌手} {歌名} {专辑}
6. 双击预览区域可编辑文件名
7. 点击"▶ 重命名"执行操作
8. 所有操作可通过"↶ 撤销"恢复
"""
        messagebox.showinfo("操作说明", help_text)
    # ----- 主题切换 -----
    def toggle_theme(self):
        self.theme_name = 'dark' if self.theme_name == 'light' else 'light'
        self.colors = THEMES[self.theme_name]
        self._set_style()
        self._apply_theme()
        self.status.config(text=f'已切换至 {self.theme_name} 主题')
    # ----- 选择文件夹 -----
    def select_folder(self):
        f = filedialog.askdirectory(title='选择音乐文件夹')
        if not f:
            return
        self.folder = f
        self.scan()
    def scan(self):
        self.items.clear()
        pattern = self.get_pattern()
        # 获取所有文件
        all_files = []
        for root, _, names in os.walk(self.folder):
            for name in names:
                if name.lower().endswith(SUPPORT_EXT):
                    fp = os.path.join(root, name)
                    all_files.append((fp, name))
        
        # 更新进度条
        total_files = len(all_files)
        self.progress['maximum'] = total_files
        
        for i, (fp, name) in enumerate(all_files):
            tags, _ = read_tags(fp, pattern)
            if not tags:
                tags = parse_fallback(name, pattern)
            tags['序号'] = extract_index(name)
            tags['__file__'] = fp
            tags['__old_name__'] = name
            self.items.append(tags)
            
            # 更新进度条
            self.progress['value'] = i + 1
            self.update_idletasks()
            
        if not self.items:
            messagebox.showwarning('未找到音乐文件')
            return
        self.populate_sort_tree()
        self.status.config(text=f'扫描完成,共 {len(self.items)} 首')
        self.progress['value'] = 0
    def populate_sort_tree(self):
        self.sort_tree.delete(*self.sort_tree.get_children())
        for idx, it in enumerate(self.items, 1):
            self.sort_tree.insert('', 'end', values=(idx, it.get('序号', ''), it.get('歌手', ''), it.get('歌名', '')))
    # ----- 排序 / 拖拽 -----
    def sort_by(self, key, reverse):
        self.items.sort(key=lambda x: x.get(key, ''), reverse=reverse)
        self.populate_sort_tree()
        self.apply_template()
    def _drag_start(self, event):
        tv = event.widget
        self.drag_item = tv.identify_row(event.y)
    def _drag_move(self, event):
        tv = event.widget
        target = tv.identify_row(event.y)
        if not target or target == self.drag_item:
            return
        idx1 = tv.index(self.drag_item)
        idx2 = tv.index(target)
        self.items[idx1], self.items[idx2] = self.items[idx2], self.items[idx1]
        self.populate_sort_tree()
        tv.selection_set(self.drag_item)
        self.apply_template()
    def _move(self, delta):
        sel = self.sort_tree.selection()
        if not sel:
            return
        idx = self.sort_tree.index(sel[0])
        new = idx + delta
        if 0 ', save_edit)
        entry.bind('', save_edit)
    # ----- 重命名 + 撤销 -----
    def rename(self):
        if not self.items:
            messagebox.showwarning('无数据')
            return
            
        # 确认对话框
        result = messagebox.askyesno("确认重命名", f"确定要重命名 {len(self.items)} 个文件吗?")
        if not result:
            return
            
        # 备份 csv
        bk_file = os.path.join(self.folder, f'backup_{datetime.datetime.now():%Y%m%d_%H%M%S}.csv')
        with open(bk_file, 'w', newline='', encoding='utf-8-sig') as f:
            writer = csv.writer(f)
            writer.writerow(['原路径', '新文件名'])
            for it in self.items:
                writer.writerow([it['__file__'], it['__new_name__']])
               
        # 执行重命名
        success = 0
        failed = 0
        undo_list = []
        
        # 设置进度条
        self.progress['maximum'] = len(self.items)
        
        for i, it in enumerate(self.items):
            old = it['__file__']
            new_f = os.path.join(os.path.dirname(old), it['__new_name__'])
            
            # 检查文件是否已存在
            if os.path.exists(new_f):
                failed += 1
                self.status.config(text=f'重命名失败: {it["__old_name__"]} (文件已存在)')
                continue
               
            try:
                os.rename(old, new_f)
                undo_list.append((new_f, old))
                success += 1
            except Exception as e:
                failed += 1
                self.status.config(text=f'重命名失败: {it["__old_name__"]} ({str(e)})')
               
            # 更新进度条
            self.progress['value'] = i + 1
            self.update_idletasks()
            
        self.undo_stack = undo_list
        self.progress['value'] = 0
        self.status.config(text=f'重命名完成 {success} 首,失败 {failed} 首,可按 Ctrl+Z 撤销')
        messagebox.showinfo('完成', f'已重命名 {success} 首,失败 {failed} 首!\n备份:{bk_file}')
    def undo_rename(self):
        if not self.undo_stack:
            messagebox.showinfo('提示', '暂无操作可撤销')
            return
            
        count = 0
        # 设置进度条
        self.progress['maximum'] = len(self.undo_stack)
        
        for i, (new, old) in enumerate(self.undo_stack):
            if os.path.exists(new):
                try:
                    os.rename(new, old)
                    count += 1
                except Exception as e:
                    self.status.config(text=f'撤销失败: {os.path.basename(new)} ({str(e)})')
                    
            # 更新进度条
            self.progress['value'] = i + 1
            self.update_idletasks()
            
        self.undo_stack.clear()
        self.progress['value'] = 0
        self.scan()
        self.status.config(text=f'已撤销 {count} 个重命名')
# ---------- main ----------
if __name__ == '__main__':
    App().mainloop()

重命名, 音乐文件

西北玄天一片云
OP
  

『来自123云盘用户的分享』AudioRenamer Pro V8 - 音乐文件重命名工具.exe
链接:https://www.123865.com/s/idNljv-BPfoH?pwd=52pj#
提取码:52pj
打包好的程序在这里,忘记发了,不好意思,请各位大佬使用
西北玄天一片云
OP
  


helh0275 发表于 2025-10-20 11:54
试用了一下,总体不错,但模板不能更改咋回事?

歌曲无非就是歌手、歌名这些关键信息,这些信息你可以手动改模板,比如{序号} - {歌手} - {歌名}换成 {歌名} - {歌手}  这样的话就很简洁,如果你保存的歌曲中有大量重复的词,可以通过黑名单剔除后保存。
maomaochong   

楼🐷,链接呢
wwwww1986528   

好像没有提供软件下载啊。。源代码我可不会用 哈哈
Molong   

没下载连接呢
riunchen   

刮削用过一个,然后为了重命名自己写了一py的
alex701   

插个眼~~~~~
sx4267   

和重命名软件有啥区别?
AutumnBootLeaf   

这个不错,一万多首歌终于可以统一命名了
您需要登录后才可以回帖 登录 | 立即注册

返回顶部