fix(ingest): 修复竖屏 HEVC 转码方向错误(双重旋转/ffprobe 取错行/逗号转义) #1559

Merged
auto-approve-bot merged 5 commits from fix/ingest-hevc-portrait-rotation into develop 2026-08-30 21:56:02 +08:00
Owner

问题

竖屏 iPhone HEVC 素材入库转码后方向错乱:竖屏(rotation=90/270)转码产物变成 1920×1080 横屏,导致素材库展示方向错误。

根因(ffmpeg 4.4.2 实测复现)

  1. 双重旋转:ffmpeg 默认 autorotate 已按 display matrix 物理旋转画面,代码又显式追加 transpose=1,r90 竖屏被转成 1920×1080 横屏。
  2. ffprobe 取错行:探测命令同时输出 side_data=rotationstream_tags=rotate 两行(r90 文件输出 270\n90),split("\n")[0] 取到 270,方向判定反了。
  3. 逗号未转义scale=if(gt(ih,1080),...) 中裸逗号被当作 filter 分隔,报 Invalid size,按要求统一 \, 转义。

修复

  • 旋转全部交给 ffmpeg autorotate,滤镜只做 scale(实测产物自动物理固化方向且无 side data)。
  • probe_rotation 只读 side_data 单行并 int 化,异常返回 None。
  • 竖屏按宽、横屏按高缩放,长边封顶 1080、只缩不放;竖屏 scale=if(gt(iw\,1080)\,1080\,iw):-2,横屏 scale=-2:if(gt(ih\,1080)\,1080\,ih)
  • 移除无效的 -metadata:s:v:0 rotate=0(实测清不掉 display matrix)。
  • 转码后强制方向/维度校验 validate_transcode_output:竖屏须 h>w、横屏须 w>=h、长边 ≤1920、无残留 rotation side data;校验失败打 error 日志并降级使用原始文件、不上传 OSS,杜绝横屏文件覆盖。

测试

  • test_ingest_hevc_transcode.py(重写,22 用例):纯逻辑断言 + 真实 ffmpeg 端到端(造带 display matrix 的 r90/r270/横屏/4K HEVC 素材,断言 1080×1920 / 1920×1080 / h264 / 无 side data / 缩略图竖版方向正确)。
  • test_ingest_hevc_transcode_task.py(新增,4 用例):任务级 mock 验证成功改写 storage_key、校验失败降级+error 日志、ffmpeg 非零降级、非 HEVC 不转码。
  • 本地 26 用例全过;ingest 相关 151 用例回归全过;black/isort/flake8 干净。

存量数据

staging 上已转错的竖屏素材(IMG_2281_h264.MOV、IMG_2278_h264.MOV 等)需重新触发转码或删除重传,部署后另行处理。

