Fastwhisper Processor

这是一个基于 faster-whisper 的 Modal 转录处理器。

第一版只做语音转文字,不做翻译。它从 Cloudflare R2 下载音频或视频文件,转录成结构化 JSON,再把结果写回 R2。

当前 MVP 能力

  1. 通过 sourceKey 从 R2 读取源音视频文件。
  2. 支持 FFmpeg 可解码的常见格式,例如 mp4、m4a、wav、mp3、mov、webm、aac、ogg、flac。
  3. 使用 faster-whisper 执行原语言转录。
  4. 上传转录 JSON 到 R2。
  5. 可选接收 jobId 作为当前 Modal 任务 ID;不传则生成 modal-<uuid>
  6. 可选接收 jobIdstepKeystepName/name,并向 Job Hub 上报当前 step 进度。
  7. 可选传入 webhookUrl,回调进度和完成状态。

默认输出位置:

transcripts/{jobId}/transcript.json

第一版只输出 JSON 文件,不额外输出 txt、srt 或 vtt。

目录与入口

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

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

当前 app 入口文件:

apps/fastwhisper/modal_app.py

必需的 Modal Secret

复用现有 R2 secret,名称必须是:

video-to-short-r2

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

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

安装依赖

pip install -r requirements.txt

本地触发测试

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

modal run apps/fastwhisper/modal_app.py \
  --source-key youtube_video/youtube-video-1779260797732.m4a \
  --job-id transcribe-test-001

显式指定输出位置:

modal run apps/fastwhisper/modal_app.py \
  --source-key uploads/source.mp4 \
  --job-id transcribe-test-001 \
  --output-key transcripts/transcribe-test-001/transcript.json

指定源语言和模型大小:

modal run apps/fastwhisper/modal_app.py \
  --source-key uploads/source.mp4 \
  --source-language zh \
  --model-size small

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

'status': 'completed'
'outputKey': 'transcripts/transcribe-test-001/transcript.json'

部署

modal deploy apps/fastwhisper/modal_app.py

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

https://<workspace>--fastwhisper-processor-transcribe-media.modal.run

外部 HTTP 调用

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

curl -X POST "https://zhengkinson--fastwhisper-processor-transcribe-media.modal.run" \
  -H "Content-Type: application/json" \
  -d '{
    "sourceKey": "uploads/source.mp4",
    "outputKey": "transcripts/source/transcript.json",
    "sourceLanguage": "zh",
    "modelSize": "small"
  }'

预期会立刻返回:

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

注意:这个返回只表示 Modal 已接收任务。返回里的 jobId 是当前 Modal 转录任务 ID:如果请求传了 jobId,会返回传入值;如果没传,则由 Modal endpoint 生成。

jobId 是可选字段,表示当前 Modal 任务 ID;如果请求同时传入 jobIdstepKeystepName/name,处理器会通过 Job Hub step 接口上报进度。缺少任一字段时,不进行 Job Hub 更新。job_idstep_keystep_name 兼容下划线写法。处理失败且 Job Hub 字段完整时,处理器会上报 status=failed、失败原因 messagefailJob=true

Webhook 格式

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

请求示例:

{
  "sourceKey": "uploads/source.mp4",
  "outputKey": "transcripts/source/transcript.json",
  "jobId": "job-123",
  "stepKey": "transcribe",
  "stepName": "转写音视频",
  "jobProgressStart": 20,
  "jobProgressEnd": 70,
  "webhookUrl": "https://your-app.com/api/processor/webhook",
  "sourceLanguage": "zh",
  "modelSize": "small"
}

进度 webhook 示例:

{
  "jobId": "job-123",
  "stepKey": "transcribe",
  "stepName": "转写音视频",
  "sourceKey": "uploads/source.mp4",
  "outputKey": "transcripts/source/transcript.json",
  "modelSize": "small",
  "sourceLanguage": "zh",
  "status": "processing",
  "stage": "transcribing",
  "progress": 40,
  "message": "Transcribing source media with faster-whisper."
}

完成 webhook 示例:

{
  "jobId": "job-123",
  "stepKey": "transcribe",
  "stepName": "转写音视频",
  "sourceKey": "uploads/source.mp4",
  "outputKey": "transcripts/source/transcript.json",
  "modelSize": "small",
  "sourceLanguage": "zh",
  "status": "completed",
  "stage": "completed",
  "progress": 100,
  "message": "Audio transcription completed successfully.",
  "language": "zh",
  "languageProbability": 0.98,
  "durationSeconds": 123.45
}

R2 输出 JSON 格式

R2 中的 outputKey 会保存完整转录结果:

segments 是经过句子级后处理的片段,不是 faster-whisper 原始时间块。处理器会优先按 . ? ! 。?! 断句,并在原始时间块内按字符位置近似估算句子边界时间。

{
  "jobId": "job-123",
  "stepKey": "transcribe",
  "stepName": "转写音视频",
  "sourceKey": "uploads/source.mp4",
  "outputKey": "transcripts/source/transcript.json",
  "modelSize": "small",
  "sourceLanguage": "zh",
  "status": "completed",
  "language": "zh",
  "languageProbability": 0.98,
  "durationSeconds": 123.45,
  "text": "完整转录文本...",
  "segments": [
    {
      "id": 0,
      "index": 0,
      "segmentKey": "seg_0123456789abcdef0123456789abcdef",
      "start": 0.0,
      "end": 4.2,
      "startMs": 0,
      "endMs": 4200,
      "text": "第一段文本"
    }
  ],
  "model": {
    "name": "faster-whisper",
    "size": "small"
  }
}

参数说明

sourceKey

必填。R2 上的源音视频对象路径。

outputKey

选填。R2 上的转录 JSON 输出路径。默认 transcripts/{jobId}/transcript.json

sourceLanguage

选填。源文件语言,例如 zhen。不传时由 Whisper 自动识别。

modelSize

选填。默认 small。可按 Modal 容器资源和准确率需求调整为 basesmallmedium 等。

常见错误

Missing required field: sourceKey

请求里没有传 R2 源文件路径。

NoSuchKey

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

Missing required environment variable: R2_ACCOUNT_ID

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

AccessDenied

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

Web endpoint Functions require FastAPI

镜像缺少 FastAPI。执行 pip install -r requirements.txt 后重新部署。