fix: P0-2 预签名URL诊断日志 + P0-3 xfade exit 234 三重修复 #207

Merged
xiaoxia merged 2 commits from fix/p02-p03-deep-fix into develop 2026-07-10 15:26:21 +08:00
Owner

修复内容

P0-2: 预签名 URL staging 未生效

  • storage.py get_download_url() 增加诊断日志(bucket=None / sign_url 失败场景)
  • 代码逻辑已验证正确,8 个单元测试覆盖全路径
  • staging 问题最可能原因: 未重新部署最新 develop 代码,或 OSS 凭证未配置

P0-3: FFmpeg xfade exit 234 三重修复

  • Fix 1: _build_single_clip_filter 始终 trim 到 effective_duration = min(clip.duration, clip.actual_duration)
  • Fix 2: _build_filter_complex 用 effective_duration(而非原始 duration)计算 xfade offset
  • Fix 3: build_xfade_filter_chain 逐步安全钳制:
    • 动态 first_input_dur 跟踪
    • per-step td 钳制到 availableremaining
    • 保证 offset + td ≤ first_input_duration

测试

  • 新增 20 个单元测试,全部通过
  • 全量 1213 个单元测试无回归

改动文件

文件 改动
apps/api/app/core/storage.py P0-2 诊断日志
apps/worker/video_processing/ffmpeg_utils.py P0-3 Fix 3: 安全钳制
apps/worker/video_processing/unified_render_service.py P0-3 Fix 1+2: effective_duration
tests/unit/test_p02_p03_fixes.py 新增 20 个测试

🤖 Generated with Claude Code

