Video To Short Processor

这是 video-to-short MVP 的 Modal 视频处理器。

它是主 Cloudflare 应用背后的“视频加工服务”。它不负责用户、任务列表、计费、页面 UI 或业务数据库,只负责把一个源视频处理成一个 MVP 版竖屏短视频,并把结果写回 Cloudflare R2。

当前 MVP 能力

  1. 通过 sourceKey 从 R2 读取源视频,或者通过 sourceDownloadUrl 从临时 HTTP URL 下载源视频。
  2. 使用 FFmpeg 截取一段开头片段和一段中间片段。
  3. 拼接片段并转成竖屏 MP4。
  4. 上传最终视频到 R2。
  5. 可选接收 jobIdstepKeystepName/name,并向 Job Hub 上报当前 step 进度。
  6. 如果传入 webhookUrl,会回调进度和完成状态。

当前默认输出位置:

generated/{jobId}/final.mp4

这里的 {jobId} 是处理器运行 ID。HTTP endpoint 会优先使用请求里的 jobId,也兼容 renderTaskId;如果都没有传,则生成 modal-<uuid>。如果需要稳定输出路径,建议显式传入 outputKey。只有请求同时传入 jobIdstepKeystepName/name 时,才会更新 Job Hub。

示例:

generated/test-001/final.mp4

目录与入口

所有命令建议从 modal-processor 项目根目录执行:

cd ~/projects/modal-processor
source .venv/bin/activate

当前 app 入口文件:

apps/video_to_short/modal_app.py

必需的 Modal Secret

需要在 Modal 后台创建一个 secret,名称必须是:

video-to-short-r2

里面需要包含这些环境变量:

R2_ACCOUNT_ID=...
R2_ACCESS_KEY_ID=...
R2_SECRET_ACCESS_KEY=...
R2_BUCKET=omninote

说明:

R2_ACCESS_KEY_IDR2_SECRET_ACCESS_KEY 是 Cloudflare R2 的 S3-compatible access key。不要写进代码,不要提交到 Git,也不要放到 README 或聊天记录里。

安装依赖

pip install -r requirements.txt

本地触发测试

这个命令是在本地终端触发 Modal 远端任务。真正的视频处理发生在 Modal 远端容器里,不是在本机。

modal run apps/video_to_short/modal_app.py \
  --source-key youtube_video/youtube-video-1776311042955.mp4 \
  --job-id test-001

显式指定输出位置:

modal run apps/video_to_short/modal_app.py \
  --source-key youtube_video/youtube-video-1776311042955.mp4 \
  --job-id test-001 \
  --output-key generated/test-001/final.mp4

如果源视频不是 R2 key,而是一个临时下载 URL,可以这样运行:

modal run apps/video_to_short/modal_app.py \
  --source-download-url "https://example.com/source.mp4" \
  --job-id test-001

成功时输出里会包含类似内容:

'status': 'completed'
'outputKey': 'generated/test-001/final.mp4'

生成的视频保存在 R2,不在本地机器上。

部署

执行部署后,Modal 会创建一个稳定的 HTTP endpoint,供 Cloudflare 主站线上调用。

modal deploy apps/video_to_short/modal_app.py

部署成功后,Modal 会输出 process_video 函数对应的 .modal.run URL,形式类似:

https://<workspace>--video-to-short-processor-process-video.modal.run

这个 URL 就是主站里的 PROCESSOR_ENDPOINT

外部 HTTP 调用

部署后,可以通过 POST JSON 调用这个 endpoint:

当前线上示例 endpoint:https://zhengkinson--video-to-short-processor-process-video.modal.run

curl -X POST "https://<your-modal-endpoint>.modal.run" \
  -H "Content-Type: application/json" \
  -d '{
    "sourceKey": "youtube_video/youtube-video-1776311042955.mp4",
    "outputKey": "generated/test-002/final.mp4",
    "platform": "tiktok",
    "quality": "balanced"
  }'

预期会立刻返回:

{"accepted": true, "jobId": "modal-..."}

注意:这个返回只表示 Modal 已接收任务。返回里的 jobId 是当前 Modal 处理任务 ID:如果请求传了 jobId,会返回传入值;如果没传,则由 Modal endpoint 生成。视频处理会在后台继续执行。处理完成后,到 R2 里检查:

generated/test-002/final.mp4

jobId 是可选字段,表示当前 Modal 处理任务 ID;如果不传,会由 Modal endpoint 自动生成。请求同时传入 jobIdstepKeystepName/name 时,处理器会通过 Job Hub step 接口上报进度;缺少任一字段时,不进行 Job Hub 更新。job_idstep_keystep_name 兼容下划线写法。

Webhook 格式

如果请求里传了 webhookUrl,处理器会回调进度和最终结果。旧字段 callbackUrl 会被兼容识别为 webhookUrl

请求示例:

{
  "jobId": "job-123",
  "stepKey": "render-short",
  "stepName": "生成短视频",
  "jobProgressStart": 60,
  "jobProgressEnd": 100,
  "sourceDownloadUrl": "https://temporary-source-url.example.com/video.mp4",
  "outputKey": "generated/job-123/final.mp4",
  "platform": "tiktok",
  "quality": "balanced",
  "webhookUrl": "https://your-cloudflare-app.com/api/processor/webhook"
}

进度 webhook 示例:

{
  "jobId": "job-123",
  "stepKey": "render-short",
  "stepName": "生成短视频",
  "outputKey": "generated/job-123/final.mp4",
  "platform": "tiktok",
  "quality": "balanced",
  "status": "processing",
  "stage": "rendering",
  "progress": 45,
  "message": "Rendering MVP vertical short."
}

完成 webhook 示例:

{
  "jobId": "job-123",
  "stepKey": "render-short",
  "stepName": "生成短视频",
  "outputKey": "generated/parent-job-123/final.mp4",
  "platform": "tiktok",
  "quality": "balanced",
  "status": "completed",
  "stage": "completed",
  "progress": 100,
  "message": "MVP video rendered successfully.",
  "renderInfo": {
    "sourceDurationSeconds": 222.4936,
    "clipWindows": [
      { "start": 0, "duration": 15 },
      { "start": 103.7468, "duration": 15 }
    ],
    "width": 720,
    "height": 1280
  }
}

webhook 里的 jobId 是 Modal 处理器运行 ID;如果请求传入了 stepKeystepName/name,webhook 会同时带回这两个字段。处理失败且 Job Hub 字段完整时,处理器会上报 status=failed、失败原因 messagefailJob=true

质量参数

fast      720x1280,编码更快
balanced  720x1280,默认
high      1080x1920,编码更慢,质量更高

常见错误

Secret 'video-to-short-r2' not found

Modal 里没有创建 video-to-short-r2,或者 secret 名字写错了。

Missing required environment variable: R2_ACCOUNT_ID

Modal secret 存在,但里面缺少必要字段。

NoSuchKey

传入的 sourceKey 在当前 R2 bucket 里不存在。

AccessDenied

R2 S3 access key 不正确,或者没有当前 bucket 的读写权限。

Web endpoint Functions require FastAPI

镜像缺少 FastAPI。执行 pip install -r requirements.txt 后重新部署。当前 requirements.txt 已包含 fastapi[standard]

后续计划

  • 给公开 Modal endpoint 增加请求 token 校验。
  • 用字幕和 LLM 高光分析替代当前的简单前段/中段截取。
  • 增加自动字幕。
  • 增加更智能的 9:16 裁剪,例如人脸居中。