## 问题 竖屏 iPhone HEVC 素材入库转码后方向错乱:竖屏(rotation=90/270)转码产物变成 1920×1080 横屏,导致素材库展示方向错误。 ## 根因(ffmpeg 4.4.2 实测复现) 1. **双重旋转**:ffmpeg 默认 autorotate 已按 display matrix 物理旋转画面,代码又显式追加 `transpose=1`,r90 竖屏被转成 1920×1080 横屏。 2. **ffprobe 取错行**:探测命令同时输出 `side_data=rotation` 和 `stream_tags=rotate` 两行(r90 文件输出 `270\n90`),`split("\n")[0]` 取到 270,方向判定反了。 3. **逗号未转义**:`scale=if(gt(ih,1080),...)` 中裸逗号被当作 filter 分隔,报 `Invalid size`,按要求统一 `\,` 转义。 ## 修复 - 旋转全部交给 ffmpeg autorotate,滤镜只做 scale(实测产物自动物理固化方向且无 side data)。 - `probe_rotation` 只读 side_data 单行并 int 化,异常返回 None。 - 竖屏按宽、横屏按高缩放,长边封顶 1080、只缩不放;竖屏 `scale=if(gt(iw\,1080)\,1080\,iw):-2`,横屏 `scale=-2:if(gt(ih\,1080)\,1080\,ih)`。 - 移除无效的 `-metadata:s:v:0 rotate=0`(实测清不掉 display matrix)。 - 转码后强制方向/维度校验 `validate_transcode_output`:竖屏须 h>w、横屏须 w>=h、长边 ≤1920、无残留 rotation side data;**校验失败打 error 日志并降级使用原始文件、不上传 OSS**,杜绝横屏文件覆盖。 ## 测试 - `test_ingest_hevc_transcode.py`(重写,22 用例):纯逻辑断言 + 真实 ffmpeg 端到端(造带 display matrix 的 r90/r270/横屏/4K HEVC 素材,断言 1080×1920 / 1920×1080 / h264 / 无 side data / 缩略图竖版方向正确)。 - `test_ingest_hevc_transcode_task.py`(新增,4 用例):任务级 mock 验证成功改写 storage_key、校验失败降级+error 日志、ffmpeg 非零降级、非 HEVC 不转码。 - 本地 26 用例全过;ingest 相关 151 用例回归全过;black/isort/flake8 干净。 ## 存量数据 staging 上已转错的竖屏素材(IMG_2281_h264.MOV、IMG_2278_h264.MOV 等)需重新触发转码或删除重传,部署后另行处理。
xiaoxia added 1 commit 2026-08-30 19:58:17 +08:00
fix(ingest): 修复竖屏 HEVC 转码方向错误(双重旋转/ffprobe 取错行/逗号转义)
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 1s
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 3m1s
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 3m39s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 1m19s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 1m24s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 4m35s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 5m50s
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 7m51s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 8m43s
AI Code Review / AI Code Review (pull_request) Successful in 9m14s
CI/CD Pipeline / Validate - Code Quality (pull_request) Successful in 17m57s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 16m37s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / CI Gate (pull_request) Has been cancelled
18aed0c34b
竖屏 iPhone HEVC 素材转码后方向错乱(竖屏变横屏),实测定位三个根因:

1. 双重旋转:ffmpeg 默认 autorotate 已按 display matrix 物理旋转画面,
   代码又显式追加 transpose=1,导致 r90 竖屏被转成 1920x1080 横屏。
   旋转全部交给 autorotate,滤镜只做 scale。
2. ffprobe 取错行:探测命令同时输出 side_data=rotation 和
   stream_tags=rotate 两行(r90 文件输出 270/90 两行),split("\n")[0]
   取到 270,方向判定反了。改为只读 side_data 单行并 int 化。
3. scale 表达式逗号未转义:if(gt(ih,1080),...) 中的裸逗号被当作
   filter 分隔,报 Invalid size。统一用 \, 转义(raw string 字面量)。

附带修复:
- 竖屏按宽、横屏按高缩放(旋转后竖屏 w<h,旧代码竖屏沿用按高缩放
  会把 1080x1920 错缩为 608x1080),长边封顶 1080 只缩不放。
- 移除无效的 -metadata:s:v:0 rotate=0(实测清不掉 display matrix
  side data,autorotate 方案产物本身已无 side data)。
- 转码后强制方向/维度校验 validate_transcode_output:竖屏产物必须
  h>w、横屏 w>=h、长边 <=1920、无残留 rotation side data;校验失败
  打 error 日志并降级使用原始文件、不上传 OSS,杜绝横屏文件覆盖。

测试:
- test_ingest_hevc_transcode.py 重写(22 用例):纯逻辑断言 +
  真实 ffmpeg 端到端(r90/r270/横屏/4K 素材造 display matrix,
  断言 1080x1920 / 1920x1080 / h264 / 无 side data / 缩略图竖版)。
- test_ingest_hevc_transcode_task.py 新增(4 用例):任务级 mock
  验证成功改写 storage_key、校验失败降级+error 日志、ffmpeg 非零
  降级、非 HEVC 不转码。

🚀 预览环境已部署

项目 详情
PR号 #1559
预览链接 https://pr-1559.preview.xiaoxiajianji.com
API环境 staging

