fix(cover): Worker本地ffmpeg抽帧 + E2源素材封面标题叠加 #1462
Reference in New Issue
Block a user
Delete Branch "fix/cover-ffmpeg-title-overlay"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
问题
修复
1. Worker 统一走本地 ffmpeg 抽帧(apps/worker/worker_app/tasks/generation.py)
_render_video返回值从(output_path, render_duration)改为(output_path, render_duration, cover_candidates)gen_task.cover_url,完整列表写入gen_task.metadata["cover_candidates"]2. 标题文字叠加(packages/shared/title_overlay.py + thumbnail_generator.py)
packages/shared/title_overlay.py:Pillow 绘制白色文字 + 黑色描边/阴影,支持 CJK 字体和自动换行,API/Worker 共用thumbnail_generator.extract_and_upload_cover_frames的title_text参数真正生效render_adapter.py从已渲染视频抽帧时传空title_text(标题已通过 ASS 烧录进画面,避免二次叠加重影)3. 封面 API E2 兜底叠加标题(generation_cover.py)
plan.config.title.text读取标题并叠加到帧图_persist_cover_frame增加title_text参数,下载帧后用 Pillow 叠加再转存 OSS4. API 基础镜像依赖(infra/docker/api-base.Dockerfile + requirements-base.txt)
测试
test_title_overlay.py(5 个:空标题跳过、绘制、位置、自动换行、显式换行)_persist_cover_frametest_1294_preview_voice_injection.py适配 3 元组返回值验收
gen_task.cover_url非空,封面图带标题问题: 1. 之前 Worker 用 MediaKit 网络抽帧写 cover_url,因状态字符串 bug(#1461 已修) 一直失败;且在 API 请求内同步抽帧易超时。 2. 封面从源素材抽取时无标题(源素材未烧录标题)。 修复: 1. Worker 渲染完成后直接复用 RenderAdapter 已用本地 ffmpeg 抽出的 cover_candidates: - _render_video 返回值改为 (output_path, render_duration, cover_candidates) - 第一帧写入 gen_task.cover_url,完整列表写入 metadata.cover_candidates - 删除步骤 4.5 的 MediaKit 抽帧代码(不依赖网络,不阻塞) 2. 标题叠加: - 新增 packages/shared/title_overlay.py,Pillow 绘制白色文字+黑色描边/阴影, 支持 CJK 字体和自动换行(API/Worker 共用) - thumbnail_generator.extract_and_upload_cover_frames 的 title_text 参数真正生效 - render_adapter 从已渲染视频抽帧时传空 title_text(标题已 ASS 烧录,避免重影) - 封面 API E2 从源素材抽帧后,从 plan.config.title.text 读取标题并叠加 3. API 基础镜像增加 Pillow 和 fonts-noto-cjk(E2 标题叠加依赖) 测试:新增 test_title_overlay.py(5)、E2 标题透传测试,更新 1294 解包; 相关 51 passed。🚀 预览环境已部署
【阻塞级判定】
📊 审查概览
🔴 阻塞级问题(必须修复)
无
💡 改进建议(不阻塞合并)
[packages/shared/title_overlay.py: 98] 资源管理优化
img = Image.open(image_path).convert("RGB")建议使用with Image.open(image_path) as img:上下文管理器,并在with块内进行convert和操作。虽然函数结束时会触发 GC,但显式关闭文件句柄是更佳实践,尤其在高并发场景下能避免文件句柄耗尽风险。[packages/shared/title_overlay.py: 28] 性能优化建议
find_title_font函数每次调用都会遍历文件系统检查字体路径是否存在。由于字体路径在容器生命周期内是固定的,建议使用lru_cache或全局变量缓存第一次找到的字体对象,避免重复的 IO 开销。✅ 良好实践
generation_cover.py中对plan.config的多层嵌套取值做了非常完善的空值和类型检查(or {},isinstance判断),有效避免了 NPE 风险。_persist_cover_frame和generate_video中,对标题叠加和封面持久化等非关键路径操作使用了try-except包裹,失败时记录 Warning 但不中断主流程,保证了系统的鲁棒性。generation.py中更新metadata时使用了dict(_cover_model.metadata or {})创建副本,避免了直接修改引用可能带来的副作用,符合 SQLAlchemy JSON 字段的最佳实践。✅ 格式检查通过 | ✅ 逻辑审查通过 | ✅ 性能良好
🤖 由 AI 代码审查机器人自动生成 | 2026-08-23 02:34:06 | 模型:
CI全绿,自动审批通过。
CI全绿,自动审批通过。
🗑️ 预览环境已清理
PR #1462 已关闭或合并,对应的预览环境已被清理。