AI 视频全自动生产线 - opus.pro 视频生成 + OpusClip 字幕/元数据 + Genspark 高质量缩略图 + viaSocket → YouTube,全程零成本
┌──────────────────────────────────────────────────────────────────────────────┐
│ VideoGenerator v3 流水线 │
│ │
│ idea ──→ video-script ──→ Story Video ──→ 字幕+缩略图+元数据 ──→ YouTube │
│ (并行处理) │
└──────────────────────────────────────────────────────────────────────────────┘
详细流程:
① OpusClip guest token (免费, 7天有效)
POST /auth/grant-free-tool-credential
↓
② opus.pro Story Video 生成 (或 Agent Video)
POST /long-take-videos → CDN HEAD 轮询等 200
↓
③ 并行启动三组任务:
├── 字幕: source-videos → clip-projects → 轮询 → exportable-clips → compress
├── 缩略图: Genspark 图片模型 (eval_js SSE) → 轮询 → 同源 fetch → cPanel 公开托管
└── 元数据: generative-jobs × 3 (title + description + hashtag)
↓
④ viaSocket webhook → YouTube
Upload Video → Update Thumbnail → Add to Playlist
: 无服务器、无 API 费用、无 Oracle/ffmpeg/Whisper 依赖。
| 模式 | 入口方法 | 说明 |
|---|---|---|
| Story (推荐) | run(topic, {videoMode:'story', script}) | 你写 transcript → opus.pro 配画面 → 全流程 |
| Idea | fromIdea(idea) | 从一个 idea 自动生成 script → 再走 Story 流程 |
| Process | processExistingVideo(url, topic) | 已有视频 URL → 只走字幕+缩略图+元数据+上传 |
| Agent (legacy) | run(topic, {videoMode:'agent'}) | opus.pro Agent Video,仅 9:16,自带字幕 |
https://agent.opus.pro/(Story/Agent 模式需要 opus.pro token)upload.php 端点 (~/public_html/thumbnails/upload.php),用于缩略图公开托管| 端点 | 用途 |
|---|---|
POST /long-take-videos | Story Video 创建(transcript + 画面风格) |
HEAD s2v-ext.cdn.opus.pro/agent/workspace/{id}/final_video.mp4 | CDN 轮询,200=完成 |
POST /project | Agent Video 创建(legacy) |
| 端点 | 用途 |
|---|---|
POST /api/agent/ask_proxy | 提交图片生成请求(SSE 流式响应,返回 task_id) |
GET /api/spark/image_generation_task_detail?task_id=xxx | 轮询任务状态,等 status=SUCCESS |
GET /api/files/s/{IMAGE_ID}?cache_control=3600 | 下载无水印图片(需同源 cookie) |
POST ezmusicstore.com/thumbnails/upload.php | 上传到 cPanel 获取公开 URL |
| 端点 | 用途 |
|---|---|
POST /auth/grant-free-tool-credential | 获取 guest token(无需登录,7天有效) |
GET /fancy-template-presets | 22 种字幕样式列表 |
POST /source-videos | 视频预检(语言检测、时长) |
POST /clip-projects | 创建字幕项目 |
GET /clip-projects/{id} | 轮询项目状态(stage=COMPLETE) |
GET /exportable-clips?projectId={id} | 获取高清无水印视频 URL |
| jobType | 输入参数 | 输出 |
|---|---|---|
thumbnail | {sourceUri} | 2张 1280×720 PNG(备选,质量较低,已被 Genspark 替代) |
video-script | {idea, platform, videoType, audience, tone, duration} | Markdown 脚本 |
youtube-title | {text} | 5 个标题候选 |
youtube-description | {text} | 3 个描述候选 |
youtube-hashtag | {description} | 20 个 hashtag |
video-compression | {sourceUri} | 压缩后视频 URL |
ai-video-summarizer | {sourceUri} | 视频摘要(待测试) |
transcript | {sourceUri} | 转录文本(待测试) |
所有 generative-jobs 通过 POST /generative-jobs 创建,GET /generative-jobs/{jobId} 轮询,status=CONCLUDED 表示完成。
| 端点 | Webhook payload |
|---|---|
POST https://flow.sokt.io/func/scri42hM0QuZ | {video_url, thumbnail_url, youtube_title, youtube_description, playlist_id, category_id} |
Workflow 三步: Upload Video → Update Thumbnail → Add to Playlist
Authorization: Bearer <token>
X-OPUS-ORG-ID: <orgId>
X-OPUS-USER-ID: <userId>
Content-Type: application/json
Origin: https://clip.opus.pro
Referer: https://clip.opus.pro/captions
Token 通过 grant-free-tool-credential 获取,无需登录,每次生成新 guest 身份。
| layoutAspectRatio | 分辨率 | 场景 |
|---|---|---|
landscape | 16:9 | YouTube 常规视频 |
portrait | 9:16 | YouTube Shorts |
square | 1:1 | |
four_five | 4:5 |
Karaoke, Gameplay, Beasty (MrBeast), Deep Diver, Youshaei, Pod P, Mozi, Netflix, Hormozi, AJ, Boldy, Minimal, Poppy, Sleek, Ali A, Spotlight, Wired, Baseline, Iman, TedX, Beast, The Standard
通过 brandTemplateId: "preset-fancy-{Name}" 指定。
{
"videoUrl": "https://...",
"brandTemplateId": "preset-fancy-Karaoke",
"productTier": "FREE.CAPTIONS",
"curationPref": { "skipSlicing": true },
"renderPref": {
"layoutAspectRatio": "landscape",
"enableCaption": true,
"enableHighlight": true
}
}
Genspark 图片模型 (nano-banana-pro) 生成的缩略图质量远高于 OpusClip generative-jobs {jobType: thumbnail}。支持自定义 prompt 精确控制构图,输出无水印 1376×768 JPEG。
POST /api/agent/ask_proxy(SSE 流),拿 task_idGET /api/spark/image_generation_task_detail,等 status=SUCCESS,拿 image_urls_nowatermarkezmusicstore.com/thumbnails/upload.php → 拿到公开 URL| 参数 | 值 | 说明 |
|---|---|---|
model_params.model | nano-banana-pro | 图片生成模型 |
model_params.aspect_ratio | 16:9 | 宽高比 |
model_params.auto_prompt | true | 自动优化 prompt |
model_params.background_mode | true | 背景模式 |
type | image_generation_agent | 任务类型 |
project_id | 7e6cbd20-270d-43aa-afe0-331d1c6d7f52 | 图片生成 Agent ID |
直接在 Genspark prompt 中要求生成带文字的完整缩略图,不需要 ImageMagick 后期叠加。nano-banana-pro 模型可以直接渲染清晰的英文文字。在 prompt 中明确指定文字内容、颜色、位置、大小即可。
在 prompt 中同时描述场景和文字,一步到位生成完整缩略图。[文字指令] 示例:Large bold white text at top: TITLE LINE. Large bold red text below: SUBTITLE. Small yellow text at bottom: TAGLINE.
| 分类 | Prompt 模板 |
|---|---|
| tech | YouTube thumbnail 1280x720 with bold text. Futuristic tech style: [场景描述]. [文字指令]. Neon blue and cyan accents, dark background, circuit board elements. Ultra sharp, cinematic. |
| business | YouTube thumbnail 1280x720 with bold text. Professional business style: [场景描述]. [文字指令]. Corporate aesthetics, dramatic lighting, gold and navy accents. Ultra high quality. |
| science | YouTube thumbnail 1280x720 with bold text. Scientific documentary style: [场景描述]. [文字指令]. Deep space blue, laboratory aesthetics, data visualization elements. Cinematic. |
| people | YouTube thumbnail 1280x720 with bold text. Editorial portrait style: [场景描述]. [文字指令]. Dramatic side lighting, bokeh background, warm tones. Magazine quality. |
| society | YouTube thumbnail 1280x720 with bold text. Journalistic documentary style: [场景描述]. [文字指令]. Urban settings, natural lighting, gritty realism. High contrast. |
| culture | YouTube thumbnail 1280x720 with bold text. Artistic cultural style: [场景描述]. [文字指令]. Rich colors, artistic composition, museum-quality aesthetics. Painterly lighting. |
| 属性 | 值 |
|---|---|
| 分辨率 | 1376 × 768 (接近 16:9) |
| 格式 | JPEG |
| 文件大小 | 通常 500KB - 800KB |
| 速度 | 10-30 秒 |
/api/files/s/ 端点需要 cookie 认证,只能通过 eval_js 在同源页面内 fetch,不能用 curl.then() Promise 链image_urls_nowatermark 格式为 /api/files/s/{IMAGE_ID}?cache_control=3600详细 API 文档见: skills/opus-video/THUMBNAIL_GENSPARK.md
| 内部分类 | YouTube categoryId | Playlist ID |
|---|---|---|
| tech | 28 (Science & Tech) | PLYtnUtZt0ZnFNjguN43KAb3aYFwCMTYZW |
| people | 22 (People & Blogs) | PLYtnUtZt0ZnGnjjJ3L60TIK7kBT93yRo3 |
| society | 24 (Entertainment) | PLYtnUtZt0ZnFssUY9G1cLpXO-D6JKPHH5 |
| science | 27 (Education) | PLYtnUtZt0ZnFn-PNqSLN-_wPkIFGGCSlw |
| business | 24 (Entertainment) | PLYtnUtZt0ZnE0_9LXZTFOlgFxFB-oh8sK |
| culture | 24 (Entertainment) | PLYtnUtZt0ZnHIwG9vhWqSr6t1vGRr0AQR |
| wildcard | 24 (Entertainment) | PLYtnUtZt0ZnF-oneo7UEDTO_OGJQ12ovZ |
每天自动轮换分类 (周日=wildcard, 周一=tech, ...)。
9 种风格: economic (默认), claymation, watercolor, halftone, collage, penink, schematic, line2d, animation。
通过 options.style 或 options.styleText 自定义。
在 opus.pro 页面通过扩展面板触发 VideoGenerator.showTopicDialog(),弹出对话框选择模式、分类、比例、样式,输入 topic/script 后一键启动。实时日志显示在对话框底部。
extension/
video-generator.js (707行) VideoGenerator class + UI dialog
content.js (3653行) 扩展主体,加载 video-generator.js
skills/opus-video/
SKILL.md 本文档
CAPTIONS_API.md OpusClip Captions API 详细文档
THUMBNAIL_GENSPARK.md Genspark 缩略图 API 详细文档
const vg = new VideoGenerator();
const result = await vg.fromIdea('How quantum computing will change cryptography', {
category: 'tech',
aspectRatio: 'landscape',
style: 'economic',
});
// result: { success, video_url, thumbnail_url, youtube_title, youtube_description, playlist_id, ... }
执行过程:
getClipCredential() → guest tokengenerative-jobs {jobType: video-script} → 生成 2 分钟脚本long-take-videos → opus.pro 生成 Story Video → CDN 轮询addCaptions() + generateThumbnail() (Genspark API → cPanel) + generateMetadata()pollCaptionProject() → getExportUrl() → compressVideo()uploadToYouTube() → viaSocket webhookconst vg = new VideoGenerator();
const result = await vg.processExistingVideo(
'https://cdn.opus.pro/.../final_video.mp4',
'Quantum cryptography explained',
{ category: 'science', aspectRatio: 'landscape' }
);
在 opus.pro 页面,扩展面板点击按钮 → 弹出 Video Generator v3 对话框 → 选择模式/分类/比例/样式 → 输入 topic → Start。
d.data.status === 'SUCCESS'?key=ag3nt2026&name=文件名.jpg,POST raw bodyPOST /long-take-videos。每次创建都消耗 credit。HEAD https://s2v-ext.cdn.opus.pro/agent/workspace/{id}/final_video.mp4 纯公开 URL,404=生成中,200=完成。.then() Promise 链返回结果。video-compression,不是 compress。必须在 opus.pro 的 tab 上用 eval_js 执行(需要该域的 localStorage token):
Ω{"tool":"eval_js","params":{"code":"return (async () => { var token = localStorage.getItem('atom:user:access-token').replace(/^\\\"|\\\"$/g, ''); var orgId = localStorage.getItem('atom:user:org-id').replace(/^\\\"|\\\"$/g, ''); var userId = localStorage.getItem('atom:user:org-user-id').replace(/^\\\"|\\\"$/g, ''); var resp = await fetch('https://api.opus.pro/api/long-take-videos', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token, 'X-OPUS-ORG-ID': orgId, 'X-OPUS-USER-ID': userId, 'Origin': 'https://agent.opus.pro', 'Referer': 'https://agent.opus.pro/' }, body: JSON.stringify({ prompt: SCRIPT_TEXT, ratio: '9:16', customStyle: false, styleText: 'STYLE_TEXT', voiceId: 'MM0375rv1dy8' }) }); var text = await resp.text(); return { status: resp.status, body: text.substring(0, 500) }; })();","tabId":OPUS_TAB_ID}}ΩSTOP
返回格式: {"projectId":"02101512-8up","workflowId":"long-take:02101512-8up","videoId":"02101512-8up"}
创建成功后立即同时启动以下三组任务,不需要等视频完成:
2a. CDN 轮询(async_task):
Ω{"tool":"async_task","params":{"code":"return fetch('https://s2v-ext.cdn.opus.pro/agent/workspace/PROJECT_ID/final_video.mp4',{method:'HEAD'}).then(function(r){return {status:r.status,ready:r.status===200};});","condition":"result.ready === true","interval":30000,"timeout":1800000,"tabId":ANY_TAB_ID,"label":"轮询 Story Video PROJECT_ID"}}ΩSTOP
2b. 缩略图生成(eval_js fire-and-forget + async_task 轮询):
第一步:发送请求,存 window 变量,立即 return:
Ω{"tool":"eval_js","params":{"code":"window._tid=null; window._tdone=false; var mp={}; mp.model='nano-banana-pro'; mp.aspect_ratio='9:16'; mp.auto_prompt=true; mp.background_mode=true; var tp={}; tp.type='image_generation_agent'; tp.project_id='7e6cbd20-270d-43aa-afe0-331d1c6d7f52'; tp.model_params=mp; var msg='THUMBNAIL_PROMPT_HERE'; tp.messages=[{id:Date.now().toString(),role:'user',content:msg,created_at:new Date().toISOString()}]; fetch('/api/agent/ask_proxy',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(tp)}).then(function(r){return r.text()}).then(function(t){var p=t.split('task_id'); if(p.length>1){var pat=new RegExp('([a-f0-9]+-[a-f0-9]+-[a-f0-9]+-[a-f0-9]+-[a-f0-9]+)'); var u=p[1].match(pat); if(u) window._tid=u[1];} window._tdone=true;}); return 'started';","tabId":GENSPARK_TAB_ID}}ΩSTOP
第二步:轮询 task_id:
Ω{"tool":"async_task","params":{"code":"return {taskId:window._tid,done:window._tdone};","condition":"result.taskId","interval":3000,"timeout":30000,"tabId":GENSPARK_TAB_ID,"label":"等待缩略图task_id"}}ΩSTOP
第三步:拿到 task_id 后轮询图片生成状态(注意 d.data.status):
Ω{"tool":"async_task","params":{"code":"return fetch('/api/spark/image_generation_task_detail?task_id=TASK_ID').then(function(r){return r.json()}).then(function(d){return {status:d.data.status, urls:d.data.image_urls_nowatermark};});","condition":"result.status === 'SUCCESS'","interval":5000,"timeout":120000,"tabId":GENSPARK_TAB_ID,"label":"缩略图轮询"}}ΩSTOP
第四步:下载 + 上传 cPanel(注意带 key 和 name 参数):
Ω{"tool":"eval_js","params":{"code":"return fetch('/api/files/s/IMAGE_ID?cache_control=3600').then(function(r){return r.arrayBuffer();}).then(function(buf){return fetch('https://ezmusicstore.com/thumbnails/upload.php?key=ag3nt2026&name=FILENAME.jpg',{method:'POST',body:buf});}).then(function(r){return r.json();});","tabId":GENSPARK_TAB_ID}}ΩSTOP
2c. 元数据(bash 一次性获取 token + 创建 3 个 jobs):
bash -c '
RESP=$(curl -s -X POST https://api.opus.pro/api/auth/grant-free-tool-credential -H "Content-Type: application/json" -H "Origin: https://clip.opus.pro")
T=$(echo $RESP | python3 -c "import sys,json;d=json.load(sys.stdin);print(d[\"data\"][\"token\"])")
O=$(echo $RESP | python3 -c "import sys,json;d=json.load(sys.stdin);print(d[\"data\"][\"orgId\"])")
TEXT="VIDEO_SUMMARY_TEXT"
echo "=== TITLE ==="
curl -s -X POST https://api.opus.pro/api/generative-jobs -H "Authorization: Bearer $T" -H "X-OPUS-ORG-ID: $O" -H "X-OPUS-USER-ID: $O" -H "Content-Type: application/json" -H "Origin: https://clip.opus.pro" -d "{\"jobType\":\"youtube-title\",\"text\":\"$TEXT\"}"
echo ""
echo "=== DESC ==="
curl -s -X POST https://api.opus.pro/api/generative-jobs -H "Authorization: Bearer $T" -H "X-OPUS-ORG-ID: $O" -H "X-OPUS-USER-ID: $O" -H "Content-Type: application/json" -H "Origin: https://clip.opus.pro" -d "{\"jobType\":\"youtube-description\",\"text\":\"$TEXT\"}"
echo ""
echo "=== HASHTAG ==="
curl -s -X POST https://api.opus.pro/api/generative-jobs -H "Authorization: Bearer $T" -H "X-OPUS-ORG-ID: $O" -H "X-OPUS-USER-ID: $O" -H "Content-Type: application/json" -H "Origin: https://clip.opus.pro" -d "{\"jobType\":\"youtube-hashtag\",\"description\":\"$TEXT\"}"
'
查询结果时也要重新获取 token(因为有效期短):
bash -c '
RESP=$(curl -s -X POST https://api.opus.pro/api/auth/grant-free-tool-credential -H "Content-Type: application/json" -H "Origin: https://clip.opus.pro")
T=$(echo $RESP | python3 -c "import sys,json;d=json.load(sys.stdin);print(d[\"data\"][\"token\"])")
O=$(echo $RESP | python3 -c "import sys,json;d=json.load(sys.stdin);print(d[\"data\"][\"orgId\"])")
curl -s "https://api.opus.pro/api/generative-jobs/JOB_ID" -H "Authorization: Bearer $T" -H "X-OPUS-ORG-ID: $O" -H "X-OPUS-USER-ID: $O" -H "Origin: https://clip.opus.pro"
'
视频较大(通常 50-100MB),用 bg_run 后台下载:
Ω{"tool":"bg_run","params":{"command":"curl -o /Users/yay/workspace/VIDEO_NAME.mp4 'https://s2v-ext.cdn.opus.pro/agent/workspace/PROJECT_ID/final_video.mp4'"}}ΩSTOP
下载完成后获取视频参数并写 ffmpeg 脚本:
ffprobe -v 0 -select_streams v:0 -show_entries stream=r_frame_rate,width,height -of csv=p=0 VIDEO.mp4
ffprobe -v 0 -select_streams a:0 -show_entries stream=sample_rate -of csv=p=0 VIDEO.mp4
ffmpeg 脚本(写成 .sh 文件执行,不要在 run_command 直接拼复杂参数):
#!/bin/bash
set -e
VIDEO=/Users/yay/workspace/VIDEO_NAME.mp4
THUMB=/Users/yay/workspace/THUMB_NAME.jpg # 先 curl 从 cPanel 下载
OUTPUT=/Users/yay/workspace/VIDEO_FINAL.mp4
ENDFRAME=/Users/yay/workspace/endframe.mp4
# 生成 1 帧视频(时长 = 1/fps 秒)
ffmpeg -y \
-loop 1 -i $THUMB \
-f lavfi -i anullsrc=r=SAMPLE_RATE:cl=stereo \
-t FRAME_DURATION \
-vf scale=WIDTH:HEIGHT \
-c:v libx264 \
-preset veryfast \
-crf 18 \
-c:a aac \
-ar SAMPLE_RATE \
-pix_fmt yuv420p \
-r FPS \
$ENDFRAME
# concat
printf "file 'VIDEO_NAME.mp4'\nfile 'endframe.mp4'\n" > /Users/yay/workspace/concat-list.txt
ffmpeg -y -f concat -safe 0 -i /Users/yay/workspace/concat-list.txt -c copy $OUTPUT
上传最终视频到 cPanel(bg_run 因为文件大):
Ω{"tool":"bg_run","params":{"command":"curl -X POST 'https://ezmusicstore.com/thumbnails/upload.php?key=ag3nt2026&name=VIDEO_FINAL.mp4' --data-binary @/Users/yay/workspace/VIDEO_FINAL.mp4"}}ΩSTOP
提交 webhook(用 bash stdin 避免转义问题):
curl -s -X POST 'https://flow.sokt.io/func/scri42hM0QuZ' \
-H 'Content-Type: application/json' \
-d '{
"video_url": "https://ezmusicstore.com/thumbnails/VIDEO_FINAL.mp4",
"thumbnail_url": "https://ezmusicstore.com/thumbnails/THUMB_NAME.jpg",
"youtube_title": "TITLE (60-70字符以内)",
"youtube_description": "DESCRIPTION\n\n#Hashtag1 #Hashtag2 ...",
"playlist_id": "PLAYLIST_ID",
"category_id": "CATEGORY_ID"
}'
rm -f /Users/yay/workspace/VIDEO_NAME.mp4 /Users/yay/workspace/VIDEO_FINAL.mp4 /Users/yay/workspace/endframe.mp4 /Users/yay/workspace/concat-list.txt /Users/yay/workspace/*.sh
POST /api/agent/ask_proxy 的请求体必须用 messages: [{id, role, content, ...}] 数组格式,不能用 query 字段。用 query 会返回 SSE 流但不触发图片生成任务(没有 task_id),白白浪费请求。
在 eval_js 中直接写 {type: 'image_generation_agent', project_id: '...'} 这样的对象字面量,字符串值中的某些词(如 image_generation_agent)会被 JSON 多层解析搞坏,报 Unexpected identifier。**解决**:用变量赋值方式构建对象,如 var mp = {}; mp.type = 'image_generation_agent';`,避免在对象字面量中直接写含特殊词的字符串。
Genspark nano-banana-pro 模型可以直接在图片中渲染清晰的英文文字。不需要分两步(AI 背景 + ImageMagick 叠加),直接在 prompt 中描述文字内容、颜色、位置即可一步生成完整缩略图。
OpusClip 签名 URL(含 hdnts= 参数)有有效期,过期后 webhook 下载会报 "Failed to download video"。如果从生成签名 URL 到提交 webhook 间隔较长,签名可能已过期。保险做法:优先用 Story Video 的原始 CDN URL https://s2v-ext.cdn.opus.pro/agent/workspace/{id}/final_video.mp4(无需签名,永久可访问)。如果需要带字幕版,确保签名 URL 未过期再提交 webhook。
所有需要等待的任务都应该用 async_task(浏览器端轮询)或 bg_run(命令行后台)来异步执行,而不是反复 sleep N && curl 手动轮询。具体来说:CDN 视频轮询、图片生成轮询、字幕项目轮询、generative-jobs 轮询都应该用 async_task 一次启动后台等通知,不要来回对话浪费轮次。
在 eval_js 中写正则表达式极易被多层转义搞坏(JSON → JS → 正则)。禁止在 eval_js 中使用 [^...] 等复杂正则。
提取 task_id 的正确方式:用 split + 简单 UUID 匹配:
var parts = text.split('task_id');
if (parts.length > 1) {
var uuid = parts[1].match(/([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})/);
if (uuid) window._taskId = uuid[1];
}
model_params.aspect_ratio 设为 9:16 不一定生成竖屏图。nano-banana-pro 会根据 prompt 内容自行判断方向。必须在 prompt 文本中明确写上方向,例如:
"YouTube Shorts vertical 9:16 portrait thumbnail for ...""YouTube landscape 16:9 widescreen thumbnail for ..."生成后必须用 identify 验证尺寸,不符合就重新生成。
Agent Video 后台 worker 偶发任务调度失败,项目会永远卡在 INITIALIZING(updatedAt 不变)。判断方法:创建后 2 分钟查一次,如果 stage 仍是 INITIALIZING 且 updatedAt 没变,基本确定卡死了。此时应放弃该项目,用 Story 模式重做。
Story 模式更稳定,作为首选。Agent 模式仅在需要 9:16 自带字幕时使用。
在视频开头加 3 秒静态缩略图 intro 会致:
正确做法:把缩略图只加 1 帧到视频结尾,不影响音频和字幕同步。
用 concat demuxer 合并视频时,intro 和原视频的音频采样率必须一致。opus.pro 输出视频通常是 48000Hz,生成 intro 的静音音轨也要用 anullsrc=r=48000,不能用默认的 44100。
JSON.parse() 去引号,不要用正则 replace上传缩略图到 cPanel 时,URL 必须带 ?key=ag3nt2026&name=文件名.jpg,且 POST body 直接传 raw arrayBuffer,不是 multidata。正确写法:
fetch('https://ezmusicstore.com/thumbnails/upload.php?key=ag3nt2026&name=xxx.jpg', {method:'POST', body: arrayBuffer})
不带 key 参数会返回 {"error":"forbidden"}。每次都在这里浪费重试,必须记住。
GET /api/spark/image_generation_task_detail 返回 {status: 0, message: "...", data: {status: "SUCCESS", image_urls_nowatermark: [...]}}。轮询条件必须用 d.data.status === 'SUCCESS',不是 d.status === 'SUCCESS'。第一层 status 是 HTTP 状态码 0,真正的任务状态在 data.status 里。