1实现原理 · 为什么它能做到
实现路径是单个 Python 脚本编排 ffmpeg/ffprobe:ffprobe 取元数据 → ffmpeg 算 PSNR/SSIM → ffmpeg 按间隔抽帧缩放 → 把帧与指标填进一份 HTML 模板 → 落盘报告。没有模型参与,纯确定性流水线。
for tool in ['ffmpeg', 'ffprobe']:
报告不是「先写模板再渲染」,而是「读模板 + 用正则逐块替换占位」——因此对模板结构高度耦合,同一段落换行或元素位置一变即静默失配。
r'<div class="metric-value">[\d.]+ dB</div>\s*<div class="metric-subtitle">偏低</div>',
帧序列不是内嵌进 HTML,而是复制到 HTML 同级的子目录(original/ 与 wechat/),由模板用相对路径引用——即产物是一组文件而非单文件。
Copy frames to a subdirectory next to the HTML output. Args: frames: List of (timestamp, frame_path) tuples output_html_path: Path to the output HTML file subfolder: Subdirectory name (e.g., 'original', 'wechat')
输入校验是一等公民:路径 resolve 后检查存在性/是文件/扩展名白名单/可读/体积上限,ffmpeg 与 ffprobe 的存在性单独预检并给出各平台安装指引。
# Configuration constants ALLOWED_EXTENSIONS = {'.mp4', '.mov', '.avi', '.mkv', '.webm'} MAX_FILE_SIZE_MB = 500 FFMPEG_TIMEOUT = 300 # 5 minutes FFPROBE_TIMEOUT = 30 # 30 seconds BASE_FRAME_HEIGHT = 800 FRAME_INTERVAL = 5 # seconds
命令注入防护靠「参数列表 + 无 shell=True + 超时」三件套,而不是字符串转义——即 subprocess 的正确用法。
try: result = subprocess.run( args, capture_output=True, timeout=timeout, check=True, text=True ) return result.stdout
在算指标前先做「两段视频是否同一内容」的一致性校验(时长差阈值、压缩后反而变大时告警),避免把不同视频的对比当成质量分析输出。
""" Validate that two videos are likely the same content. Args: metadata1: First video metadata (original) metadata2: Second video metadata (compressed) duration_threshold: Maximum allowed duration difference in seconds allow_size_increase: If False, warn when compressed is larger
2核心能力
3外部依赖
| 类型 | 依赖 |
|---|---|
| cli | ffmpeg(必需;抽帧、缩放、算 PSNR/SSIM) |
| cli | ffprobe(必需;元数据提取) |
| cli | python3(3.8+,脚本运行时;仅标准库) |
| package | img-comparison-slider(Web Component,报告页从 CDN 加载,非随包分发) |
| network | unpkg(第三方 CDN;打开报告时由浏览器发起请求) |
| network | ffmpeg 官网(文档里的安装指引链接,非运行时依赖) |
4风险提醒 风险提醒:黄色 · 留意使用
- 「self-contained / works offline」是错误声明,会导致误用 — 实际产物是 HTML + original/ + wechat/ 目录,且滑块组件从 unpkg CDN 加载。若按文档只取走 HTML 文件、或在离线环境打开,会丢帧或失去滑块功能。建议以实际产物结构为准(整目录一起搬,且需联网才有滑块)。
- 报告页存在第三方 CDN 供应链暴露 — 任何打开报告的人都会向 unpkg.com 请求并执行 img-comparison-slider 的脚本。若报告被当作内部静态文件流转,这属于未预期的对外请求;CDN 内容一旦被篡改即影响所有打开者。介意的话可把该 JS/CSS 下载到本地并改写模板引用。
- 输出目录会被静默覆盖 — 脚本在与报告同级处创建 original/ 与 wechat/ 并写入 frame_001.png…,未检查已存在同名内容;对同一输出目录重复运行会覆盖旧帧(旧报告帧数更多时还会残留多余帧,导致报告与实物不一致)。
- 文档漂移面较大 — README 行数(696 vs 实际 1036)、架构清单(漏 configuration.md)、base64 内嵌声明、CDN 声明与 SKILL.md 互相矛盾;README 与 SKILL.md 对同一产物给出不同描述。使用者应先以代码与产物实测为准。
- 模板正则耦合,改动易静默失效 — 报告生成依赖对固定模板文本的 11 处 re.sub/replace(含 r'<div class="frame-selector">.*?</div>' 这类 DOTALL 匹配)。一旦改模板结构,替换会静默不生效(正则无匹配也不报错),产出的报告会保留占位内容。
- 默认专用词汇与非微信场景不匹配 — 帧目录写死 wechat/、标签写「微信视频号」「微信重新编码」。用于其他平台(B 站/抖音/YouTube)时产物文案会张冠李戴,需改模板与源码常量。
5第二遍独立确认
- [discrepancy] SKILL.md 的『frames 以 base64 内嵌进 self-contained HTML』声明是否成立 — 不成立,是本次侦查发现的最重问题。SKILL.md 第 99 行:'The script extracts frames at specified intervals (default: 5 seconds), scales them to consistent height (800px) for comparison, and embeds them as base64 data URLs in self-contained HTML. Temporary files are automatically cleaned after processing.' 实际实现是把帧复制到 HTML 同级子目录:copy_frames_to_output 的 docstring 即 'Copy frames to a subdirectory next to the HTML output.',dest_name = f"frame_{i:03d}.png"、shutil.copy2(frame_path, dest_path),调用处为 copy_frames_to_output(original_frames, output_path, 'original') 与 (compressed_frames, output_path, 'wechat')。全目录 grep base64/b64encode/data:image 在 .py 与 .html 中零命中(仅出现在 README 的依赖清单与 configuration.md 的说明文字里)。模板侧确认用相对路径:<img slot="first" id="originalImage" src="original/frame_001.png" ...>。即产物是「HTML + original/ + wechat/」一整个目录,不是单文件。
- [discrepancy] SKILL.md 的『Self-contained format (no server required, works offline)』是否成立 — 不成立(两条独立原因)。SKILL.md 第 108 行原文:'- Self-contained format (no server required, works offline)'。①存在外部 CDN 依赖:template.html 第 7-8 行加载 https://unpkg.com/img-comparison-slider@8/dist/styles.css 与 .../index.js,断网时滑块组件不可用;②依赖同级帧目录(见上一条),单独拷走 HTML 会丢帧。值得注意的是 README 自己承认了 CDN 依赖(依赖清单写 'img-comparison-slider (loaded from CDN)'),却同时在架构说明里声称 template.html 具备 'Base64-encoded image embedding'——即 README 内部亦不自洽,且与 SKILL.md 的 offline 声明互相矛盾。
- [discrepancy] README 的『Architecture』信息是否与文件系统/实现一致 — 三处不符:①行数——README 写 'compare.py # Main comparison script (696 lines)',实测 wc -l = 1036 行;②架构清单只列 video_metrics.md 与 ffmpeg_commands.md 两份 reference,漏掉实际存在且被 SKILL.md 明确引用的 references/configuration.md;③把 template.html 描述为 'Base64-encoded image embedding',与实现不符(见第 1 条)。
- [discrepancy] 文档是否宣称了未实现的能力(VMAF 等) — SKILL.md 层面是对的:它只说两个指标(PSNR、SSIM),与脚本一致(grep -i vmaf 在 compare.py 中零命中)。但 references/video_metrics.md(4 处 VMAF)与 references/ffmpeg_commands.md(3 处,含 'VMAF Calculation' 小节)都提供了 VMAF 的定义与 ffmpeg 命令。即参考材料覆盖了脚本未实现的第三指标——不是夸大声明(SKILL.md 未据此承诺),而是「参考材料超出工具能力」,使用者若按 reference 期待 VMAF 输出会落空。
- [ok] 三条安全声明(path validation / command injection prevention / resource limits)是否有代码对应 — 逐条核实成立:①path validation —— validate_video_file 内 Path(path).resolve()、exists()、is_file()、后缀白名单 ALLOWED_EXTENSIONS、os.access(R_OK)、体积上限 MAX_FILE_SIZE_MB=500;②command injection prevention —— run_ffmpeg_command 以参数列表调用 subprocess.run(docstring:'args: Command arguments as list (prevents shell injection)'),全文无 shell=True;③resource limits —— FFMPEG_TIMEOUT=300、FFPROBE_TIMEOUT=30,且 TimeoutExpired 有专门异常映射。声明未夸大。
- [ok] 是否存在未声明的网络/凭证行为 — 反例检索失败:compare.py 的 import 段为 argparse/json/logging/os/re/subprocess/sys/tempfile/time/pathlib/typing,无 urllib/requests/socket/http;全目录 grep token/secret/password/api_key/getenv/os.environ 零命中;无 base64 大块、无 >200 字符无空格长行。结论成立。
- [discrepancy] 模板的『通用性』呈现与内容是否相符 — SKILL.md/README 把它呈现为通用的视频压缩对比工具,但 assets/template.html 的文案与路径是微信专用的:标签为「🎬 原始视频 (AVC)」与「📱 微信视频号 (HEVC)」、替换目标里有 '<div class="metric-subtitle">微信重新编码</div>'、帧相对路径写死 'wechat/frame_001.png',且 compare.py 的复制调用也写死 subfolder='wechat'(而非 'compressed')。结论:工具可用,但默认产物带有上游使用场景的专用词汇;非微信场景需自行改模板与源码常量。
- [ok] pin commit 与 skill.path 是否与任务书表格一致 — git rev-parse HEAD = d5c4678cb5d4fd6acc9c922690df035dbd33d247,与任务书表格『库内 HEAD』逐字一致;目录位于仓库根,相对路径 video-comparer;本目录最后一次提交 878f947(2026-09-07)。GitHub API 复核 MIT / stars 1392 / pushed 2026-09-15T09:30:04Z。
6结论
f6f34839800154ed…d5c4678cb5