fix(worker): ingest增加素材有效性校验,坏文件标记为ERROR #488

Merged
auto-approve-bot merged 1 commits from fix/ingest-validation into develop 2026-07-17 22:20:57 +08:00
Owner

问题

ingest流程中ffprobe/Pillow失败时仍然创建status=READY的asset,导致27字节文本文件也能伪装成有效视频进入素材库。一键生成自动选到坏素材直接渲染失败。

修复

  1. extract_media_metadata 返回 (metadata, success) 二元组:能区分解析成功和失败
  2. 新增 _is_valid_media 有效性校验
    • 视频:ffprobe成功 + size>=1KB + duration>0
    • 音频:ffprobe成功 + size>=100B + duration>0
    • 图片:Pillow验证通过 + size>=100B + width>0 + height>0
  3. 校验失败处理:asset标记为ERROR状态,ingest job标记为FAILED,不影响其他任务
  4. 补充音频元数据提取:之前只有视频和图片走ffprobe,音频现在也提取

影响范围

只改了 apps/worker/worker_app/tasks/ingest.py,不影响已有的正常素材(READY状态的不受影响)。

测试

  • 新增 tests/unit/test_ingest_validation.py,14个测试覆盖:
    • _is_valid_media 9个用例(视频/音频/图片各场景)
    • extract_media_metadata返回值签名 3个用例
    • 完整ingest流程 2个用例(无效文件→ERROR,有效文件→READY)
  • 原有 3个 ingest 测试全部通过
  • black / isort / ruff 全过
## 问题 ingest流程中ffprobe/Pillow失败时仍然创建status=READY的asset,导致27字节文本文件也能伪装成有效视频进入素材库。一键生成自动选到坏素材直接渲染失败。 ## 修复 1. **extract_media_metadata 返回 (metadata, success) 二元组**:能区分解析成功和失败 2. **新增 _is_valid_media 有效性校验**: - 视频:ffprobe成功 + size>=1KB + duration>0 - 音频:ffprobe成功 + size>=100B + duration>0 - 图片:Pillow验证通过 + size>=100B + width>0 + height>0 3. **校验失败处理**:asset标记为ERROR状态,ingest job标记为FAILED,不影响其他任务 4. **补充音频元数据提取**:之前只有视频和图片走ffprobe,音频现在也提取 ## 影响范围 只改了 `apps/worker/worker_app/tasks/ingest.py`,不影响已有的正常素材(READY状态的不受影响)。 ## 测试 - 新增 `tests/unit/test_ingest_validation.py`,14个测试覆盖: - _is_valid_media 9个用例(视频/音频/图片各场景) - extract_media_metadata返回值签名 3个用例 - 完整ingest流程 2个用例(无效文件→ERROR,有效文件→READY) - 原有 3个 ingest 测试全部通过 - black / isort / ruff 全过
xiaoxia added 1 commit 2026-07-17 22:15:56 +08:00
fix(worker): ingest增加素材有效性校验,坏文件标记为ERROR不再以READY入库
CI Build & Deploy Pipeline / Build Staging API Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Production API Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Production Web Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI Build & Deploy Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI Build & Deploy Pipeline / Deploy Production (pull_request) Has been skipped
CI Build & Deploy Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 15s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 28s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 50s
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Successful in 3m22s
Auto Approve CI PRs / Auto Approve on CI Green (pull_request) Successful in 3m41s
AI Code Review / AI Code Review (pull_request) Successful in 4m21s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m20s
Auto Merge CI PRs / Auto Merge on CI Green + Approved (pull_request) Successful in 5m0s
355e25b2aa
问题:ingest流程中ffprobe/Pillow失败时仍然创建status=READY的asset,
导致27字节文本文件也能伪装成有效视频进入素材库,
一键生成自动选到坏素材直接渲染失败。

修复:
- extract_media_metadata返回(metadata, success)二元组
- 新增_is_valid_media校验函数:
  * 视频:ffprobe成功 + size>=1KB + duration>0
  * 音频:ffprobe成功 + size>=100B + duration>0
  * 图片:Pillow验证通过 + size>=100B + width>0 + height>0
- 校验失败的asset标记为ERROR状态,ingest job标记为FAILED
- 新增14个单元测试:有效性判断+返回值签名+完整ingest流程
- 补充音频元数据提取(之前只有视频和图片)
auto-approve-bot approved these changes 2026-07-17 22:19:38 +08:00
auto-approve-bot left a comment
Collaborator

CI全绿,自动审批通过。

CI全绿,自动审批通过。
auto-approve-bot approved these changes 2026-07-17 22:19:38 +08:00
auto-approve-bot left a comment
Collaborator

CI全绿,自动审批通过。

CI全绿,自动审批通过。
Collaborator

代码审查结果 - PR #488

