尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

视频横竖屏自动分类工具:ffprobe批量整理素材实战

视频横竖屏自动分类工具:ffprobe批量整理素材实战 视频横竖屏自动分类工具批量整理神器做视频内容运营和素材管理的人应该都体会过那种绝望一个硬盘里堆了几百个视频文件手机拍的竖屏素材、相机拍的横屏成片、直播录屏、网课切片乱七八糟混在一起。你要剪辑的时候找不到素材要发布的时候发现封面图比例不对要归档的时候更是无从下手。我去年年底帮一个MCN机构整理素材库光是把三千多个视频按横竖屏分开就花了整整两天纯手工操作点开每个文件看分辨率眼睛都快瞎了。后来我实在受不了花了一个晚上写了这套自动分类工具从那之后再大的素材库我也能十分钟内整理完。这篇东西不是讲什么高大上的AI识别就是用ffprobe加一段简单脚本把视频文件的宽高比读出来按你设定的规则批量归类。听起来很简单对吧但真正做起来里面的坑比你想象的多尤其是手机竖屏视频的实际分辨率反而不一定比横屏小你要是按文件大小或者分辨率绝对值去判断直接翻车。1. 工具设计的整体思路与选型考量1.1 为什么需要自动分类而不是手动整理先算一笔账。假设你有1000个视频文件每个文件你都要双击打开、看画面比例、判断横竖、拖拽到对应文件夹。就算你手速快一个文件平均也要5秒钟那就是5000秒将近一个半小时。而且这活儿极度枯燥干到一半就开始走神判断标准就开始漂移——前300个你觉得大于宽的就是横屏后300个你开始觉得接近正方形也算竖屏标准不一致后期用的时候更乱。更重要的是手动整理没法嵌入到自动化流程里。比如你做一个定时任务每天从相机、手机、无人机里同步素材到NAS如果没有自动分类每天都要花时间手动整理。一旦素材多了人工成本是线性增长的而你的时间并不会增加。自动分类的核心收益不是省时间是让整理这个动作变成一条确定性的规则。你只需要告诉程序“宽大于高的归横屏高大于宽的归竖屏”剩下的事情交给脚本跑规则统一、结果一致、可追溯。1.2 方案选型为什么用ffprobe而不是其他方式市面上其实有不少现成的媒体管理工具比如Adobe Bridge、Eagle、Billfish这些它们都能显示分辨率信息也有一定的筛选功能。但问题是这些工具要么收费要么是图形界面操作要么不支持命令行批量处理更别说嵌入到自动化脚本里了。我最终选择了ffprobe理由有三个第一ffprobe是FFmpeg家族的一员命令行工具跨平台支持Windows、macOS、Linux你写好的脚本可以在任何机器上跑。第二ffprobe读取的是视频文件的元数据和流信息速度极快。一个1GB的视频文件ffprobe只需要几十毫秒就能拿到分辨率信息因为它不是真的去解码视频画面而是解析文件头部的流参数。实测3000个文件全部扫描完也就一两分钟。第三ffprobe是开源免费的你不用给钱也不用担心授权问题。当然纯ffprobe也有局限它只能拿到编码层面的分辨率如果视频画面本身是竖屏拍摄但被加了个横屏的黑边或者画面内容做了旋转那仅靠分辨率就可能判断错误。这个我后面在“进阶玩法”里会讲怎么应对基础版先把批量分类跑通再说。1.3 设计目标和使用场景我设计这套工具的初始目标是输入一个文件夹路径递归扫描所有视频文件读取每个文件的宽高信息按照“宽大于高横屏、高大于宽竖屏”的规则进行分类自动创建横屏和竖屏两个子文件夹把视频文件移动或复制到对应文件夹输出一份完整的分类报告方便核对这套工具适合什么场景短视频创作者批量整理手机素材、摄影工作室归档婚礼跟拍视频、课程制作团队管理录屏素材、家庭用户整理NAS里的手机视频、电商团队管理商品视频素材。基本上只要你手上有超过50个视频文件又不想一个一个打开看这套工具就能派上用场。2. 核心细节解析与环境准备2.1 判断横竖屏的真正逻辑宽高比而不是分辨率我知道很多人第一反应是“分辨率大的就是横屏分辨率小的就是竖屏”这是最大的误区。我们用手机举个例子iPhone 15 Pro Max拍摄的横屏视频分辨率是1920x1080竖屏视频分辨率是1080x1920两者分辨率绝对值完全一样只是宽高互换了。你要是用分辨率大小判断这俩就没法区分但用宽高比判断一眼就能看出来。所以核心规则很简单width / height 1是横屏width / height 1是竖屏width / height 1是正方形这个比例基本只存在于老电视内容或某些特殊素材。但实际操作中事情没这么简单。因为视频编码里存在一个叫“旋转角度”rotation的元数据。你手机竖着拍摄时传感器采集到的原始画面其实是横的但文件里写了一个rotation: 90的标记播放器读到这个标记后会自动旋转90度显示但编码层的宽高并没有变。所以你在ffprobe里看到的原始分辨率可能是1920x1080但实际播放出来是竖屏效果。这就导致了两种情况如果视频分辨率是1080x1920宽高比明显小于1那肯定是竖屏不管有没有rotation标记如果视频分辨率是1920x1080但带了rotation: 90的标记那实际效果是竖屏只判断宽高比会漏掉第二种情况。所以我在脚本里加了一步读rotation字段如果rotation是90或270就把width和height对调之后再来算宽高比。2.2 环境准备与安装步骤这套工具的运行环境要求不高核心依赖就两个FFmpeg提供ffprobe和Python 3用来写控制脚本。如果你不想装Python也可以直接用批处理脚本Windows或Shell脚本macOS/Linux但Python版更灵活后面加功能也方便。先装FFmpeg。Windows用户去FFmpeg官网下载编译好的release版本解压后把bin目录的路径加到系统环境变量PATH里。macOS用户最简单直接终端执行 brew install ffmpeg Linux用户用apt或者yum装 sudo apt install ffmpeg 装完之后在终端输入ffprobe -version能输出版本号就说明安装成功了。Python 3的话官网下载安装包安装装完在终端输入python3 --version确认版本。我这里用Python 3.9以上版本主要是用到pathlib这个标准库更低版本也能跑但代码得改两行。2.3 目录结构和文件命名规范为了脚本能稳定运行我建议先把素材按照“来源”分好层级而不是把所有视频堆在一个文件夹里。举个例子你的素材根目录长这样/home/me/videos/ ├── 2024_01_手机拍摄/ ├── 2024_02_相机拍摄/ ├── 2024_03_无人机/ └── 2024_04_直播录屏/每个子目录里的视频可能混着横竖屏脚本的作用就是把每个子目录里的横竖屏分开成两个子文件夹。我倾向于输出结构是/home/me/videos/ ├── 2024_01_手机拍摄/ │ ├── 横屏/ │ ├── 竖屏/ │ └── 正方形/这样能保留原始来源信息同时完成横竖屏分类。如果你想把全世界所有横屏视频归到一个文件夹也可以调整脚本参数但对大多数人来说先按来源分再按比例分是最好维护的结构。3. 实操过程与核心代码实现3.1 读取视频信息的核心命令先单独感受一下ffprobe的输出是什么样。在终端里执行ffprobe -v quiet -print_format json -show_streams input.mp4这条命令会输出JSON格式的视频流信息里面有width、height、codec_name、duration、tags等一堆字段。但如果我们只要宽高和旋转信息可以用更简洁的方式ffprobe -v error -select_streams v:0 -show_entries streamwidth,height:stream_tagsrotate -of defaultnoprint_wrappers1 input.mp4输出长这样width1920 height1080 rotate90旋转字段不是每个视频都有没有的话就忽略。我在脚本里用subprocess调用ffprobe然后解析这段输出。3.2 Python脚本整体实现下面给出完整的核心代码注释我写得很详细方便你按自己的需求改#!/usr/bin/env python3 # -*- coding: utf-8 -*- import os import shutil import subprocess import json from pathlib import Path def get_video_info(file_path): 使用ffprobe读取视频的宽度、高度和旋转信息 返回(width, height, rotate) cmd [ ffprobe, -v, error, -select_streams, v:0, -show_entries, streamwidth,height:stream_tagsrotate, -of, json, str(file_path) ] try: result subprocess.run(cmd, capture_outputTrue, textTrue, timeout10) data json.loads(result.stdout) stream data[streams][0] width int(stream.get(width, 0)) height int(stream.get(height, 0)) tags stream.get(tags, {}) rotate int(tags.get(rotate, 0)) return width, height, rotate except Exception as e: print(f[错误] 无法读取 {file_path}: {e}) return None def classify_video(width, height, rotate): 根据宽高和旋转角度判断横竖屏 核心逻辑: 如果旋转角度是90或270宽高对调 if rotate in (90, 270): width, height height, width if width height: return 横屏 elif width height: return 竖屏 else: return 正方形 def process_folder(folder_path, dry_runFalse, move_modeTrue): 扫描整个文件夹分类所有视频文件 dry_runTrue 表示只预览不执行 move_modeTrue 表示移动文件False 表示复制文件 folder Path(folder_path) if not folder.exists(): print(f文件夹不存在: {folder_path}) return video_exts {.mp4, .mov, .avi, .mkv, .flv, .wmv, .m4v, .webm, .ts, .mts, .m2ts} categories [横屏, 竖屏, 正方形] for cat in categories: (folder / cat).mkdir(exist_okTrue) stats {cat: 0 for cat in categories} failures [] video_files [] for root, dirs, files in os.walk(folder): dirs[:] [d for d in dirs if d not in categories] for f in files: if Path(f).suffix.lower() in video_exts: video_files.append(Path(root) / f) total len(video_files) print(f共发现 {total} 个视频文件\n) for idx, file_path in enumerate(video_files, 1): info get_video_info(file_path) if info is None: failures.append(str(file_path)) continue width, height, rotate info category classify_video(width, height, rotate) stats[category] 1 target_path folder / category / file_path.name # 如果目标文件已存在加数字后缀避免覆盖 if target_path.exists(): stem file_path.stem suffix file_path.suffix counter 1 while target_path.exists(): target_path folder / category / f{stem}_{counter}{suffix} counter 1 print(f[{idx}/{total}] {file_path.name} f- {category} ({width}x{height}, rotation{rotate})) if not dry_run: if move_mode: shutil.move(str(file_path), str(target_path)) else: shutil.copy2(str(file_path), str(target_path)) print(\n分类完成) print(f横屏: {stats[横屏]} 个) print(f竖屏: {stats[竖屏]} 个) print(f正方形: {stats[正方形]} 个) if failures: print(f\n读取失败的文件: {len(failures)} 个) for f in failures: print(f - {f}) if __name__ __main__: import argparse parser argparse.ArgumentParser(description视频横竖屏自动分类工具) parser.add_argument(folder, help要处理的文件夹路径) parser.add_argument(--dry-run, actionstore_true, help仅预览不执行移动/复制) parser.add_argument(--copy, actionstore_true, help复制模式默认是移动) args parser.parse_args() process_folder(args.folder, dry_runargs.dry_run, move_modenot args.copy)3.3 运行示例与效果演示我把脚本保存为video_sorter.py然后在终端里运行python3 video_sorter.py /home/me/videos/2024_01_手机拍摄 --dry-run加上--dry-run参数就是先预览一遍不会真的移动文件这个参数强烈建议第一次使用的人加上先看清楚判断结果对不对再实际执行。运行时的输出长这样共发现 45 个视频文件 [1/45] IMG_1011.MOV - 竖屏 (1080x1920, rotation0) [2/45] IMG_1012.MOV - 横屏 (1920x1080, rotation0) [3/45] IMG_1045.MOV - 竖屏 (1080x1920, rotation0) [4/45] DJI_0034.MP4 - 横屏 (3840x2160, rotation0) ... [44/45] 直播录屏_0821.mp4 - 竖屏 (1080x1920, rotation0) [45/45] 网课切片_第12讲.mp4 - 横屏 (1920x1080, rotation0) 分类完成 横屏: 27 个 竖屏: 18 个 正方形: 0 个确认预览结果没问题把--dry-run去掉真正执行移动python3 video_sorter.py /home/me/videos/2024_01_手机拍摄跑完之后文件夹结构就变成/home/me/videos/2024_01_手机拍摄/ ├── 横屏/ (27个文件) ├── 竖屏/ (18个文件) └── 正方形/ (0个文件)整个处理过程不到10秒。3.4 进阶玩法处理黑边视频和旋转视频前面说的都是基础版的横竖屏判断。但真实世界的素材往往不按套路出牌最常见的就是黑边问题一段手机竖屏视频导入剪辑软件后导出成横屏画面两侧加了黑边导出后分辨率是1920x1080但实际有效画面只有中间一条竖的。这种视频用宽高比判断会被归类为横屏但实际内容是竖屏。如果你想把它也抓出来就得做画面分析。这里的思路是用FFmpeg的cropdetect滤镜检测有效画面区域ffmpeg -i input.mp4 -vf cropdetect -f null - 21 | grep crop输出类似[Parsed_cropdetect_0 ...] x:0 y:140 w:1080 h:800 crop1080:800:0:140这个输出说明有效画面是1080x800位于整体1920x1080画面的中部上下各有140像素的黑边。如果有效画面的宽高比小于1那这个视频内容实际是竖屏内容。不过这个方法有个缺点cropdetect需要解码部分帧速度比ffprobe读元数据慢不少。我一般是先跑一遍基础版把明显横竖屏的分开剩下无法确定的再跑画面分析。这个思路适合素材量特别大的场景。4. 常见问题与排查技巧实录4.1 视频移动了但打不开先检查文件是否在传输中我第一次用这个脚本批量整理500个文件时跑完之后发现有几个文件移动到了分类文件夹但打不开提示文件损坏。排查了半天发现是那几个文件当时还在从相机SD卡往电脑拷贝的过程中文件大小没写完ffprobe读取失败导致脚本把它跳过了但后来手动移动时拷了半个文件过来。解决办法是在脚本开头加一个“文件大小稳定”检查两次读取文件大小间隔2秒如果大小一致才继续处理。这个对直接从存储卡读取素材的场景特别重要。4.2 ffprobe读取超时怎么办加超时参数有些视频文件本身结构损坏或者文件很大、存储设备很慢ffprobe读取时可能卡住导致整个脚本停在那里不动。我的做法是在subprocess.run里加timeout10超过10秒就放弃并且把超时的文件记录下来等所有文件处理完了再单独处理这些“顽固分子”。4.3 文件名重复了怎么处理自动加数字后缀不同来源的文件夹里可能都有video.mp4这个名字混合在一个目录后就会出现重名。我在脚本里做了判断如果目标文件名已存在自动在后面加_1、_2这样的数字编号避免覆盖。不过这里有个小坑如果你移动文件时跨磁盘比如从一个盘移动到另一个盘shutil.move先复制再删除复制过程中如果源文件很大且目标盘空间不足会报错。建议移动前先确认目标盘剩余空间。4.4 特殊格式支持不好补上扩展名白名单我处理的素材里出现过.mts、.m2ts这种摄像机原始格式还有.rmvb这种老古董。ffprobe本身支持几乎所有格式但脚本里的扩展名白名单得提前加好。我把常见格式都加进去了你也可能在实战中遇到冷门后缀直接把后缀补充到video_exts集合里就行。4.5 一键处理多个文件夹用循环或批量参数如果你的素材分布在多个目录比如一年12个月每个月一个目录不用每个目录分别跑一次脚本。直接在终端里用循环for dir in /home/me/videos/2024_*; do python3 video_sorter.py $dir doneLinux和macOS上这个写法直接有效Windows用PowerShell的话是Get-ChildItem D:\videos\2024_* -Directory | ForEach-Object { python3 video_sorter.py $_.FullName }4.6 常见问题速查表问题现象可能原因解决办法竖屏视频被分到横屏编码rotation为90或270宽高未对调脚本已处理确认ffprobe版本较新黑边视频分类错误分辨率为横屏但有效画面为竖屏用cropdetect做二次检测文件被跳过没分类文件被占用、权限不足或ffprobe超时查看日志输出单独处理失败文件运行时报错“FileNotFoundError”ffprobe未安装或不在PATH中先终端执行ffprobe -version验证脚本卡住不动大文件读取慢或网络驱动器问题加timeout参数设置合理超时时间目标文件名出现“(1)”同名文件已存在脚本自动命名无需额外处理移动到外接硬盘很慢跨设备移动需要复制再删除改用--copy模式先复制确认无误再手动删源文件5. 使用场景延伸与扩展玩法建议这个工具解决了横竖屏分类这一个维度的问题但同样的思路完全可以扩展到其他视频整理场景。我梳理几个我自己实际用过的扩展方向5.1 按时长分类短视频运营经常需要区分“1分钟以内的快剪素材”和“长视频成片”。在ffprobe输出信息里加上duration字段就能按视频时长自动归类到不同文件夹。逻辑很简单show_entries streamduration然后按照你设定的阈值分类。5.2 按分辨率等级归类如果你同时处理了手机素材1080P、相机素材4K、无人机素材5.4K可以在一个文件夹里再按清晰度分为“高清”“超清”“4K”等子类。判断标准就是分辨率像素量的区间划分。5.3 按拍摄日期归档ffprobe能读取到视频的创建时间tags里的creation_time按照年份和月份自动归类到不同月份目录。这个配合横竖屏分类很适用先按时间分再按比例分归档就彻底完成了。5.4 网盘和NAS自动整理在群晖或威联通的NAS上可以把这个脚本挂到定时任务里每天凌晨扫描上传目录里的新视频自动按横竖屏分类分类完发送通知到手机。这样你手机拍的素材传到NAS之后什么都不用管第二天打开盘一看该在的位置都已经就位了。5.5 批量重命名分类的同时可以顺便给文件重命名。比如竖屏视频统一改成“竖屏_日期_序号”的格式方便以后用文件名直接搜索筛选。注意批量重命名时先做dry-run确认新文件名没有冲突再实际执行。写在最后的一点经验分享这个工具看起来真的很简单代码量也不大但实际用了一年多我对它的认知也在不断加深。最初我以为分类的核心是“识别准不准”后来发现识别逻辑反而是最简单的部分真正考验人的是“异地执行时会不会出幺蛾子”——文件权限是否够、目标盘空间足不足、有没有文件正在被其他程序占用、中文文件名在跨平台移动时会不会乱码这些都是实战中必须面对的问题。我个人的建议是第一次运行务必用--dry-run模式先看预览结果正式执行前给重要素材留个备份处理超大素材库时分批跑不要一次性塞几千个文件进去——倒不是脚本处理不了而是万一中途出错了排查方范围太广。另外脚本放在本地用没问题如果你要把它分享给团队其他成员用最好再写个简单的README把依赖安装步骤和参数说明都写清楚免得同事上来就跑出个FileNotFoundError然后跑来问你。最后分享一个我自己的使用习惯我喜欢把脚本放在家里的NAS上然后手机投递视频到NAS的“待整理”共享文件夹设置定时任务每天晚上11点自动跑一遍分类脚本。第二天早上起来打开家庭相册应用素材已经被整整齐齐按横屏竖屏分好了家人都不知道背后有脚本在干活。这种“基础设施”式的工具用的时候没有感觉一旦没了才知道有多舒服。
返回列表