Skip to content

工程剪辑与合成(Synthesis)

本页聚焦于 VideoSynthesis.vue 的 6 步合成流程,提供完整时序图与接口字段字典,覆盖素材上传/注册、时间线构建与工程创建、提交剪辑、任务轮询、预签名下载、保存分镜与下载。

6步流程与时序图

mermaid
sequenceDiagram
  participant SB as StoryBoard.vue
  participant VS as VideoSynthesis.vue
  participant ICE as ICE API
  participant OSS as Aliyun OSS
  participant API as Backend API

  SB->>VS: startSynthesis(type)
  VS->>VS: sanitizeUrl & 过滤空thumb
  alt 采用OSS Key
    VS->>API: fetchRegisterMediaInfo(mediaType=video,inputURL=oss://bucket/key)
    API-->>VS: MediaId(v)
    VS->>API: fetchRegisterMediaInfo(mediaType=audio,inputURL=oss://bucket/key)
    API-->>VS: MediaId(a)
  else URL/Blob音频
    VS->>API: fetchGetDirectUploadUrl(bucket,key,expires)
    API-->>VS: uploadUrl
    VS->>OSS: PUT audio Blob
    VS->>API: fetchGetPresignUrl(bucket,key)
    API-->>VS: presign_url
  end
  VS->>API: fetchUploadMediaByUrl(data[])
  API-->>VS: videoMediaId / audioMediaId
  alt 单条合成(type=0)
    VS->>ICE: fetchCreateEditingProject(title,coverURL,timeline)
    ICE-->>VS: ProjectId
  else 合并合成(type=1)
    VS->>VS: convertStoryBoardDataToTimeline
    VS->>ICE: fetchCreateEditingProject(...)
    ICE-->>VS: ProjectId
  end
  VS->>ICE: fetchSubmitMediaProducing(projectId, outputMediaConfig)
  ICE-->>VS: JobId
  loop 3-5秒轮询
    VS->>ICE: fetchGetMediaProducingJob(jobId)
    ICE-->>VS: Status/MediaURL
  end
  VS->>API: fetchGetPresignUrl(bucket,key)
  API-->>VS: presign_url
  VS->>API: fetchIceSave(item)
  API-->>VS: returnCode=0
  VS->>VS: downloadFile(iceVideoUrl)

接口字段字典

fetchRegisterMediaInfo

  • 描述:注册媒体信息到 ICE,优先使用 oss://bucket/key 格式
  • 请求字段:
    • mediaTypevideo | audio
    • inputURLoss://bucket/keyhttp(s)(前端统一规范化)
    • title:可选,媒体标题
  • 响应字段:
    • MediaId:字符串,ICE媒体ID
    • 兼容返回(已存在):existed: 1

fetchUploadMediaByUrl

  • 描述:将 URL 指向的素材注册/上传到 ICE
  • 请求字段(包裹在 data 数组内的每条素材):
    • descriptionlinenameeffect:文案相关
    • videoUrl:视频URL(可能追加 &extension=extension.mp4
    • audioUrl:音频URL
    • thumbUrl:缩略图URL
  • 响应字段(数组):
    • videoJobIdvideoMediaId
    • audioJobIdaudioMediaId

fetchCreateEditingProject

  • 描述:创建剪辑工程
  • 请求字段:
    • title:工程标题
    • coverURL:封面图URL
    • timeline:包含 VideoTracksAudioTracksSubtitleTracks 的时间线对象
  • 响应字段:
    • ProjectId:工程ID
    • Status:状态码(整数)

fetchSubmitMediaProducing

  • 描述:提交工程剪辑并输出到特定OSS文件
  • 请求字段:
    • projectId:工程ID
    • outputMediaTargetoss-object
    • outputMediaConfig.MediaURLhttps://bucket.oss-region.aliyuncs.com/path/file.mp4
  • 响应字段:
    • JobIdMediaIdProjectIdRequestId

fetchGetMediaProducingJob

  • 描述:查询剪辑任务状态
  • 请求字段:
    • jobId:任务ID
  • 响应字段:MediaProducingJob 对象
    • StatusSuccess | Failed | Running
    • MediaURL:最终OSS地址
    • 其他:DurationTimelineCreateTimeCompleteTime

fetchGetPresignUrl

  • 描述:获取OSS文件的临时下载地址(预签名)
  • 请求字段:
    • bucket:OSS 桶
    • key:对象键名
  • 响应字段:
    • presign_url:可下载的临时URL

fetchIceSave

  • 描述:保存单帧分镜结果(工程与输出绑定)
  • 请求字段(示例):
    • titleprojectIdjobIdiceVideoUrlbucketkey
  • 响应字段:
    • responseHeader.returnCode:0 表示成功

fetchGetDirectUploadUrl

  • 描述:获取直传地址,用于音频 Blob 上传
  • 请求字段:bucketkeyexpires(秒),region(可缺省)
  • 响应字段:uploadUrlkey

fetchListOSSFiles

  • 描述:列出OSS文件/目录
  • 请求字段:bucketprefixdelimitermaxKeys
  • 响应字段:对象列表

示例

上传/注册示例(请求)

json
{
  "data": [{
    "title": "未来城市空中交通",
    "description": "清晨的未来科技城市...",
    "line": "欢迎来到2150年的新都市...",
    "videoUrl": "https://img.stooland.com/...14908.mp4",
    "audioUrl": "https://gsv.ai-lab.top/outputs/61dc5b...e.wav",
    "thumbUrl": "https://img.stooland.com/...thumb.webp"
  }]
}

提交剪辑(请求)

json
{
  "projectId": "75573520d5294d87b6310f76bb32cd4d",
  "outputMediaTarget": "oss-object",
  "outputMediaConfig": {
    "MediaURL": "https://aigc-sz-linkt.oss-cn-shenzhen.aliyuncs.com/ice-output/ICE自动剪辑2025-05-11-12-00.mp4"
  }
}

查询任务(响应)

json
{
  "MediaProducingJob": {
    "Status": "Success",
    "MediaURL": "https://aigc-sz-linkt.../ICE自动剪辑2025-05-11-12-00.mp4"
  }
}

注意事项

  • fetchGetPresignUrl 后端默认 regioncn-shenzhen,前端可不传。
  • ICE 媒体已存在时后端兼容返回 MediaIdexisted: 1,前端直接使用即可。
  • 轮询间隔建议 3–5 秒,避免过度请求;失败时应停止并提示。

FFmpeg 合成(MCP + PyBridge)

方案与选型

  • 方案概述
    • 前端 FFmpegComposeDialog.vue 构建 timeline_jsonoptions,调用后端 MCP 工具 compose_timeline_ffmpeg
    • MCP 控制器将请求转发到 PyBridge(PYBRIDGE_URL),由 ffmpeg-python 适配器执行合成。
    • 合成进度通过 /mcp/jobs/:id/log 尾部日志解析;完成后 /mcp/jobs/:id 返回 statusmedia_url
    • media_url 为 OSS 域名,前端自动走 fetchGetPresignUrl 生成预签名,保证私有桶可播放。
  • 技术选型
    • 合成引擎:ffmpeg-python(便于计划生成与日志结构化输出)。
    • 字幕模式:统一使用整片 SRT(overlay_mode='subtitles'),禁用逐行 drawtext,提高稳定性与一致性。
    • 视频输出:强制 CFR 与 yuv420p 像素格式,提升兼容性并减少闪烁。
    • URL 签名:后端统一提供 OSS 预签名接口;前端检测后自动替换。

时间线与参数定义

  • Timeline 对象(前端)
json
{
  "storyboard_id": 123,
  "videos": ["https://.../v1.mp4", "https://.../v2.mp4"],
  "segments": [
    { "videoUrl": "https://.../v1.mp4", "audioUrl": "https://.../a1.mp3", "subtitleText": "第一段字幕" },
    { "videoUrl": "https://.../v2.mp4", "audioUrl": "https://.../a2.mp3", "subtitleText": "第二段字幕" }
  ],
  "srtText": "1\\n00:00:00,000 --> 00:00:02,000\\n第一段字幕\\n...",
  "subtitleSegments": [
    { "text": "第一段字幕", "start": 0.00, "duration": 2.00 },
    { "text": "第二段字幕", "start": 2.00, "duration": 2.00 }
  ]
}
  • Options(前端)
json
{
  "impl": "ffmpeg-python",
  "resolution": "auto",
  "fps": 30,
  "keep_ar": true,
  "video_match_audio_speed": true,
  "frame_interpolate": true,
  "overlay_mode": "subtitles",
  "use_audio_subtitles": true,
  "subtitle_chunk_seconds": 2,
  "vsync_mode": "cfr",
  "pix_fmt": "yuv420p",
  "only_subtitles_plan": false
}
  • 优先级与兜底
    • 若存在 subtitleSegments,直接使用;
    • 若仅有 srtText,前端解析为 subtitleSegments
    • only_subtitles_plan 或存在字幕时,强制 overlay_mode='subtitles'

后端接口定义

  • 触发合成(MCP 工具)
    • 路径:POST /mcp/tools/call
    • 请求:
json
{ "tool": "compose_timeline_ffmpeg", "args": { "agent_id": 1, "agent_app_id": 1, "member_id": 1, "storyboard_id": 123, "timeline_json": { }, "options": { } } }
  • 响应:
json
{ "accepted": true, "tool": "compose_timeline_ffmpeg", "job_id": "mcp_20251215121855_6343" }
  • 任务状态
    • 路径:GET /mcp/jobs/:id
    • 响应(示例):
json
{ "job_id": "mcp_...", "status": "success", "media_url": "https://aigc-sz-linkt.oss-cn-shenzhen.aliyuncs.com/ai/workflows/videos/20251215/mcp_....mp4" }
  • 任务日志
    • 路径:GET /mcp/jobs/:id/log
    • 响应:
json
{ "id": "mcp_...", "log": "stage:drawtext:start\\n...\\nsubprocess ffmpeg finished successfully" }
  • 预签名地址
    • 路径:POST /aigc/admin/ali/ice/getPresignUrl
    • 请求:{ "bucket": "aigc-sz-linkt", "key": "ai/workflows/videos/20251215/mcp_....mp4" }
    • 响应:{ "presign_url": "https://...&x-oss-signature=..." }

前端方法定义(FFmpegComposeDialog.vue)

  • 关键方法
    • start():构建 payload 并触发合成;必要时将 srtText 解析为 subtitleSegments
    • poll():每 2 秒轮询一次状态与日志;解析进度;完成后停止。
    • tryPresignMediaUrl():当 media_url 为 OSS 域名时,自动调用预签名接口替换为可播放地址。
    • generateSubtitlesVolc():通过 MCP generate_subtitle_volc 生成 subtitle_segmentssrt_text,统一切换到 SRT 模式。
  • 轮询停止条件
    • 后端返回 status === 'success' || 'failed'
    • 日志尾部包含完成标志:
      • adapter finished successfully
      • subprocess ffmpeg finished successfully
      • ffmpeg finished successfully
      • 或阶段行包含 drawtext:plan_written
    • 已获得可播放的 mediaUrl(视为成功)

目前进展

  • 已统一字幕模式为 SRT,禁用逐行 drawtext;合成更稳定。
  • 已增加轮询兜底:根据日志完成标志或 mediaUrl 出现停止轮询。
  • 已自动预签名 OSS 域名的 media_url,播放器可直接播放。
  • 为避免闪烁:
    • 默认开启插帧与变速对齐;
    • 强制输出 CFR 与 yuv420p 像素格式。

后期插帧(FILM)

方案与约束

  • 模型:Google Research FILM(TF Hub https://tfhub.dev/google/film/1
  • 环境:GPU 仅启用;CPU 环境禁用并返回错误提示
  • 输入:合成完成的视频 media_url(OSS 预签名或公网 URL)
  • 输出:提升帧率的视频(保持原时长),保留原音轨

接口与调用

  • 工具(MCP)
    • 名称:film_interpolate
    • 请求:
      json
      { "tool": "film_interpolate", "args": { "input_url": "https://...mp4", "target_fps": 60 } }
    • 响应:
      json
      { "accepted": true, "tool": "film_interpolate", "job_id": "interp_1734250000_1234" }
  • 后端(PyBridge)
    • 端点:POST /postprocess/interpolate
    • 请求:
      json
      { "input_url": "https://...mp4", "target_fps": 60, "model_handle": "https://tfhub.dev/google/film/1" }
    • 响应:
      json
      { "job_id": "interp_1734250000_1234", "accepted": true }
    • 查询:GET /jobs/{job_id}GET /jobs/{job_id}/log(与合成一致)

前端使用(FFmpegComposeDialog.vue)

  • 在合成成功且出现 mediaUrl 后,点击按钮“插帧(FILM)”
  • 触发 film_interpolate 工具,后续按相同 jobId 流程轮询直到完成
  • 完成后自动上传到 OSS,/mcp/jobs/:id 返回新的 media_url(前端仍会预签名)

细节实现

  • 帧抽取:ffmpeg -vsync 0 输出为 PNG 序列,避免损失
  • 插帧策略:按 target_fps / orig_fps 计算 times_to_interpolate = ceil(log2(ratio)),递归插入中间帧
  • 组装输出:以提升后的实际帧率写回视频;映射原音轨 -map 1:a?libx264 + yuv420p 保持兼容
  • 日志标记:stage:film:start/extract_frames/pair_done:X/assemble_video/success

环境依赖

  • Python 包:tensorflow>=2.9tensorflow_hubffmpeg-python
  • 系统组件:ffmpeg(命令行),NVIDIA CUDA/cuDNN 或对应 GPU 加速环境
  • macOS(Metal)亦可使用 tensorflow-macos + tensorflow-metal 插件(性能依设备而定)

常见问题与建议

  • 若视频仍有轻微闪烁:
    • fps 设置为与源视频一致(如 25/30);
    • 保持 resolution='auto',减少重采样。
  • 若后端延迟返回 status
    • 前端已按日志与 mediaUrl 增加兜底;仍异常时请检查 PyBridge 日志。