⚠️ 问题(2个需要修改)

  1. apps/worker/worker_app/tasks/ingest.py 第126-131行:图片元数据提取逻辑中缺少 size_bytes 字段的赋值。

    • 原因:在 extract_media_metadata 函数的 image 分支中,代码只提取了 width, height, format, mode,但没有提取文件大小。
    • 后果_is_valid_media 函数(第166行)会检查 size >= MIN_IMAGE_FILE_SIZE(100字节)。由于 metadata 中缺少 size_bytes,默认值为0,导致所有图片文件的校验结果均为 False,进而导致所有图片上传任务被标记为 ERROR 状态。
    • 修改建议:在 with Image.open(file_url) as img2: 块中添加文件大小获取逻辑,例如:
      import os
      metadata["size_bytes"] = os.path.getsize(file_url)
      
  2. apps/worker/worker_app/tasks/ingest.py 第93行:视频处理分支使用了未在当前作用域定义的变量 json_lib

    • 原因:视频处理分支(media_type == "video")中直接使用了 json_lib.loads(stdout),但该变量仅在音频处理分支(media_type == "audio",第118行)中通过 import json as json_lib 进行了局部导入。
    • 后果:如果 json_lib 未在文件头部全局定义,当处理视频文件时,代码将抛出 NameError,导致任务崩溃。虽然推测原代码可能在头部有定义,但当前 diff 显示的局部导入方式引入了作用域风险。
    • 修改建议:将 import json 移至文件头部全局区域,或者确保 json_lib 在两个分支中均可用,保持代码风格一致。

💡 建议(2个可选)

  1. apps/worker/worker_app/tasks/ingest.py 第96-111行 & 第118-142行:视频和音频的 ffprobe 调用逻辑存在大量重复代码。

    • 建议:建议将 ffprobe 的命令构建、执行和 JSON 解析逻辑抽取为一个私有辅助函数(如 _run_ffprobe_json),以减少代码重复,提高可维护性。
  2. apps/worker/worker_app/tasks/ingest.py 第239行error_reason 的日志级别建议调整。

    • 建议:当前使用 logger.warning。由于此处会导致任务失败并创建 ERROR 状态的 Asset,建议使用 logger.error 以便在监控系统中更醒目地捕获失败原因,或者保持 warning 但补充更多的上下文信息(如 storage_key)。

格式检查通过 | 逻辑审查需修改 | 性能无明显问题


🤖 由 AI 代码审查机器人自动生成 | 2026-07-17 22:20:18 | 模型:

## 代码审查结果 - PR #488 ### ⚠️ 问题(2个需要修改) 1. **apps/worker/worker_app/tasks/ingest.py 第126-131行**:图片元数据提取逻辑中缺少 `size_bytes` 字段的赋值。 - **原因**:在 `extract_media_metadata` 函数的 `image` 分支中,代码只提取了 `width`, `height`, `format`, `mode`,但没有提取文件大小。 - **后果**:`_is_valid_media` 函数(第166行)会检查 `size >= MIN_IMAGE_FILE_SIZE`(100字节)。由于 `metadata` 中缺少 `size_bytes`,默认值为0,导致所有图片文件的校验结果均为 `False`,进而导致所有图片上传任务被标记为 `ERROR` 状态。 - **修改建议**:在 `with Image.open(file_url) as img2:` 块中添加文件大小获取逻辑,例如: ```python import os metadata["size_bytes"] = os.path.getsize(file_url) ``` 2. **apps/worker/worker_app/tasks/ingest.py 第93行**:视频处理分支使用了未在当前作用域定义的变量 `json_lib`。 - **原因**:视频处理分支(`media_type == "video"`)中直接使用了 `json_lib.loads(stdout)`,但该变量仅在音频处理分支(`media_type == "audio"`,第118行)中通过 `import json as json_lib` 进行了局部导入。 - **后果**:如果 `json_lib` 未在文件头部全局定义,当处理视频文件时,代码将抛出 `NameError`,导致任务崩溃。虽然推测原代码可能在头部有定义,但当前 diff 显示的局部导入方式引入了作用域风险。 - **修改建议**:将 `import json` 移至文件头部全局区域,或者确保 `json_lib` 在两个分支中均可用,保持代码风格一致。 ### 💡 建议(2个可选) 1. **apps/worker/worker_app/tasks/ingest.py 第96-111行 & 第118-142行**:视频和音频的 `ffprobe` 调用逻辑存在大量重复代码。 - **建议**:建议将 `ffprobe` 的命令构建、执行和 JSON 解析逻辑抽取为一个私有辅助函数(如 `_run_ffprobe_json`),以减少代码重复,提高可维护性。 2. **apps/worker/worker_app/tasks/ingest.py 第239行**:`error_reason` 的日志级别建议调整。 - **建议**:当前使用 `logger.warning`。由于此处会导致任务失败并创建 ERROR 状态的 Asset,建议使用 `logger.error` 以便在监控系统中更醒目地捕获失败原因,或者保持 warning 但补充更多的上下文信息(如 `storage_key`)。 --- ✅ 格式检查通过 | ❌ 逻辑审查需修改 | ✅ 性能无明显问题 --- <sub>🤖 由 AI 代码审查机器人自动生成 | 2026-07-17 22:20:18 | 模型: </sub> <!-- AI_CODE_REVIEW_AUTO_COMMENT -->
auto-approve-bot merged commit f435a4d3a5 into develop 2026-07-17 22:20:57 +08:00
auto-approve-bot deleted branch fix/ingest-validation 2026-07-17 22:20:57 +08:00
Sign in to join this conversation.