照片视频智能分类:按日期、分辨率与大小自动归档整理

发布时间:2026/10/8 11:19:48
照片视频智能分类:按日期、分辨率与大小自动归档整理 简介照片视频智能分类软件是一款面向摄影爱好者、普通用户及轻办公场景的多媒体批量整理工具重点解决图片视频因日期、分辨率、大小等属性混杂而难以归档的痛点。支持年、年月、年月日三种日期精度也能按预设或自定义分辨率如1920×1080、大小阈值如1MB、5MB分拣并覆盖颜色属性、曝光设置、文件格式、拍摄设备、图像方向等多维组合分类贴合主流整理需求。压缩包共3个文件分别对应Python主程序脚本、txt运行依赖清单和Markdown使用说明文档rar包整体约171.55MB便于本地部署测试。该工具出自大飞哥软件自习室操作界面简洁、分类过程实时显示进度从规则设置到分类完成均有清晰反馈。目前已有206人浏览学习适合需要快速建立多媒体归档体系、减少人工整理耗时的用户直接参考。1. 照片视频还在手动建文件夹这份智能分类工具按日期、分辨率、大小一次跑完这两年拆过的素材整理工具里被问得最频繁的就是照片视频智能分类软件按日期分类功能。拍照一时爽整理火葬场——手机、相机、截图全混在一个盘里找素材翻二十层目录最后只能靠桌面快捷方式硬撑。这份工具把整理逻辑拆成三个可独立开关的维度日期按年、年月、年月日三种精度、分辨率4K/1080P/720P/标清四档、文件大小五档再配一层格式兜底。扫描一遍源目录自动生成一套分类目录复制、移动、预览都支持。适合被几万张照片逼疯的摄影爱好者也适合批量归档工作素材的活动摄影师。最反直觉的一点按日期整理只是起点按分辨率分类往往能更快找到能用的成片特别是混拍 RAW 和手机 JPG 的场合。2. 日期分类三种精度怎么选档位参数按年/年月/年月日自由切换2.1 先谈精度按年、年月、年月日的目录差异与适用场景日期精度看着简单真落地时坑不少。设计目录之前先回答一个问题这套分类结果未来的使用者是谁如果是三年才翻一次的冷备份盘按年归档足够目录数量少扫描和迁移的成本也低。如果每个周末都在整理新素材按月最平衡一个目录几百上千个文件浏览器打开不卡按事件查找也能接受。按天分则适合活动跟拍、婚礼、野外调研这种天然按事件组织的批次每个拍摄日几十到几百个文件按天归档后不需要再手动建子目录。我一般会把默认精度设为 month。原因有两点一是大多数人的素材量按年太粗某一年可能积了一万多个文件全部摊在一个目录里以后查找照样翻车二是按天太碎日常随手拍一天三五张按天会生成一堆只有两三个文件的空壳目录目录树过碎资源管理器里浏览的成本反而更高。三种精度的目录结构差异如下精度目录结构适合场景year2023/跨年度冷备份、整盘归档month2023-06/日常照片视频混存最常用day2023-06-15/婚礼、活动跟拍、按事件整理如果你的素材本身已经按项目分好了文件夹日期精度反而可以降一档。项目文件夹承担了事件分组职责日期只需要帮你在项目内部按时间排序month 甚至 year 都够用。精度不是越高越好目录层级深了复制和移动时的路径长度也容易踩 Windows 的 MAX_PATH 限制。2.2 读拍摄时间EXIF 优先文件修改时间兜底顺序别搞反读拍摄时间的顺序直接决定分类结果可不可信。最典型的错误是一上来就取文件的创建时间或修改时间这两个时间太容易被改掉把照片从手机导到电脑再同步一次创建时间变成导入时间用 Lightroom 导出过一次修改时间变成导出时间。真正可信的只有设备按下快门时写进照片的 EXIF DateTimeOriginal 字段。视频文件没有通用的 EXIF 标准只能优先用封装格式里的创建时间没有就回落文件修改时间。这里给出一个日期提取函数优先读照片 EXIF读不到再取文件修改时间保证批量扫描不会因为单个文件损坏而中断import datetime from pathlib import Path from PIL import Image SUPPORTED_EXTS {.jpg, .jpeg, .png, .heic, .mp4, .mov, .avi, .bmp, .webp} def get_capture_time(path: Path): 优先读照片EXIF里的拍摄时间读不到再用文件修改时间兜底。 注意一定先读EXIF不要用os.path.getctime复制和同步会改掉创建时间。 lower_ext path.suffix.lower() if lower_ext in {.jpg, .jpeg, .png, .heic}: try: with Image.open(path) as img: exif img.getexif() dt exif.get(36867) # 36867 对应 EXIF DateTimeOriginal if dt: return datetime.datetime.strptime(dt, %Y:%m:%d %H:%M:%S) except Exception: # 文件损坏或读取失败时直接走兜底不要中断整个扫描 pass stat path.stat() return datetime.datetime.fromtimestamp(stat.st_mtime)这里我固定用 PIL 的getexif读 36867 标签它返回的字符串格式是2023:06:15 20:30:00strptime里必须写冒号分隔符用短横线会直接抛异常。如果 EXIF 里没有 DateTimeOriginal有些设备只写 DateTime标签 36868那是数码化的时间可能和实际拍摄时间差上一两天我只建议把它当次选不要当首选。SUPPORTED_EXTS把支持范围限定在常见照片和视频扩展名没有放 RAW 的 CR3、ARW 这类格式。RAW 的缩略图 EXIF 读取方式不一样硬要处理会让脚本复杂很多而且 RAW 文件普遍很大放进全量扫描流程会拖慢速度。有 RAW 分类需求时我一般单独跑一个只按时间和大小分类的轻量脚本不混在这套流程里。2.3 把精度做成参数build_target_dir 一行一个规则日期分类的精度必须做成运行时参数不能靠改代码切档。同一个工具备份盘里跑要按年日常整理要按月活动素材要按天每次改代码既不安全也不划算。目录拼接函数只做一件事传入时间对象、精度、根目录返回目标目录的 Path 对象def build_target_dir(date_obj: datetime.datetime, precision: str, root: Path) - Path: 按精度拼目标目录支持 year / month / day 三档 if precision year: return root / str(date_obj.year) if precision month: return root / f{date_obj.year}-{date_obj.month:02d} if precision day: return root / f{date_obj.year}-{date_obj.month:02d}-{date_obj.day:02d} raise ValueError(f不支持的精度: {precision})month 目录用2023-06而不是2023_06是为了让文件夹按名称排序时时间顺序不丢。月份和日期必须补零到两位f{date_obj.month:02d}里的02d是关键不补零的话2023-10会排在2023-2前面目录排序完全乱掉。按年场景不需要补零但统一走同一套逻辑也省心。命令行运行时精度直接作为第三个参数传入python classify.py /data/photos /data/out month dry-run python classify.py /data/photos /data/out month copydry-run 会把每个文件将要去的目录打印出来不创建任何文件。这个参数是我反复提醒自己必须加的。第一次写这个工具时我直接默认移动结果一批照片被按错误的年份挪走恢复起来非常痛苦。后来强制把 dry-run 设为默认动作只有显式传了 copy 或 move 才真正写盘。3. 分辨率与文件大小把“4K”“高清”“装不下”翻译成可执行档位3.1 分辨率怎么读PIL 读图片ffprobe 读视频文件名正则做保底分辨率读取是分类里最容易“看起来正常其实全是 unknown”的一环。图片相对省事PIL 直接读文件头就能拿到宽高不会把整张图解码进内存。视频没有统一的轻量读取方式我一般用 ffprobe它是 ffmpeg 自带的探针工具只读媒体文件头信息不会像某些方案那样把整个视频流解一遍速度差异非常明显。import json import re import subprocess from pathlib import Path def get_image_resolution(path: Path): 用PIL读图片宽高读失败返回(None, None) from PIL import Image try: with Image.open(path) as img: return img.size except Exception: return None, None def get_video_resolution(path: Path): ffprobe查视频宽高查不到就从文件名里的 数字x数字 兜底 try: out subprocess.run( [ffprobe, -v, error, -select_streams, v:0, -show_entries, streamwidth,height, -of, json, str(path)], capture_outputTrue, textTrue, timeout3, ) data json.loads(out.stdout) stream data[streams][0] return int(stream[width]), int(stream[height]) except Exception: m re.search(r(\d{3,4})[xX×](\d{3,4}), path.name) if m: return int(m.group(1)), int(m.group(2)) return None, Noneget_image_resolution返回的是 (width, height) 元组对应 PIL 的 size。注意这里读的是像素宽高不是 DPI。DPI 是打印密度和屏幕分辨率没有任何关系250 DPI 的照片拿到手机上看依然是 1080P 屏幕的显示效果。以前见过有人把 DPI 当分辨率分类结果一批扫描件全混在一起属于典型的概念错位。get_video_resolution用-select_streams v:0只取第一个视频流。有些视频文件包含多个视频流不指定的话 ffprobe 会返回一堆结果解析时索引容易出错。timeout3是防止网络盘上的文件卡死整个流程有些网络存储设备读取慢ffprobe 会一直等脚本就没响应了。文件名正则兜底是最后手段匹配1920x1080、3840x2160这种写法能救回文件头损坏但文件名还靠谱的视频也会误判文件名乱写的文件所以优先级必须放最后。3.2 档位规则用最长边匹配竖拍照片不再误判档位设计是这个分类工具的灵魂。很多人把规则写成width 3840就归 4K这个写法在横拍素材里没问题遇到竖拍就翻车。手机竖拍一张 1080x1920 的照片如果用width 1920判断 1080P它会被误判成 720P 甚至标清档用户就会抱怨“我明明拍的高清怎么分到了标清”。正确做法是拿最长边 max(width, height) 做比较竖拍横拍一视同仁。规则文件用 JSON 维护档位从高到低排列{ resolution_rules: [ {name: 4K, min_edge: 3840}, {name: 1080P, min_edge: 1920}, {name: 720P, min_edge: 1280}, {name: SD, min_edge: 0} ] }匹配函数按最长边从大到小找第一个命中的档位def classify_by_resolution(width: int, height: int, rules) - str: 按最长边匹配档位竖拍照片不会因为宽度被误判 edge max(width, height) for rule in sorted(rules, keylambda r: r.get(min_edge, 0), reverseTrue): if edge rule[min_edge]: return rule[name] return unknown用最长边判断3840x2160 的横拍 4K 和 2160x3840 的竖拍 4K 都会归入 4K 档行为正确且符合直觉。如果自己的素材全是横拍或者项目硬性要求按宽度分类把edge max(width, height)换成edge width即可规则文件不用动。rules排序从大到小第一个命中就返回避免大文件同时满足多档SD 档的 min_edge 为 0保证任何非负长宽的图都能兜底。不建议用“面积”作为档位标准。视频分辨率虽然是宽乘高但行业里说 4K 指的是 3840x2160说 1080P 指的是 1920x1080面积和档位名的对应关系很乱还容易把 4096x2160 这种 DCI 4K 和 UHD 4K 混在一起。按最长边匹配最接近用户直觉。3.3 文件大小分类阈值统一用 1024 进制的 MB别设太碎文件大小分类的逻辑比分辨率简单但最容易犯的错是档位太碎。10MB、100MB、1GB、10GB 四档已经足够日常使用再加 20MB、50MB 档目录区分度反而下降。文件大小分类的价值是快速定位“能发微信的”“没法传邮件的”“工程级大文件”档太碎会让目录失去意义。规则文件里max_mb: null表示不设上限排序时永远放最后{ size_rules: [ {name: tiny, max_mb: 1}, {name: small, max_mb: 10}, {name: medium, max_mb: 100}, {name: large, max_mb: 1024}, {name: huge, max_mb: null} ] }def classify_by_size(file_size_bytes: int, size_rules) - str: 按字节数分档规则里单位用MB内部统一转成字节比较 for rule in sorted(size_rules, keylambda r: r.get(max_mb, 0)): if rule.get(max_mb) is None: return rule[name] if file_size_bytes rule[max_mb] * 1024 * 1024: return rule[name] return unknown内部换算统一用1024 * 1024不要用1000 * 1000。硬盘厂商按 1000 标称容量但文件系统分配空间始终是 1024 进制的块分类标准不一致会导致同一个文件在归档后换一台电脑就“变了档”属于典型的给自己埋坑。sorted按 max_mb 从小到大排null 档位的 key 为 0 会排到最前面所以要先判断 None 直接返回保证它只兜最后一档。4. 把三套规则串成智能分类流水线dry-run、规则文件、大目录扫描一起解决4.1 主入口与三种动作dry-run 先行复制/移动二选一前面第 2 章、第 3 章写完核心函数这里把它们编成一个可执行的入口。一个能日常使用的工具至少要有三种动作dry-run 只打印预览copy 复制move 移动。我默认不提供删除动作删除源文件应该由用户在确认结果后自己执行工具不应该碰这个权限。import shutil import sys import json from pathlib import Path def classify_one(path: Path, config: dict, base_out: Path, action: str): date_obj get_capture_time(path) date_dir build_target_dir(date_obj, config.get(date_precision, month), base_out) width, height None, None if path.suffix.lower() in {.jpg, .jpeg, .png, .bmp, .webp, .heic}: width, height get_image_resolution(path) else: width, height get_video_resolution(path) res_name classify_by_resolution(width or 0, height or 0, config[resolution_rules]) size_name classify_by_size(path.stat().st_size, config[size_rules]) target date_dir / res_name / size_name if action dry-run: print(f[DRY] {path.name} - {target.relative_to(base_out)}) else: target.mkdir(parentsTrue, exist_okTrue) if action copy: shutil.copy2(path, target / path.name) elif action move: shutil.move(str(path), str(target / path.name)) def iter_media_files(source: Path, exclude: set): 生成器递归遍历源目录跳过输出目录避免二次扫描 for path in source.rglob(*): if not (path.is_file() and path.suffix.lower() in SUPPORTED_EXTS): continue if any(str(path).startswith(str(ex)) for ex in exclude): continue yield path if __name__ __main__: source_root Path(sys.argv[1]) out_root Path(sys.argv[2]) precision sys.argv[3] if len(sys.argv) 3 else month action sys.argv[4] if len(sys.argv) 4 else dry-run config json.loads(Path(rules.json).read_text(encodingutf-8)) config[date_precision] precision for path in iter_media_files(source_root, {out_root}): classify_one(path, config, out_root, action)classify_one按“日期/分辨率/大小”的优先级拼目标目录外层是日期中层是分辨率内层是大小。这个顺序可以按个人习惯调整比如你希望按分辨率目录找同一天的照片把 res_dir 放外层即可。目标目录用exist_okTrue避免多线程同时创建目录时报FileExistsError。dry-run 模式下只打印相对路径。真正处理时用shutil.copy2而不是shutil.copycopy2 会保留文件元数据mtime 是很多文件最后的兜底信息浅拷贝会把这条信息丢掉。move 动作把 path 转成字符串传给shutil.move是为了兼容部分 Python 版本对 Path 对象的处理差异字符串形式最稳。4.2 rules.json 规则文件改阈值不用动代码格式分类也挂在这层完整的 rules.json 把日期精度、分辨率档位、大小档位集中管理日常调阈值只需要改这个文件{ date_precision: month, resolution_rules: [ {name: 4K, min_edge: 3840}, {name: 1080P, min_edge: 1920}, {name: 720P, min_edge: 1280}, {name: SD, min_edge: 0} ], size_rules: [ {name: tiny, max_mb: 1}, {name: small, max_mb: 10}, {name: medium, max_mb: 100}, {name: large, max_mb: 1024}, {name: huge, max_mb: null} ] }规则文件的好处是改档位门槛极低。今天想把 1080P 的下限从 1920 改成 1600直接编辑 JSON 重新跑一次 dry-run 看效果不碰任何代码。命令行里的precision参数会覆盖规则文件里的 date_precision因为日期精度属于高频切换项放命令行更顺手。如果你的核心场景其实就是照片按格式分类软件格式规则也可以挂在这一层。格式是所有维度里最简单的扩展名到目录名的一次映射而已不需要单独做一套逻辑format_rules: { .jpg: JPG, .png: PNG, .heic: HEIC, .mp4: MP4, .mov: MOV }格式规则接入后目标目录会变成 date/res/size/format 四层找文件时更精确但目录层级过深复制时路径长度也容易越限。我一般是只在 unknown 目录里按格式再拆一层方便排查解析失败的文件正常文件不单独按格式建目录。4.3 上万文件怎么扫生成器遍历并跳过输出目录大批量目录扫描最常见的翻车点是在扫描前先把所有路径收集进一个列表再慢慢处理。rglob本身是惰性的但有人习惯在外面套个list()百万级文件时光路径字符串就占掉几百 MB 内存。上文的iter_media_files用生成器包一层一次只产生一个路径内存占用稳定在几十 MB这是处理十万张照片的基本功。排除输出目录的逻辑这里要特别小心。str(path).startswith(str(ex))虽然简单但有个边界问题如果输出目录是/data/out而源目录里恰好有个/data/out100的真实文件夹startswith 会把/data/out100里的文件也排除掉。稳妥写法是判断目录边界import os def is_excluded(path: Path, exclude: set): for ex in exclude: if str(path) str(ex) or str(path).startswith(str(ex) os.sep): return True return False用os.sep而不是硬编码/Windows 上也能正确判断。把输出目录挂载到源目录内部时这一步没做对第二次跑分类就会把第一次生成的结果原样再复制一遍目录文件量直接翻倍我在下一章的避坑记录里会专门展开。5. 避坑日期跑到 1970、文件名带空格、二次扫描重复复制这五个坑最常踩5.1 日期全变成 1970 年或者清一色 2002 年现象分类结果里出现一个巨大的 1970 目录里面堆了几百个文件或者所有照片都归到 2002 年。前者是文件修改时间戳为 0后者是相机主板电池耗尽恢复出厂时间。照片本身没问题但分类结果完全没法用。原因EXIF 读取失败或者这批文件根本不是照片原文件。相机如果没有写 EXIFget_capture_time 会落到 mtime而很多同步软件把 mtime 改成了迁移时间。2002 年这个值特别有迷惑性看起来像真实年份实际是相机出厂固件里写死的时钟。解决在 get_capture_time 加一个年份边界检查超出常识范围的日期打警告不要直接归类EARLIEST_YEAR 1980 latest datetime.datetime.now() datetime.timedelta(days1) if date_obj.year EARLIEST_YEAR or date_obj latest: print(f[WARN] 时间异常: {path} - {date_obj})警告信息至少能让你知道哪些文件走了兜底。如果确认某批 RAW 文件根本没有 EXIF可以在命令行加一个偏移参数整体修正我一般会给主流程加--shift-years N把识别出来的异常批次整体平移。5.2 文件名带空格和 #路径拼接直接跳飞现象脚本跑到一半报FileNotFoundError或者复制出来的文件路径断成两截目标目录里出现名不副实的空文件夹。原因这是典型的字符串拼接路径问题。早期版本我图省事用os.path.join拼路径遇到文件名含空格、#、这类特殊字符时shutil 内部解析错乱尤其当文件名里带空格时str(path).split( )这类操作会直接把路径拆碎。解决全程使用pathlib.Path对象禁止手拼路径字符串。需要调用外部命令时subprocess 必须用列表传参会自动处理特殊字符subprocess.run([ffprobe, -v, error, str(path)], ...)不要写成fffprobe -v error {path}再丢给 shell。现在第 3 章的 ffprobe 调用已经是用列表传参这个习惯要保持到所有外部命令。复制和移动目标目录也一律target / path.name不要自己拼。5.3 输出目录被当成输入目录二次分类复制出双份现象第一次分类跑完目录结构看着正常。隔几天再跑一次输出目录里文件数量翻倍同一张照片出现在 2023-06/4K/large/ 和 2023-06/4K/large/ 两个位置内容一模一样。原因源目录包含输出目录或者输出目录挂载在源目录内部iter_media_files 递归时把上次生成的分类结果又扫进来源。最典型的是用户把源盘和输出盘都指向/datarglob 把所有文件全遍历了一遍。解决除排除目录外在输出根目录放一个标记文件作为双保险# 首次初始化时在输出根目录写入标记 (output_root / .classify_done).write_text(, encodingutf-8)扫描时检测到标记文件就直接跳过整个输出根目录。这个跟排除目录二选一其实也够用但两个都做最稳特别是输出目录换了挂载点或盘符时标记文件不会失效。分类完成后别删源目录里的原文件先核对数量差再说。5.4 ffprobe 扫描视频奇慢5GB 视频卡住整个流程现象照片分类几小时能跑完加了几百个视频后直接卡死单个 5GB 视频处理十几分钟都没结束只能 kill 进程。原因每视频调一次 ffprobe 进程启动开销本身就不小部分网络盘读取慢timeout3之后重试逻辑又反复触发等于每个视频都在耗三倍时间。更糟的是有些脚本在视频文件上误用Image.open试图把视频当图片解码那速度完全是灾难。解决视频文件名里已经带分辨率时直接跳过 ffprobem re.search(r(\d{3,4})[xX]\d{3,4}, path.name) if m: width, height int(m.group(1)), int(m.group(2)) else: width, height get_video_resolution(path)仍然需要 ffprobe 的视频可以把扫描任务改成线程池并发I/O 密集用线程就够不用上进程池from concurrent.futures import ThreadPoolExecutor with ThreadPoolExecutor(max_workers8) as pool: list(pool.map(lambda p: classify_one(p, config, out_root, action), iter_media_files(source_root, exclude)))8 个并发线程对 ffprobe 这种轻量进程是安全的不会把 CPU 打满。最重要的是把超时时间和跳过逻辑放在前面不要让大文件拖住整批。5.5 HEIC 照片全进 unknownPillow 默认读不了现象iPhone 用户占比大的素材库里unknown 目录里堆满 .heic 文件其他分类逻辑都正常唯独这批手机照片全没进去。原因Pillow 默认不支持 HEIC 容器格式Image.open()会直接抛异常get_image_resolution 返回 (None, None)classify_by_resolution 只能落到 unknown。日期读取也一样HEIC 的 EXIF 在 Pillow 原生状态下根本读不到全部掉到 mtime 兜底。解决装 pillow-heif 插件并在脚本入口注册import pillow_heif pillow_heif.register_heif_opener()注册后第 2 章和第 3 章的代码不用改一行Image.open(path)就能正常读 HEICEXIF 也能取到。如果不想引入插件可以在 SUPPORTED_EXTS 里把 .heic 单独拆出来直接按文件和大小分类绕开分辨率读取。苹果设备占比高的项目里HEIC 迟早会遇到提前处理比等 unknown 目录爆炸再补救省事得多。6. 分类完不急着删验证三步走、硬链接免复制、按月份断点续跑6.1 验证文件数与 unknown 目录跑完分类第一件事不是看目录而是核数量。用两个 find 命令对比源目录和目标目录的文件数find /data/source -type f | wc -l find /data/out -type f ! -name .classify_done ! -name .DS_Store | wc -l差值应该等于日志里记录的解析失败数量。接着抽查 unknown 目录里的三个文件确认是 HEIC 未识别、文件损坏还是文件名完全无规律。最后看每个日期目录的大小分布如果某个月的目录大小异常地小说明那一批素材的 EXIF 大概率没读到回到了第 5.1 节的时间异常检查流程。6.2 后悔药先复制两周再用硬链接建同盘目录分类工具最怕的是“跑完才发现规则设错了”尤其是 move 模式一旦按错误规则挪完恢复成本极高。我的习惯是第一次永远 dry-run第二次 copy确认两侧文件数一致后把源文件保留两周再清理。如果源目录和目标目录在同一个分区还可以用硬链接快速建出一套分类目录不占双倍空间if action link: target.mkdir(parentsTrue, exist_okTrue) os.link(str(path), str(target / path.name))硬链接只对同一分区的文件系统有效跨磁盘会直接抛 OSError所以 link 动作要放在 copy 之后作为备选。硬链接的好处是源文件删除后链接方的数据仍然完整适合做“先建立分类索引再慢慢整理原盘”的工作流。6.3 断点续跑按月份过滤增量任务几万张照片跑一半断电很常见与其每次从头扫不如给主流程加两个时间过滤参数。把第 4 章的 main 换成 argparse 版本import argparse argp argparse.ArgumentParser() argp.add_argument(source) argp.add_argument(out) argp.add_argument(--precision, defaultmonth) argp.add_argument(--action, defaultdry-run) argp.add_argument(--since, defaultNone) argp.add_argument(--until, defaultNone) args argp.parse_args() since datetime.datetime.fromisoformat(args.since) if args.since else None until datetime.datetime.fromisoformat(args.until) if args.until else Noneclassify_one 里加两行过滤if since and date_obj since: return None if until and date_obj until: return None配合月份精度使用断电后可以指定从上次中断的月份继续不必重扫全盘。批量分类工具做到这一步基本可以放心交给它处理大目录了。最初我不太信任自动分类总觉着一次跑完就完事直到有一次重要视频误 move 找不到原目录才把流程固定成 dry-run 预览、copy 跑通、硬链接建立索引、两周后确认无误才清理源盘。从那以后我每次给客户整理素材都强制走一遍这套流程希望帮到你。本文还有配套的精品资源点击获取