HeyGen 异步视频任务全指南:状态轮询、超时控制与下载实现(基于 OpenMontage avatar-video Skill)
HeyGen 异步视频任务全指南状态轮询、超时控制与下载实现基于 OpenMontage avatar-video Skill【免费下载链接】OpenMontageWorlds first open-source, agentic video production system. 12 production pipelines, 100 tools, 700 agent skill and production-knowledge files. Turn your AI coding assistant into a full video production studio.项目地址: https://gitcode.com/GitHub_Trending/op/OpenMontageHeyGen 的视频生成是典型的异步云任务提交生成请求后立即拿到video_id视频在数分钟至更长时间内于服务端渲染完成客户端需要主动查询状态、等待completed再取得下载地址。本文档.agents/skills/avatar-video/references/video-status.md是 OpenMontage 仓库中avatar-videoSkill 的状态与下载基础参考它与 video-generation.md提交任务、webhooks.md事件回调共同构成完整的异步闭环。阅读本文后你将掌握 HeyGen v2 视频状态 API 的字段语义、curl/TypeScript/Python 三种查询写法、带进度回调和指数退避的轮询实现、带重试的断点下载以及先保存 video_id、后回来查状态的可恢复长任务模式。为什么必须轮询HeyGen 的视频是异步生成的向POST /v2/video/generate提交 avatar/voice/script 配置后HeyGen 服务端并不会在 HTTP 响应里直接返回成片而是返回一个video_id请求体中则携带 avatar_id、voice_id、脚本、背景等逐场景配置见 video-generation.md。从提交到成片需要经过排队、语音合成、虚拟人渲染、混流等多个服务端阶段因此生成结果必须通过独立的查询接口异步获取。在 OpenMontage 的完整工作流中这一步处于 avatar-video Skill 默认流程的第 5 步GET /v2/avatars挑选 avatar 并记录avatar_idGET /v2/voices挑选匹配的 voice编写脚本与分场景结构POST /v2/video/generate提交任务轮询GET /v2/videos/{video_id}直到status为completed即本文主题下载成片继续后续剪辑。查询视频状态的方式优先使用 MCP 工具当 HeyGen MCP 服务器已接入 Agent 环境时Skill 推荐直接调用mcp__heygen__get_video并传入videoId参数一次调用即可返回status、video_url、thumbnail_url、duration、title、gif_url、captioned_video_url等元数据。与之对应的其他 MCP 能力包括mcp__heygen__list_videos列出账号下视频与mcp__heygen__delete_video删除视频详见 avatar-video/SKILL.md 的工具选择表。MCP 方式自动处理鉴权与请求格式化是首选路径直接 API 是等价的后备方案。curl 直接查询curl -X GET https://api.heygen.com/v2/videos/YOUR_VIDEO_ID \ -H X-Api-Key: $HEYGEN_API_KEY所有请求都必须携带X-Api-Key头。密钥来自HEYGEN_API_KEY环境变量——这与仓库工具层 tools/video/heygen_video.py 的鉴权方式一致该工具在get_status()中检测HEYGEN_API_KEY是否设置未设置时返回ToolStatus.UNAVAILABLE从而让 Agent 在缺失密钥时获得明确的安装指引而不是静默失败。TypeScript 查询函数interface VideoStatusResponse { error: null | string; data: { id: string; status: pending | processing | completed | failed; video_url?: string; thumbnail_url?: string; duration?: number; title?: string; created_at?: string; completed_at?: string; gif_url?: string; captioned_video_url?: string; subtitle_url?: string; folder_id?: string; output_language?: string; failure_code?: string; failure_message?: string; }; } async function getVideoStatus(videoId: string): PromiseVideoStatusResponse[data] { const response await fetch( https://api.heygen.com/v2/videos/${videoId}, { headers: { X-Api-Key: process.env.HEYGEN_API_KEY! } } ); const json: VideoStatusResponse await response.json(); if (json.error) { throw new Error(json.error); } return json.data; }Python 查询函数import requests import os def get_video_status(video_id: str) - dict: response requests.get( fhttps://api.heygen.com/v2/videos/{video_id}, headers{X-Api-Key: os.environ[HEYGEN_API_KEY]} ) data response.json() if data.get(error): raise Exception(data[error]) return data[data]一个值得注意的健壮性细节成功查询时顶层error为nulldata中才包含视频信息而视频本身失败时status为failed此时data里会出现failure_code与failure_message。因此错误处理要区分接口调用失败顶层error非空与视频生成失败status failed两层语义。状态机语义pending / processing / completed / failed状态含义处理动作pending视频已进入处理队列尚未开始渲染继续轮询等待processing视频正在生成中继续轮询等待completed视频渲染完成可以下载取出video_url下载failed视频生成失败读取failure_code/failure_message定位原因四个状态构成一个单向推进的流程pending → processing → completed / failed。轮询代码只需关注终态completed/failed对两个中间态一律继续等待即可。生成时长的预期管理与超时设置HeyGen 视频生成的典型耗时是5–15 分钟高峰时段或长脚本可能超过 20 分钟。影响耗时的因素如下表因素影响脚本长度脚本越长处理时间增长越显著分辨率1080p 比 720p 更耗时Avatar 复杂度部分 avatar 渲染更快队列负载高峰时段可能出现 15–20 分钟的等待多场景每个场景都会叠加处理时间实操建议单次同步等待的超时时间设为15–20 分钟即 900,000–1,200,000 ms作为安全边界语音内容超过 2 分钟的脚本按 15 分钟以上的耗时预期设计流程长视频优先考虑异步模式先保存video_id稍后再回来查询而不是让进程长时间挂起。仓库工具层的印证600 秒超时与自适应轮询间隔OpenMontage 仓库内部的 HeyGen 集成并非停留在文档层面。在 tools/video/_shared.py 的poll_heygen()中可以看到一个真实运行的轮询实现它围绕v1/workflows/executions/{execution_id}以 600 秒为 deadline、初始 5 秒为间隔循环查询把status completed视为成功、status in {failed, error}视为失败并抛出带服务端错误信息的异常超时则抛出TimeoutError。这段代码还体现了文档建议用指数退避拉长间隔的工程化落地每次轮询后interval min(interval * 1.2, 30.0)——间隔从 5 秒起步、每次放大 1.2 倍、封顶 30 秒。其调用方 tools/video/heygen_video.py 更把这一思想固化进元数据retry_policy RetryPolicy(max_retries2, backoff_seconds10.0, retryable_errors[rate_limit, timeout, server_error])即网络层重试 2 次、退避 10 秒。文档的 v2 头像视频接口与工具的 v1 工作流接口虽然路径不同但异步提交 轮询 退避 超时的骨架完全一致可互相印证最佳实践。响应格式详解成功完成completed的完整响应{ error: null, data: { id: abc123, status: completed, video_url: https://files.heygen.ai/video/abc123.mp4, thumbnail_url: https://files.heygen.ai/thumbnail/abc123.jpg, duration: 45.2, title: My Video, created_at: 2024-01-15T10:30:00Z, completed_at: 2024-01-15T10:38:00Z, gif_url: https://files.heygen.ai/gif/abc123.gif, captioned_video_url: null, subtitle_url: null, folder_id: null, output_language: en } }字段用途速查video_url为成片主文件thumbnail_url可用于预览/封面gif_url适合做轻量动图预览captioned_video_url与subtitle_url在启用字幕功能后返回关于自动字幕配置见 captions.mdduration单位为秒created_at/completed_at可用于统计实际渲染耗时output_language标识成片语言。生成失败failed的响应{ error: null, data: { id: abc123, status: failed, failure_code: script_too_long, failure_message: Script too long for selected avatar } }注意error仍为null——请求本身成功失败发生在业务侧。务必把failure_code/failure_message透传给用户它们是可操作的排障信息例如示例中的script_too_long提示需要缩短脚本或更换支持更长文本的 avatar。轮询实现三件套基础轮询TypeScriptasync function waitForVideo( videoId: string, maxWaitMs 600000, // 10 minutes pollIntervalMs 5000 // 5 seconds ): Promisestring { const startTime Date.now(); while (Date.now() - startTime maxWaitMs) { const status await getVideoStatus(videoId); switch (status.status) { case completed: return status.video_url!; case failed: throw new Error(status.failure_message || Video generation failed); case pending: case processing: await new Promise((resolve) setTimeout(resolve, pollIntervalMs)); break; } } throw new Error(Video generation timed out); }代码要点以Date.now() - startTime硬性约束总时长默认 10 分钟防止无限等待completed直接返回video_urlfailed抛出带失败信息的异常两个中间态用setTimeout睡满轮询间隔后进入下一轮。带进度回调的轮询TypeScripttype ProgressCallback (status: string, elapsed: number) void; async function waitForVideoWithProgress( videoId: string, onProgress?: ProgressCallback, maxWaitMs 600000, pollIntervalMs 5000 ): Promisestring { const startTime Date.now(); while (Date.now() - startTime maxWaitMs) { const elapsed Date.now() - startTime; const status await getVideoStatus(videoId); onProgress?.(status.status, elapsed); switch (status.status) { case completed: return status.video_url!; case failed: throw new Error(status.failure_message || Video generation failed); default: await new Promise((resolve) setTimeout(resolve, pollIntervalMs)); } } throw new Error(Video generation timed out); } // Usage const videoUrl await waitForVideoWithProgress( videoId, (status, elapsed) { console.log(Status: ${status}, Elapsed: ${Math.round(elapsed / 1000)}s); } );进度回调的价值在于长任务运行中用户/上层编排能看到已等待 N 秒、当前处于 X 状态而不是面对一个沉默的黑盒。这一思路在 OpenMontage 的 avatar-video Skill 中亦被明确强调见 video-generation.md 的最佳实践第 8 条实现带进度反馈的轮询。Python 轮询含进度回调import time from typing import Optional, Callable def wait_for_video( video_id: str, max_wait_seconds: int 600, poll_interval: int 5, on_progress: Optional[Callable[[str, int], None]] None ) - str: start_time time.time() while time.time() - start_time max_wait_seconds: elapsed int(time.time() - start_time) status_data get_video_status(video_id) status status_data[status] if on_progress: on_progress(status, elapsed) if status completed: return status_data[video_url] elif status failed: raise Exception(status_data.get(failure_message, Video generation failed)) time.sleep(poll_interval) raise Exception(Video generation timed out) # Usage def progress_callback(status: str, elapsed: int): print(fStatus: {status}, Elapsed: {elapsed}s) video_url wait_for_video(video_id, on_progressprogress_callback)下载视频completed 之后的最后一公里关键提醒状态显示completed后video_url对应的文件可能并非立即可访问服务端尚在最终落盘/分发因此下载必须使用带退避的重试逻辑。TypeScript 下载指数退避重试import fs from fs; import path from path; async function downloadVideoWithRetry( videoUrl: string, outputPath ./output/video.mp4, maxRetries 5, initialDelayMs 2000 ): Promisevoid { let lastError: Error | null null; for (let attempt 0; attempt maxRetries; attempt) { try { const response await fetch(videoUrl); if (!response.ok) { throw new Error(HTTP ${response.status}: ${response.statusText}); } const arrayBuffer await response.arrayBuffer(); fs.writeFileSync(path.resolve(outputPath), Buffer.from(arrayBuffer)); console.log(Video downloaded to ${outputPath}); return; } catch (error) { lastError error as Error; const delay initialDelayMs * Math.pow(2, attempt); // Exponential backoff console.log(Download attempt ${attempt 1} failed, retrying in ${delay}ms...); await new Promise((resolve) setTimeout(resolve, delay)); } } throw new Error(Failed to download after ${maxRetries} attempts: ${lastError?.message}); }退避策略initialDelayMs * 2^attempt会让重试间隔按 2s、4s、8s、16s……递增避免在文件尚不可达时对服务端形成请求风暴。Python 下载流式 指数退避重试import requests import time def download_video_with_retry( video_url: str, output_path: str, max_retries: int 5, initial_delay: float 2.0 ) - None: last_error None for attempt in range(max_retries): try: response requests.get(video_url, streamTrue, timeout60) response.raise_for_status() with open(output_path, wb) as f: for chunk in response.iter_content(chunk_size8192): f.write(chunk) print(fVideo downloaded to {output_path}) return except Exception as e: last_error e delay initial_delay * (2 ** attempt) # Exponential backoff print(fDownload attempt {attempt 1} failed, retrying in {delay}s...) time.sleep(delay) raise Exception(fFailed to download after {max_retries} attempts: {last_error})Python 版本额外使用streamTrue 8192 字节分块写盘避免大文件一次性载入内存。简易下载无重试适合快速脚本、失败时人工重试的场景async function downloadVideo(videoUrl: string, outputPath ./output/video.mp4) { const response await fetch(videoUrl); if (!response.ok) { throw new Error(Failed to download: ${response.status}); } const arrayBuffer await response.arrayBuffer(); fs.writeFileSync(path.resolve(outputPath), Buffer.from(arrayBuffer)); }完整工作流示例生成 → 轮询 → 下载async function generateAndDownloadVideo(config: VideoConfig): Promisestring { // 1. Generate video const generateResponse await fetch( https://api.heygen.com/v2/video/generate, { method: POST, headers: { X-Api-Key: process.env.HEYGEN_API_KEY!, Content-Type: application/json, }, body: JSON.stringify(config), } ); const { data: generateData } await generateResponse.json(); const videoId generateData.video_id; console.log(Video ID: ${videoId}); // 2. Poll for completion const videoUrl await waitForVideoWithProgress( videoId, (status, elapsed) { console.log([${Math.round(elapsed / 1000)}s] Status: ${status}); } ); // 3. Download const outputPath ./output/${videoId}.mp4; await downloadVideo(videoUrl, outputPath); return outputPath; }三步结构拿video_id→ 带进度轮询 → 下载到本地与仓库内generate_heygen_video的实现路径tools/video/_shared.py完全对应该函数同样先取execution_id再poll_heygen阻塞轮询至成功拿到video_url随后requests.get下载并写入output_path默认heygen_video_{execution_id}.mp4最后把 provider、prompt、aspect_ratio、format 等写进ToolResult.data供上层编排读取。可恢复的长任务保存 video_id稍后再查对于预计超过 10 分钟的长脚本与其让进程一直挂起不如提交后落盘状态、之后随时回来查询。这是长视频生产流程的重要模式。生成后保存状态interface PendingVideo { videoId: string; createdAt: string; script: string; avatarId: string; voiceId: string; } async function startVideoGeneration(config: VideoGenerateRequest): PromisePendingVideo { const videoId await generateVideo(config); const pending: PendingVideo { videoId, createdAt: new Date().toISOString(), script: config.video_inputs[0].voice.input_text!, avatarId: config.video_inputs[0].character.avatar_id!, voiceId: config.video_inputs[0].voice.voice_id!, }; // Save to file for later retrieval fs.writeFileSync(pending-video.json, JSON.stringify(pending, null, 2)); console.log(Video generation started. ID: ${videoId}); console.log(Check status later with: checkVideoStatus()); return pending; }保存内容刻意包含script、avatarId、voiceId等生成上下文这样即使进程重启也能完整重建这笔任务当初是什么的记录。事后查询并分流处理async function checkVideoStatus(): Promisevoid { if (!fs.existsSync(pending-video.json)) { console.log(No pending video found); return; } const pending: PendingVideo JSON.parse( fs.readFileSync(pending-video.json, utf-8) ); const elapsed Date.now() - new Date(pending.createdAt).getTime(); console.log(Checking video ${pending.videoId} (started ${Math.round(elapsed / 60000)} min ago)...); const status await getVideoStatus(pending.videoId); switch (status.status) { case completed: console.log(Video ready: ${status.video_url}); console.log(Duration: ${status.duration}s); // Clean up pending file fs.unlinkSync(pending-video.json); // Save result fs.writeFileSync(video-result.json, JSON.stringify({ ...pending, videoUrl: status.video_url, thumbnailUrl: status.thumbnail_url, duration: status.duration, title: status.title, createdAt: status.created_at, completedAt: status.completed_at, }, null, 2)); break; case failed: console.error(Video failed: ${status.failure_message}); fs.unlinkSync(pending-video.json); break; default: console.log(Status: ${status.status} - check again in a few minutes); } }模式要点completed时清掉 pending 文件、把结果归档到video-result.jsonfailed时同样清理 pending 并透出失败信息中间态则保持文件不动提示稍后再查。CLI 友好的双命令模式// generate-video.ts - Start generation and exit async function main() { const pending await startVideoGeneration(config); console.log(\nVideo ID saved. Run npx tsx check-status.ts to check progress.); process.exit(0); // Exit immediately, dont wait } // check-status.ts - Check and optionally wait async function main() { const args process.argv.slice(2); const shouldWait args.includes(--wait); if (shouldWait) { // Poll until complete (with 20 min timeout) const result await waitForVideo(pending.videoId, apiKey, onProgress, 1200000); console.log(Done: ${result.video_url}); } else { // Just check once and report await checkVideoStatus(); } }generate-video.ts提交后立即process.exit(0)退出check-status.ts默认只查一次加--wait时则以 20 分钟超时阻塞到完成。两个命令可以分别被调度器触发也可手动反复运行。替代方案Webhooks生产环境推荐轮询的本质是客户端主动反复询问。若不想维护长连接轮询可改用 WebhookHeyGen 在视频完成/失败等事件发生时主动向你的服务器推送通知。完整的接入说明见 webhooks.md这里给出两者取舍的核心对比维度WebhookPolling延迟即时推送取决于轮询间隔效率高push低重复请求复杂度需要公网可访问的端点实现更简单可靠性需要自行补重试保证最终能查到成本API 调用更少API 调用更多与视频状态相关的关键事件为avatar_video.success携带video_id、video_url、thumbnail_url、duration与avatar_video.fail携带video_id与error可通过callback_id把回调关联回原始业务请求。Skill 的权衡结论是生产系统优先 Webhook脚本与原型阶段用轮询更省事。此外video-status.md 及其姊妹篇 heygen/SKILL.md 下的同名参考在仓库内存在多份对应内容avatar 与通用 heygen Skill 共用同一套状态语义。工程最佳实践清单使用指数退避——长任务运行中逐渐拉大轮询间隔降低无效请求频率工具层poll_heygen用interval * 1.2封顶 30s 即为参照实现设置合理超时——大多数视频 10 分钟内完成同步等待建议给 15–20 分钟上限优雅处理失败——优先检查failure_code/failure_message把可操作的错误信息直接抛给上层而非笼统报错考虑 Webhook——生产系统用事件推送替代轮询更高效缓存视频 URL——下载链接有效期有限拿到completed后应尽快下载或持久化文件不要长期保存 URL 指望复用区分两层错误——顶层error表示请求失败status: failed才表示视频业务失败处理逻辑不要混淆下载也要重试——completed不代表文件立即可取用指数退避补齐最后一公里。这套提交即返回 ID 轮询终态 退避下载的模式既是 HeyGen v2 头像视频 API 的标准用法也与 OpenMontage 仓库中 tools/video/heygen_video.py 与 tools/video/_shared.py 的实际工程实现相互印证——阅读本参考文档并对照源码即可在 Agent 流水线中稳定地拿到可用的 avatar 成片。【免费下载链接】OpenMontageWorlds first open-source, agentic video production system. 12 production pipelines, 100 tools, 700 agent skill and production-knowledge files. Turn your AI coding assistant into a full video production studio.项目地址: https://gitcode.com/GitHub_Trending/op/OpenMontage创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联
返回资讯列表 →