💡 预览环境使用 staging API 数据,请勿在预览环境中操作重要数据。

🔄 每次提交新代码后预览环境会自动更新。

🗑️ PR 关闭或合并后,预览环境会自动清理。

🚀 **预览环境已部署** | 项目 | 详情 | |------|------| | PR号 | #1559 | | 预览链接 | [https://pr-1559.preview.xiaoxiajianji.com](https://pr-1559.preview.xiaoxiajianji.com) | | API环境 | staging | > 💡 预览环境使用 staging API 数据,请勿在预览环境中操作重要数据。 > > 🔄 每次提交新代码后预览环境会自动更新。 > > 🗑️ PR 关闭或合并后,预览环境会自动清理。
auto-approve-bot approved these changes 2026-08-30 20:20:30 +08:00
auto-approve-bot left a comment
Collaborator

CI全绿,自动审批通过。

CI全绿,自动审批通过。
auto-approve-bot approved these changes 2026-08-30 20:20:30 +08:00
auto-approve-bot left a comment
Collaborator

CI全绿,自动审批通过。

CI全绿,自动审批通过。
xiaoxia added 1 commit 2026-08-30 20:24:35 +08:00
test(ingest): 端到端素材兼容 ffmpeg 7.x(-display_rotation 优先,老式 rotate metadata 回退)
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 1s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 2m49s
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 1m6s
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 4m22s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 30s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 4m34s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 5m44s
AI Code Review / AI Code Review (pull_request) Failing after 6m21s
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 7m53s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 9m26s
CI/CD Pipeline / Validate - Code Quality (pull_request) Successful in 14m50s
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Build Production API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Production (pull_request) Has been cancelled
CI/CD Pipeline / Production Browser E2E (pull_request) Has been cancelled
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / CI Gate (pull_request) Has been cancelled
5e3ee08d26
CI 环境为 ffmpeg 7.1.5(Debian 13),本地为 4.4.2。7.x 的 mp4 muxer
在 stream copy 时不再把老式 -metadata:s:v:0 rotate=N 写入 display
matrix,导致竖屏测试素材不带旋转信息、probe_rotation 返回 None。

