一款 Windows 桌面小工具,用 Python + PySide6 开发。
核心功能:自动识别通过 IDM 等工具下载的 B 站分离式 m4s 文件(纯视频+纯音频),提供自动与手动配对、批量命名、灵活的输出目录结构、统一合并为完整视频。
- 输入:用户通过按钮或拖拽添加文件夹(可多个)。
- 递归扫描:遍历所有子文件夹,收集全部
.m4s文件。 - 流类型分析:使用
ffprobe检测每个 m4s 文件的音视频轨道状态。video_only:仅有视频流audio_only:仅有音频流muxed:同时含有视频和音频流 → 标记为“已完整”
- 分析结果展示:所有文件在工具内按状态分类展示。
工具支持同时处理两种来源的文件:
模式 A — 子文件夹自动配对
- 触发条件:某个最底层文件夹内,恰好存在 1 个
video_only+ 1 个audio_only,且无其他同类型文件。 - 行为:
- 自动将这一对视为一个合并任务。
- 任务名称默认取该文件夹的名称。
- 任务自动进入“配对队列”(待合并列表)。
- 多对情形(极少数,如文件夹内有 2 个纯视频+2 个纯音频):
- 同样自动配对,按文件名字典序第1视频配第1音频、第2配第2。
- 配对后在列表中用黄色高亮标记“疑似多集,请确认名称”。
- 数量不对等(如 2 视频+1 音频):不自动配对,相关文件仍散落在“待整理”列表,标记预警色,等待用户手动干预。
模式 B — 零散文件手动配对
- 所有未被自动配对的单独
video_only和audio_only文件,集中列入“待整理”标签页。 - 手动配对操作:
- 在“待整理”列表中,用户可多选一个视频文件和一个音频文件(通过 Ctrl/Shift)。
- 点击工具栏「配对」按钮,或右键选择“配对所选”。
- 配对后立即弹出一个内联编辑框或小弹窗,要求用户输入输出文件名(不含扩展名)。
- 确认后该配对任务移入“配对队列”,输出文件名即为用户填写的内容。
- 批量命名加速:
- 在“配对队列”中,用户可多选多个任务 → 右键选择“批量命名”。
- 弹出对话框,填写:
前缀文字+起始序号+序号位数(默认2)。 - 系统自动按任务顺序依次重命名为:
前缀_01、前缀_02...
- 命名修改:任何任务的“输出名”均可在表格中双击单元格直接修改,优先级最高。
muxed文件默认不进入合并队列,仅在界面上列出(可隐藏),状态显示“已完整(跳过)”。- 提供右键菜单选项:「转封装为输出格式」,可不经配对直接封装成目标容器格式(同样使用
ffmpeg -c copy)。此时命名默认使用原文件名。
- 在任意文件列表(待整理、配对队列中的源文件)上,右键菜单选择「预览」。
- 工具调用系统默认播放器打开该 m4s 文件(使用
os.startfile())。 - 帮助用户辨别视频内容以便正确配对,尤其是文件名无意义时。
提供集中的设置面板,包含:
- 输出格式:下拉选择框,选项:
mp4、mkv、mov、flv。默认mp4。 - 输出目录:文本框 + 「浏览」按钮,用户选择一个文件夹作为最终视频的统一输出根目录。
若留空,则要求用户必须先选择。 - 路径镜像与层级控制:
- 默认行为:保留从“源拖入根目录”到文件所在文件夹的完整相对路径。
- 例:拖入根
D:\下载,文件在D:\下载\番剧A\01\→ 输出到<输出根>\番剧A\01\。
- 例:拖入根
- 自定义层级(路径深度):在设置区提供一个数字输入框,默认值为
0(保留全部层级)。- 若设为
1:则去掉最下层文件夹(即文件直接父文件夹被忽略)。- 上例输出变为:
<输出根>\番剧A\番剧A 01.mp4。
- 上例输出变为:
- 若设为
2:同时去掉最末两级文件夹,最终扁平输出到输出根。
- 若设为
- 实时预览:输入深度后,下方显示一个示例路径字符串(如“当前设置结果示例:E:\已完成\番剧A\番剧A 01.mp4”),方便理解。
- 零散文件处理:对于直接位于拖入根目录下(无子文件夹)的配对,无论深度设为多少,都直接输出到输出根目录,不产生额外文件夹。
- 默认行为:保留从“源拖入根目录”到文件所在文件夹的完整相对路径。
- 源文件处理选项:
- 复选框:
合并完成后删除原始 m4s 文件,默认不勾选。 - 该选项仅作为许可开关,实际删除前还需二次确认。
- 复选框:
- 用户点击「开始合并」,工具按配对队列顺序依次处理。
- 每个任务调用
ffmpeg进行流复制合并,保留原始质量。 - 进度显示:
- 顶部总进度条(按已完成任务数/总任务数推进)。
- 底部状态文本显示“正在处理:xxx.mp4”。
- 合并完成:弹出结果摘要(成功 X 个,失败 Y 个)。
- 删除源文件二次确认:
- 仅当用户在设置中勾选了“合并后删除”选项并且至少有一个文件合并成功时,合并完成弹窗后立即再弹出一个确认对话框:“所有成功合并的源文件将被删除,确定吗?[是]/[否]”。
- 用户确认后才执行删除(移至回收站,而非直接永久删除)。
- 列表清空:可一键清除所有扫描结果,恢复初始状态。
- 错误处理:单文件合并失败不会中断后续任务,日志记录详细错误信息(仅窗口底部简单显示,点击可查看完整日志)。
- 输出文件重名处理:若输出目录已存在同名文件,弹出小对话框,提供:
覆盖/自动重命名(添加后缀)/跳过。用户可勾选“本次合并全部采用此选择”。
窗口标题:B站m4s合并工具
尺寸:约 1100x700 像素,可自由缩放。
- 按钮:
📂 添加文件夹、🧹 清空列表 - 右侧文本:“就绪” / “正在处理...”
采用 QTabWidget 两个标签页:
标签1:「合并队列」
- 显示所有已确认的合并任务(自动配对+手动配对)。
- 表格列:
- 状态图标(待合并✓ / 错误❌)
- 输出文件名(可编辑)
- 源视频文件(完整相对路径)
- 源音频文件
- 预计输出路径
- 右键菜单:
批量命名、移除任务、预览视频、预览音频。 - 支持多选行(用于批量命名或移除)。
标签2:「待整理」
- 专门显示零散未配对的纯视频和纯音频文件。
- 表格列:
类型图标(🎬 或 🔊)、文件名、所在文件夹。 - 右键菜单:
配对所选(需选中一视频一音频)、预览、标记为“已完整”跳过。 - 支持多选(但配对按钮仅在恰好选中一个视频和一个音频时真正可用,否则灰显提示)。
- 点击「配对」后,弹出小输入框让用户填写输出名。或者直接在表格中新增一行(在配对队列中出现)并立即进入编辑状态。
采用分组框样式的垂直布局:
组1:输出格式
- 标签:“输出格式”
- 下拉框:
mp4(默认) /mkv/mov/flv
组2:输出目录
- 标签:“统一输出到”
- 行:文本框(显示路径)+
浏览...按钮 - 下方灰字提示:“合并后的视频将全部放置于此目录下(保留相对路径镜像)”
组3:路径层级
- 标签:“保留文件夹层级深度(0为完整保留)”
- 数字输入框,默认
0,最小值0。 - 示例预览标签:“输出示例:E:...\番剧A\01\番剧A 01.mp4”(根据实际拖入路径动态更新)
组4:源文件处理
- 复选框:“合并成功后允许删除原始 m4s 文件”
- 下方灰字:“仅当勾选且合并全部成功后,会再次询问确认”
组5:操作按钮
- 大按钮:
▶ 开始合并(未完成设置或列表为空时禁用) - 下方状态提示:“请先添加文件夹并完成配对”
- 左侧:总进度条(占满剩余宽度)
- 右侧:当前任务文字(如“正在合并: 番剧A 01.mp4”)
- 添加文件夹
- 用户选择/拖拽文件夹路径 → 存储为“根路径列表”。
- 扫描阶段
- 递归遍历所有根路径下的子目录,收集
.m4s文件。 - 对每个文件调用
ffprobe,解析 JSON,确定video_only、audio_only或muxed。 - 构建内部数据结构列表。
- 递归遍历所有根路径下的子目录,收集
- 自动配对阶段(针对每个最底层子文件夹):
- 分组统计该文件夹下的视频/音频文件。
- 若
count(video_only)=1且count(audio_only)=1→ 创建配对,任务名=文件夹名。 - 若数量相等且 > 1 → 按文件名排序后顺序配对,标记“多集”。
- 若不满足以上条件 → 文件留入“待整理”池。
- 所有
muxed文件单独列表。
- 界面填充:
- “合并队列”加载所有自动配对任务。
- “待整理”加载剩余零散文件。
- “已完整”文件暂存入内部列表,不显示在主要标签页(可后续通过菜单查看)。
- 手动整理交互:
- 用户在“待整理”中手动配对并命名 → 实时更新“合并队列”。
- 用户可随时切换标签进行编辑、重命名、删除任务等。
- 开始合并:
- 检查输出目录非空,若无则提示。
- 遍历合并队列每个任务:
- 计算输出路径(依据输出根、相对路径、路径深度、输出名、格式)。
- 创建目标文件夹(
os.makedirs)。 - 组装 ffmpeg 命令:
ffmpeg -i video.m4s -i audio.m4s -c copy -map 0:v:0 -map 1:a:0 -y "输出路径"(-y需结合重名策略调整)。 - 使用
subprocess.Popen执行,不解析内部进度,仅等待结束。 - 依据返回码判断成功/失败,更新状态和进度条。
- 合并循环结束后:
- 显示结果摘要。
- 如果设置允许删除且至少有一个成功,弹出确认删除对话框。
- 用户确认后,遍历成功任务的源文件,调用
send2trash移入回收站(避免彻底删除)。
- 错误处理:
- ffmpeg 执行失败时,该行标记为红色错误状态,记录 stderr 信息到内存日志,继续下一个。
- 重名策略:检测到输出文件已存在时弹出对话框(覆盖/重命名/跳过),可勾选应用到所有后续。
- 语言/框架:Python 3.9+,PySide6。
- ffmpeg 依赖:需要系统已安装 ffmpeg 并加入 PATH。工具启动时自动检测
ffmpeg -version,若不可用则弹出警告并禁用合并按钮。 - ffprobe JSON 解析:
解析
ffprobe -v quiet -print_format json -show_streams "文件路径"streams数组,查找codec_type为video或audio。 - 预览实现:
os.startfile(filepath)。 - 路径镜像计算:
- 用户在拖入时,记住每个添加的文件夹的绝对路径,作为
root_paths。 - 对于一个源文件所在的绝对目录
file_dir,找到它是哪个root的子路径,得到relative = os.path.relpath(file_dir, root)。 - 输出结构基于
relative和用户设置的深度进行裁剪(去掉最后 N 级子目录)。
- 用户在拖入时,记住每个添加的文件夹的绝对路径,作为
- 路径深度裁剪算法:
- 将
relative分割成各级目录列表,例如['番剧A', '01']。 - 若
depth >= len(parts),则结果为输出根(扁平)。 - 否则保留前
len(parts)-depth部分。
- 将
- 删除文件:使用
send2trash第三方库,安全删除至回收站。若无此库则退化为永久删除(需警告)。 - 打包:最终用 PyInstaller 生成单文件 exe,可包含
ffmpeg依赖检测但不捆绑 ffmpeg。
- 无管理员权限:不影响程序功能,文件删除、写入正常用户目录即可。
- 文件名含空格/特殊字符:ffmpeg 命令使用列表形式传递参数,避免 shell 注入。
- 媒体流异常(如一个文件同时有多个视频轨):解析时优先取第一个视频流和第一个音频流,并在界面备注。
- 超大文件夹:性能上采用逐文件分析,扫描时用多线程避免界面冻结,但初期可简单用单线程 + 进度提示。
- 输出磁盘空间不足:合并失败会捕获异常并提示。
- 环境搭建:创建虚拟环境,安装 PySide6、send2trash 等。
- 核心工具函数:ffprobe 解析、配对逻辑、路径计算。
- 界面骨架:主窗口、标签页、表格、设置面板。
- 交互功能:拖拽、列表刷新、右键菜单、命名编辑。
- 合并引擎:调用 ffmpeg、进度更新、错误处理、删除逻辑。
- 测试与优化:各种 IDM 下载场景测试。
- 打包:PyInstaller 生成 exe。
以上即为完全详尽的最终设计,涵盖了所有功能定义、界面细节和技术实施路径。如确认无误,我就开始进入编码阶段。