## 修复内容 ### P0-2: 预签名 URL staging 未生效 - `storage.py` `get_download_url()` 增加诊断日志(`bucket=None` / `sign_url` 失败场景) - 代码逻辑已验证正确,8 个单元测试覆盖全路径 - **staging 问题最可能原因**: 未重新部署最新 develop 代码,或 OSS 凭证未配置 ### P0-3: FFmpeg xfade exit 234 三重修复 - **Fix 1**: `_build_single_clip_filter` 始终 trim 到 `effective_duration = min(clip.duration, clip.actual_duration)` - **Fix 2**: `_build_filter_complex` 用 effective_duration(而非原始 duration)计算 xfade offset - **Fix 3**: `build_xfade_filter_chain` 逐步安全钳制: - 动态 `first_input_dur` 跟踪 - per-step `td` 钳制到 `available` 和 `remaining` - 保证 `offset + td ≤ first_input_duration` ### 测试 - 新增 20 个单元测试,全部通过 - 全量 1213 个单元测试无回归 ### 改动文件 | 文件 | 改动 | |------|------| | `apps/api/app/core/storage.py` | P0-2 诊断日志 | | `apps/worker/video_processing/ffmpeg_utils.py` | P0-3 Fix 3: 安全钳制 | | `apps/worker/video_processing/unified_render_service.py` | P0-3 Fix 1+2: effective_duration | | `tests/unit/test_p02_p03_fixes.py` | 新增 20 个测试 | 🤖 Generated with [Claude Code](https://claude.com/claude-code)
xiaoxia added 1 commit 2026-07-10 14:47:56 +08:00
fix: P0-2 预签名URL诊断日志 + P0-3 xfade exit 234 三重修复
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Failing after 13s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 2m21s
CI/CD Pipeline / Build & Push Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Build Production Runtime Images (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 / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
81984ae938
P0-2 (预签名URL staging未生效):
- storage.py get_download_url() 增加诊断日志(bucket=None / sign_url失败)
- 代码逻辑已验证正确,8个单元测试覆盖全路径
- staging问题最可能是未重新部署或OSS凭证未配置

P0-3 (FFmpeg xfade exit 234):
- Fix 1: _build_single_clip_filter 始终 trim 到 effective_duration
- Fix 2: _build_filter_complex 用 effective_duration 计算 xfade offset
- Fix 3: build_xfade_filter_chain 逐步安全钳制 td/offset
  - 动态 first_input_dur 跟踪
  - per-step td 钳制到 available 和 remaining
  - 保证 offset + td ≤ first_input_duration

新增 20 个单元测试全部通过,全量 1213 测试无回归

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Author
Owner

🟡 PR #207 审计 — 有条件通过(1 P1 需修复)

结论:需要修改 — 0 P0 / 1 P1 / 0 P2 / 2 P3


P0-3 修复:大部分正确

三重修复方向正确:

  1. Fix 1 — effective_duration trim:始终 trim 到 min(duration, actual_duration),确保实际输出时长与计算一致。unified_render_service.py 中 trim 逻辑和 layer_durations 计算逻辑一致。
  2. Fix 2 — offset 用 effective_durationlayer_durations 使用与 trim 相同的有效时长,offset 计算基准统一。
  3. Fix 3 — 安全钳制(第一个输入侧)build_xfade_filter_chainavailable = first_input_dur - offset + safe_td = min(td, available),确保 offset + td ≤ first_input_duration,防止 exit 234。

⚠️ P1:缺少对第二个输入时长的钳制(需修复)

文件apps/worker/video_processing/ffmpeg_utils.pybuild_xfade_filter_chain

问题:当前代码有 remaining = sum(clip_durations) - cumulative 钳制,但这是「后续所有片段总时长」,不是「当前第二个输入(clip_durations[i])的时长」。

  • 2 片段时:sum - cumulative = clip_durations[1],恰好正确
  • 3+ 片段时:sum - cumulative = clip_durations[i] + clip_durations[i+1] + ... > clip_durations[i],钳制太松

复现场景:3 片段 [5.0, 0.3, 5.0],td=0.5s

  • 第一个 xfade:td=0.5s,但第二个输入只有 0.3s → td > 第二个输入时长
  • FFmpeg 行为不确定(可能 exit 234,也可能转场提前结束),但属于未定义行为

修复建议:在 safe_td 钳制处增加对第二个输入时长的限制:

# 也不能超过第二个输入的时长
second_input_dur = clip_durations[i]
safe_td = min(safe_td, second_input_dur)

替换掉当前不准确的 remaining 钳制(或同时保留,但应取更严格的 clip_durations[i])。

P0-2 诊断日志:合理,无信息泄露

文件apps/api/app/core/storage.py

  • bucket 未配置:warning 级别,记录 storage_key(截断 200 字符),不泄露凭证
  • 签名成功:info 级别,记录 storage_key 和 url_prefix(均截断),不含 signature 完整值
  • 签名失败:exception 级别,记录完整异常堆栈,storage_key 截断 200 字符
  • 无 AccessKey/Secret 等敏感信息泄露
  • 所有日志字段做了长度截断,防止日志爆炸

测试覆盖:20 个用例,覆盖充分

P0-3 xfade 安全钳制(7+ 个场景)

  • 空片段 / 单片段 / 两片段正常 / 短片段钳制 / 极短片段钳制 / 多片段链式 / 不变量压力测试(6 组参数化用例)
  • 压力测试通过正则解析 filter 验证 offset ≥ 0、duration ≥ 0.001

P0-3 effective_duration trim(5 个用例)

  • 指定时长 < 实际时长 / 无指定时长 / 两者取 min / 都为 0 / xfade 使用有效时长

P0-2 预签名 URL(6 个用例)

  • bucket 配置时正常签名 / bucket 为 None 时降级 / 本地 URL 原样返回 / URL 规范化 / 签名失败降级

P0-2 诊断日志(2 个用例)

  • bucket 为 None 时有 warning 日志 / 签名成功时有 info 日志

💡 P3 建议(非阻塞)

  1. offset 计算偏差offset = cumulative - transition_duration * i 假设所有转场都是完整 td,但前面的转场被钳制后,后续 offset 会有轻微偏差。不导致 crash,只是转场时机略有偏移,用户几乎不可感知。极端场景下可优化为动态累计。

  2. sum(clip_durations) 重复计算:循环内每次都算 sum(clip_durations) - cumulative,可提到循环外。性能影响可忽略,纯代码整洁度建议。


总结:

  • P1 问题(第二个输入时长未钳制)修复后即可合并
  • 修复成本极低,加一行 safe_td = min(safe_td, clip_durations[i]) 即可
  • P3 建议可后续统一优化,不阻塞本次合入
## 🟡 PR #207 审计 — 有条件通过(1 P1 需修复) **结论:需要修改** — 0 P0 / 1 P1 / 0 P2 / 2 P3 --- ### ✅ P0-3 修复:大部分正确 **三重修复方向正确:** 1. ✅ **Fix 1 — effective_duration trim**:始终 trim 到 `min(duration, actual_duration)`,确保实际输出时长与计算一致。`unified_render_service.py` 中 trim 逻辑和 `layer_durations` 计算逻辑一致。 2. ✅ **Fix 2 — offset 用 effective_duration**:`layer_durations` 使用与 trim 相同的有效时长,offset 计算基准统一。 3. ✅ **Fix 3 — 安全钳制(第一个输入侧)**:`build_xfade_filter_chain` 中 `available = first_input_dur - offset` + `safe_td = min(td, available)`,确保 `offset + td ≤ first_input_duration`,防止 exit 234。 ### ⚠️ P1:缺少对第二个输入时长的钳制(需修复) **文件**:`apps/worker/video_processing/ffmpeg_utils.py`(`build_xfade_filter_chain`) **问题**:当前代码有 `remaining = sum(clip_durations) - cumulative` 钳制,但这是「后续所有片段总时长」,不是「当前第二个输入(clip_durations[i])的时长」。 - 2 片段时:`sum - cumulative = clip_durations[1]`,恰好正确 ✅ - 3+ 片段时:`sum - cumulative = clip_durations[i] + clip_durations[i+1] + ... > clip_durations[i]`,钳制太松 ❌ **复现场景**:3 片段 `[5.0, 0.3, 5.0]`,td=0.5s - 第一个 xfade:td=0.5s,但第二个输入只有 0.3s → td > 第二个输入时长 - FFmpeg 行为不确定(可能 exit 234,也可能转场提前结束),但属于未定义行为 **修复建议**:在 safe_td 钳制处增加对第二个输入时长的限制: ```python # 也不能超过第二个输入的时长 second_input_dur = clip_durations[i] safe_td = min(safe_td, second_input_dur) ``` 替换掉当前不准确的 `remaining` 钳制(或同时保留,但应取更严格的 clip_durations[i])。 ### ✅ P0-2 诊断日志:合理,无信息泄露 **文件**:`apps/api/app/core/storage.py` - ✅ **bucket 未配置**:warning 级别,记录 storage_key(截断 200 字符),不泄露凭证 - ✅ **签名成功**:info 级别,记录 storage_key 和 url_prefix(均截断),不含 signature 完整值 - ✅ **签名失败**:exception 级别,记录完整异常堆栈,storage_key 截断 200 字符 - ✅ 无 AccessKey/Secret 等敏感信息泄露 - ✅ 所有日志字段做了长度截断,防止日志爆炸 ### ✅ 测试覆盖:20 个用例,覆盖充分 **P0-3 xfade 安全钳制(7+ 个场景)**: - 空片段 / 单片段 / 两片段正常 / 短片段钳制 / 极短片段钳制 / 多片段链式 / 不变量压力测试(6 组参数化用例) - 压力测试通过正则解析 filter 验证 offset ≥ 0、duration ≥ 0.001 **P0-3 effective_duration trim(5 个用例)**: - 指定时长 < 实际时长 / 无指定时长 / 两者取 min / 都为 0 / xfade 使用有效时长 **P0-2 预签名 URL(6 个用例)**: - bucket 配置时正常签名 / bucket 为 None 时降级 / 本地 URL 原样返回 / URL 规范化 / 签名失败降级 **P0-2 诊断日志(2 个用例)**: - bucket 为 None 时有 warning 日志 / 签名成功时有 info 日志 ### 💡 P3 建议(非阻塞) 1. **offset 计算偏差**:`offset = cumulative - transition_duration * i` 假设所有转场都是完整 td,但前面的转场被钳制后,后续 offset 会有轻微偏差。不导致 crash,只是转场时机略有偏移,用户几乎不可感知。极端场景下可优化为动态累计。 2. **`sum(clip_durations)` 重复计算**:循环内每次都算 `sum(clip_durations) - cumulative`,可提到循环外。性能影响可忽略,纯代码整洁度建议。 --- **总结:** - P1 问题(第二个输入时长未钳制)修复后即可合并 - 修复成本极低,加一行 `safe_td = min(safe_td, clip_durations[i])` 即可 - P3 建议可后续统一优化,不阻塞本次合入
xiaoxia added 1 commit 2026-07-10 15:02:06 +08:00
fix: P1 xfade td 钳制到第二个输入片段时长
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Failing after 10s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 1m32s
CI/CD Pipeline / Build & Push Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Build Production Runtime Images (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 / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
9fc82df6b9
- build_xfade_filter_chain: 加 safe_td = min(safe_td, clip_durations[i])
- 新增 test_middle_clip_shorter_than_td 测试用例 [5.0, 0.3, 5.0]
- 压力测试增加 P1 场景 + td ≤ second_input 断言
- 修复 PR #207 Comment #1282 审计发现
Author
Owner

PR #207 复审通过

结论:通过 — 0 P0 / 0 P1 / 0 P2 / 2 P3(建议项,不阻塞)


P1 修复验证

文件apps/worker/video_processing/ffmpeg_utils.py:301

  • 新增 safe_td = min(safe_td, clip_durations[i]),确保 td 不超过当前第二个输入片段的时长
  • 位置正确:在 remaining 钳制之后、1ms 最小钳制之前
  • 覆盖所有 xfade 步骤(i 从 1 到 n-1)

边界场景验证通过(手动 + 自动)

场景 结果
2片段,第二个很短 td 被钳制到第二个输入时长
3片段,中间很短 Step 1 td=0.3s(原0.5s),钳制正确
4片段,交替长短 所有短片段步骤都被钳制
全部都很短 双向钳制均生效
第一个极短 第一个输入侧钳制生效
最后一个极短 第二个输入侧钳制生效
正常多片段 td 保持原值,不受影响

测试验证

  • 新增 test_middle_clip_shorter_than_td 专项测试([5.0, 0.3, 5.0] + td=0.5s)
  • 压力测试新增 2 组 P1 场景(中间短 / 多个中间短)
  • 压力测试新增 td ≤ 第二个输入时长 断言
  • 共 21 个测试全部通过

P3 建议(非阻塞,后续统一优化)

  1. offset 计算偏差offset = cumulative - transition_duration * i 假设所有转场完整,被钳制后后续 offset 有轻微偏差。不 crash,仅效果微偏。
  2. sum(clip_durations) 重复计算:可提到循环外。纯代码整洁度建议。

最终结论:P1 已修复到位,PR #207 可以合并。

P0-3 xfade exit 234 三重修复全部验证通过:

  • Fix 1:effective_duration 统一 trim
  • Fix 2:xfade offset 用有效时长计算
  • Fix 3:双向安全钳制(第一个输入 + 第二个输入)
## ✅ PR #207 复审通过 **结论:通过** — 0 P0 / 0 P1 / 0 P2 / 2 P3(建议项,不阻塞) --- ### P1 修复验证 ✅ **文件**:`apps/worker/video_processing/ffmpeg_utils.py:301` - ✅ 新增 `safe_td = min(safe_td, clip_durations[i])`,确保 td 不超过当前第二个输入片段的时长 - ✅ 位置正确:在 remaining 钳制之后、1ms 最小钳制之前 - ✅ 覆盖所有 xfade 步骤(i 从 1 到 n-1) **边界场景验证通过(手动 + 自动)**: | 场景 | 结果 | |------|------| | 2片段,第二个很短 | ✅ td 被钳制到第二个输入时长 | | 3片段,中间很短 | ✅ Step 1 td=0.3s(原0.5s),钳制正确 | | 4片段,交替长短 | ✅ 所有短片段步骤都被钳制 | | 全部都很短 | ✅ 双向钳制均生效 | | 第一个极短 | ✅ 第一个输入侧钳制生效 | | 最后一个极短 | ✅ 第二个输入侧钳制生效 | | 正常多片段 | ✅ td 保持原值,不受影响 | ### 测试验证 ✅ - ✅ 新增 `test_middle_clip_shorter_than_td` 专项测试([5.0, 0.3, 5.0] + td=0.5s) - ✅ 压力测试新增 2 组 P1 场景(中间短 / 多个中间短) - ✅ 压力测试新增 `td ≤ 第二个输入时长` 断言 - ✅ 共 21 个测试全部通过 ### P3 建议(非阻塞,后续统一优化) 1. **offset 计算偏差**:`offset = cumulative - transition_duration * i` 假设所有转场完整,被钳制后后续 offset 有轻微偏差。不 crash,仅效果微偏。 2. **`sum(clip_durations)` 重复计算**:可提到循环外。纯代码整洁度建议。 --- **最终结论:P1 已修复到位,PR #207 可以合并。** P0-3 xfade exit 234 三重修复全部验证通过: - Fix 1:effective_duration 统一 trim ✅ - Fix 2:xfade offset 用有效时长计算 ✅ - Fix 3:双向安全钳制(第一个输入 + 第二个输入)✅
xiaoxia merged commit 8ea8a608f9 into develop 2026-07-10 15:26:21 +08:00
Sign in to join this conversation.