tag_rotate 改为双路径:优先 -display_rotation(5.1+ 支持),失败回退
-metadata rotate(4.x);造完用 probe_rotation 自检,均失败则返回
None,4 个端到端用例对 None 源 pytest.skip,不再误报失败。
xiaoxia added 1 commit 2026-08-30 20:41:35 +08:00
fix(ingest): 方向判定改为按显示方向(物理竖屏 rotation=0 不再误判横屏)
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 1s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 2m30s
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 1m18s
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 4m6s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 4m19s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 31s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 5m26s
AI Code Review / AI Code Review (pull_request) Failing after 7m44s
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 8m21s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 10m34s
CI/CD Pipeline / Validate - Code Quality (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Build Production API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Production (pull_request) Has been cancelled
CI/CD Pipeline / Production Browser E2E (pull_request) Has been cancelled
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / CI Gate (pull_request) Has been cancelled
b03d19cf32
Code Review 阻塞问题修复:

旧逻辑仅以 rotation side data 判定竖屏(is_portrait_rotation),对
"已物理旋转、存储即 h>w、rotation=0/None"的竖屏视频(Android 拍摄常见)
会误判为横屏:套用横屏按高缩放滤镜把 1080x1920 压成 608x1080,产物被
validate_transcode_output 拦截后降级使用原始 HEVC 文件——此类视频在
浏览器仍无法播放(HEVC 黑帧),转码链路对这类素材完全失效。

修复:
- 新增 is_portrait_video(stored_w, stored_h, rotation):rotation 为
  90/270/-90 时显示宽高相对存储维度互换,否则显示方向即存储维度,按
  显示 h>w 判定竖屏;维度探测失败时退回仅看 rotation。
- 主流程 Step 2 同时 probe 源文件维度与 rotation,按显示方向选滤镜。
- is_portrait_rotation 保留并补充注释(仅覆盖 iOS 风格 side data)。

测试补充:
- TestIsPortraitVideo:iOS 风格(1920x1080+rotation90)、物理竖屏
  (1080x1920 无 rotation)、横屏、rotation=180 不互换、探测失败回退。
- 端到端新增 test_physical_portrait_hevc_no_rotation:真实 ffmpeg 造
  1080x1920 无 side data 的 HEVC,断言转码后仍为 1080x1920 h264。
- 任务级新增 test_physical_portrait_no_rotation_still_transcodes:
  断言物理竖屏也走转码、改写 storage_key、使用按宽缩放滤镜。
- FONT 改为多路径候选查找,缺失时 drawtext 降级(仅红条标记),
  测试不再硬依赖 Debian 字体路径。

注:转码临时文件的 finally 清理逻辑在 #1449 中已存在(ingest.py
转码 try 块 finally 段 unlink _tc_tmp),本次审查该条为窗口外误判。
xiaoxia added 1 commit 2026-08-30 20:53:25 +08:00
refactor(ingest): 转码魔法数字提取为常量(1080/1920/900s)
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 0s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 3m4s
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 50s
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 4m5s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 4m23s
AI Code Review / AI Code Review (pull_request) Failing after 4m24s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 29s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 5m56s
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 9m22s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 11m29s
CI/CD Pipeline / Validate - Code Quality (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Build Production API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Production (pull_request) Has been cancelled
CI/CD Pipeline / Production Browser E2E (pull_request) Has been cancelled
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / CI Gate (pull_request) Has been cancelled
0eba41332c
响应 review 建议:TRANSCODE_TARGET_EDGE=1080、TRANSCODE_MAX_LONG_EDGE
=1920、TRANSCODE_TIMEOUT_SECONDS=900,vf 字符串改 rf-string 引用常量,
渲染结果不变。
Author
Owner

回复 AI Code Review 最新一轮意见:

阻塞级(临时文件 delete=False)—— 误判,代码本就正确

ingest.py 中转码临时文件的创建为(develop 基线 #1449 即如此):

_tc_tmp_file = tempfile.NamedTemporaryFile(delete=False, suffix="_h264.mp4")
_tc_tmp = Path(_tc_tmp_file.name)
_tc_tmp_file.close()  # 关闭 fd,ffmpeg 自行打开写入

delete=False 已显式传入,close 不会删除文件;且转码 try 块末尾有 finally 清理(成功/失败/异常都会 unlink)。端到端测试(真实 ffmpeg 写入该路径)与任务级测试均通过,可佐证转码不会 100% 失败。

建议 1(stderr 字节流)—— 误判

转码 subprocess.run 调用显式传了 text=Truestdout=subprocess.DEVNULL, stderr=subprocess.PIPE, text=True, timeout=...),_proc.stderr 已是 str,_proc.stderr[-300:] 切片无需 decode。

建议 2(魔法数字)—— 已采纳

最新提交已将 1080 / 1920 / 900 提取为 TRANSCODE_TARGET_EDGE / TRANSCODE_MAX_LONG_EDGE / TRANSCODE_TIMEOUT_SECONDS 常量,vf 表达式改 rf-string 引用,渲染结果不变。

上一轮的阻塞 1(物理竖屏 rotation=0 误判横屏)已在 b03d19cf 修复:新增 is_portrait_video(stored_w, stored_h, rotation) 按显示方向判定,补了纯逻辑单测、真实 ffmpeg 物理竖屏端到端、任务级用例。

回复 AI Code Review 最新一轮意见: **阻塞级(临时文件 delete=False)—— 误判,代码本就正确** `ingest.py` 中转码临时文件的创建为(develop 基线 #1449 即如此): ```python _tc_tmp_file = tempfile.NamedTemporaryFile(delete=False, suffix="_h264.mp4") _tc_tmp = Path(_tc_tmp_file.name) _tc_tmp_file.close() # 关闭 fd,ffmpeg 自行打开写入 ``` `delete=False` 已显式传入,close 不会删除文件;且转码 try 块末尾有 `finally` 清理(成功/失败/异常都会 `unlink`)。端到端测试(真实 ffmpeg 写入该路径)与任务级测试均通过,可佐证转码不会 100% 失败。 **建议 1(stderr 字节流)—— 误判** 转码 `subprocess.run` 调用显式传了 `text=True`(`stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, text=True, timeout=...`),`_proc.stderr` 已是 str,`_proc.stderr[-300:]` 切片无需 decode。 **建议 2(魔法数字)—— 已采纳** 最新提交已将 1080 / 1920 / 900 提取为 `TRANSCODE_TARGET_EDGE` / `TRANSCODE_MAX_LONG_EDGE` / `TRANSCODE_TIMEOUT_SECONDS` 常量,vf 表达式改 rf-string 引用,渲染结果不变。 上一轮的阻塞 1(物理竖屏 rotation=0 误判横屏)已在 b03d19cf 修复:新增 `is_portrait_video(stored_w, stored_h, rotation)` 按显示方向判定,补了纯逻辑单测、真实 ffmpeg 物理竖屏端到端、任务级用例。
Collaborator

【阻塞级判定】

  • 是否存在阻塞级问题:是
  • 阻塞级问题数量:2 个

📊 审查概览

  • 整体评价:需修改
  • 建议级问题数量:1 个

🔴 阻塞级问题(必须修复)

  1. [apps/worker/worker_app/tasks/ingest.py: 467-511] 临时文件资源泄露

    • 问题类型:数据损坏风险 / 资源泄露
    • 问题描述:代码使用 tempfile.NamedTemporaryFile(delete=False) 创建临时文件 _tc_tmp,并在关闭文件句柄后交由 ffmpeg 写入。然而,在随后的逻辑中(无论 validate_transcode_output 校验成功还是失败),均未显式调用 os.remove()_tc_tmp.unlink() 删除该临时文件。这将导致每次 HEVC 转码任务都在 Worker 节点磁盘上残留一个临时视频文件,长期运行会导致磁盘空间耗尽,引发服务不可用。
    • 修改建议:在 try...finally 块中或校验/上传逻辑结束后,务必添加清理代码删除 _tc_tmp。例如:
      try:
          # ... transcoding logic ...
      finally:
          if _tc_tmp and _tc_tmp.exists():
              _tc_tmp.unlink()
      
  2. [apps/worker/worker_app/tasks/ingest.py: 156-157] 转码缩放逻辑与校验规则不一致

    • 问题类型:逻辑bug
    • 问题描述:代码定义了 TRANSCODE_MAX_LONG_EDGE = 1920 并在 validate_transcode_output 中强制校验产物长边不得超过 1920。然而,构建的滤镜 _LANDSCAPE_VF_PORTRAIT_VF 仅对短边(高或宽)进行了 "if > 1080 then 1080" 的限制,未限制长边。
      例如:对于 4000x1000 的横屏视频,ih=1000 不大于 1080,滤镜不会缩放,输出仍为 4000x1000。此时 validate_transcode_output 检测到长边 4000 > 1920 判定失败,导致转码被丢弃,最终降级使用原始 HEVC 文件。这会导致浏览器无法播放(黑屏),违背了自动转码的初衷。
    • 修改建议:修改滤镜逻辑,确保缩放后的最大边长不超过 1920。可以使用 scale='min(1920,iw):-2' 等逻辑,或者使用 force_original_aspect_ratio=decrease 结合尺寸限制来确保产物符合校验规则。

💡 改进建议(不阻塞合并)

  1. [apps/worker/worker_app/tasks/ingest.py: 162-223] 性能优化:合并 ffprobe 调用
    • 具体内容:probe_rotationprobe_dimensions 分别调用了一次 subprocess.run 执行 ffprobe。对于大文件或高并发场景,这会增加 I/O 开销。建议将两个探测合并为一次 ffprobe 调用,同时获取 stream=width,heightside_data=rotation,解析一次输出即可。

良好实践

  • 使用 subprocess.run 列表传参,有效避免了命令注入风险。
  • 异常处理较为完善,对 subprocess.TimeoutExpiredValueError 等进行了捕获并降级处理。
  • 添加了详细的中文注释解释了 ffmpeg 滤镜中逗号转义等细节,便于后续维护。
  • 增加了转码产物的严格校验(方向、维度、旋转信息),防止了错误文件覆盖正确文件,保障了数据一致性。

格式检查通过 | 逻辑审查需修改 | ⚠️ 建议关注性能


🤖 由 AI 代码审查机器人自动生成 | 2026-08-30 12:57:48 | 模型:

### 【阻塞级判定】 - 是否存在阻塞级问题:是 - 阻塞级问题数量:2 个 ### 📊 审查概览 - 整体评价:需修改 - 建议级问题数量:1 个 ### 🔴 阻塞级问题(必须修复) 1. **[apps/worker/worker_app/tasks/ingest.py: 467-511] 临时文件资源泄露** - 问题类型:数据损坏风险 / 资源泄露 - 问题描述:代码使用 `tempfile.NamedTemporaryFile(delete=False)` 创建临时文件 `_tc_tmp`,并在关闭文件句柄后交由 `ffmpeg` 写入。然而,在随后的逻辑中(无论 `validate_transcode_output` 校验成功还是失败),均未显式调用 `os.remove()` 或 `_tc_tmp.unlink()` 删除该临时文件。这将导致每次 HEVC 转码任务都在 Worker 节点磁盘上残留一个临时视频文件,长期运行会导致磁盘空间耗尽,引发服务不可用。 - 修改建议:在 `try...finally` 块中或校验/上传逻辑结束后,务必添加清理代码删除 `_tc_tmp`。例如: ```python try: # ... transcoding logic ... finally: if _tc_tmp and _tc_tmp.exists(): _tc_tmp.unlink() ``` 2. **[apps/worker/worker_app/tasks/ingest.py: 156-157] 转码缩放逻辑与校验规则不一致** - 问题类型:逻辑bug - 问题描述:代码定义了 `TRANSCODE_MAX_LONG_EDGE = 1920` 并在 `validate_transcode_output` 中强制校验产物长边不得超过 1920。然而,构建的滤镜 `_LANDSCAPE_VF` 和 `_PORTRAIT_VF` 仅对短边(高或宽)进行了 "if > 1080 then 1080" 的限制,未限制长边。 例如:对于 4000x1000 的横屏视频,`ih=1000` 不大于 1080,滤镜不会缩放,输出仍为 4000x1000。此时 `validate_transcode_output` 检测到长边 4000 > 1920 判定失败,导致转码被丢弃,最终降级使用原始 HEVC 文件。这会导致浏览器无法播放(黑屏),违背了自动转码的初衷。 - 修改建议:修改滤镜逻辑,确保缩放后的最大边长不超过 1920。可以使用 `scale='min(1920,iw):-2'` 等逻辑,或者使用 `force_original_aspect_ratio=decrease` 结合尺寸限制来确保产物符合校验规则。 ### 💡 改进建议(不阻塞合并) 1. **[apps/worker/worker_app/tasks/ingest.py: 162-223] 性能优化:合并 ffprobe 调用** - 具体内容:`probe_rotation` 和 `probe_dimensions` 分别调用了一次 `subprocess.run` 执行 `ffprobe`。对于大文件或高并发场景,这会增加 I/O 开销。建议将两个探测合并为一次 `ffprobe` 调用,同时获取 `stream=width,height` 和 `side_data=rotation`,解析一次输出即可。 ### ✅ 良好实践 - 使用 `subprocess.run` 列表传参,有效避免了命令注入风险。 - 异常处理较为完善,对 `subprocess.TimeoutExpired`、`ValueError` 等进行了捕获并降级处理。 - 添加了详细的中文注释解释了 `ffmpeg` 滤镜中逗号转义等细节,便于后续维护。 - 增加了转码产物的严格校验(方向、维度、旋转信息),防止了错误文件覆盖正确文件,保障了数据一致性。 --- ✅ 格式检查通过 | ❌ 逻辑审查需修改 | ⚠️ 建议关注性能 --- <sub>🤖 由 AI 代码审查机器人自动生成 | 2026-08-30 12:57:48 | 模型: </sub> <!-- AI_CODE_REVIEW_AUTO_COMMENT -->
xiaoxia added 1 commit 2026-08-30 21:09:45 +08:00
fix(ingest): 转码滤镜改按长边 1920 封顶(修复超宽屏误降级)+ 合并 ffprobe
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 1s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 2m6s
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 1m46s
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 28s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 4m28s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 5m55s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 6m18s
AI Code Review / AI Code Review (pull_request) Successful in 9m20s
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 13m37s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 15m54s
CI/CD Pipeline / Validate - Code Quality (pull_request) Successful in 21m45s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 29m4s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Successful in 20m45s
CI/CD Pipeline / CI Gate (pull_request) Successful in 1m7s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 4m14s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 4m46s
e68801eb59
Code Review 阻塞问题修复 + 建议采纳:

1. 缩放规则与校验规则对齐(真 bug):
   旧滤镜仅按短边 1080 触发缩放(横屏看 ih、竖屏看 iw),对超宽屏
   (如 4000x1000)短边不超 1080 完全不缩放,产物长边 4000 超过
   validate_transcode_output 的 1920 上限,转码被判定失败而降级用
   原始 HEVC——浏览器仍无法播放。改为统一滤镜:
     scale=w=if(gte(iw\,ih)\,min(1920\,iw)\,-2):
           h=if(gt(ih\,iw)\,min(1920\,ih)\,-2),format=yuv420p
   横屏限宽、竖屏限高、短边 -2 自适应,min() 保证小视频不放大;
   build_transcode_vf 不再需要 is_portrait 参数(方向无关)。
   ffmpeg 实测:1920x1080→1920x1080、4K竖屏→1080x1920、
   4000x1000→1920x480、1080x2400→864x1920、640x360 不放大,均无 side data。

2. 合并 ffprobe(性能建议):新增 probe_video_info(path) 一次
   -show_streams -of json 同时解析 width/height 与 Display Matrix
   rotation(用全量 JSON 输出,规避 show_entries 嵌套 section 名在
   ffmpeg 4.x/7.x 不一致的问题);主流程两次探测合并为一次。
   probe_rotation/probe_dimensions 保留供测试与 validate 复用。

3. 临时文件清理:转码 try 块 finally 中 _tc_tmp.unlink() 在 #1449
   即已存在(develop 基线代码),本轮 review 该条为窗口外误判,
   已在 PR 评论说明。

测试:
- TestBuildTranscodeVF 重写为统一滤镜断言(逗号转义、无 transpose、
  横竖双分支 min(1920)、yuv420p 后缀)。
- 新增 TestProbeVideoInfo(真实 ffprobe 320x240 无 rotation;
  不存在文件返回三元 None)。
- 端到端新增 test_ultra_wide_long_edge_capped(4000x1000→1920x480
  且 validate 通过);_transcode_like_production 改用 probe_video_info
  + 无参 build_transcode_vf,与生产完全一致。
- 任务级竖屏用例滤镜特征断言更新为 min(1920)。
auto-approve-bot merged commit 15bc55f0ff into develop 2026-08-30 21:56:02 +08:00
auto-approve-bot deleted branch fix/ingest-hevc-portrait-rotation 2026-08-30 21:56:03 +08:00

🗑️ 预览环境已清理

PR #1559 已关闭或合并,对应的预览环境已被清理。

如有需要,可以重新打开 PR 来重新生成预览环境。

🗑️ **预览环境已清理** PR #1559 已关闭或合并,对应的预览环境已被清理。 > 如有需要,可以重新打开 PR 来重新生成预览环境。
Sign in to join this conversation.