fix: canvas image/video download - fix data URL parsing in cvDownloadPreview (v1.5.21, hot 1.5.23)

This commit is contained in:
mac
2026-07-20 08:48:56 +08:00
parent 42e23663ee
commit aadd2b04ae
28 changed files with 5881 additions and 329 deletions
+24 -27
View File
@@ -50,17 +50,16 @@ canvasRun: (kind, params) => ipcRenderer.invoke("canvas-run", { kind, params }),
- `model`:**用户填写的图像模型**(如 `gpt-image-1`/`dall-e-3`/`flux-pro`/`seedream-3` 等)。**为空则直接返回 `{ok:false,error:"请先填写图像模型"}`,不要回退聊天模型。**
- `size`:`"WxH"`(前端按 比例×清晰度 算好传来)。
- `endpointPath`:默认 `/v1/images/generations`,允许用户覆盖。
- **图生图参考图**:`image`(用户上传的 base64 dataURL)→ 主进程 `uploadMedia()` 传兰空图床换公网直链 → 塞进请求体字段 `refField`(默认 `image`,前端高级项可改)。上传失败直接返回 `{ok:false,error}`,不静默降级。流式与非流式两条 body 都带该字段。
- **图生图参考图**:`image`(用户上传的 base64 dataURL)→ 主进程 `uploadMedia()` 传图床换公网直链 → 塞进请求体字段 `refField`(默认 `image`,前端高级项可改)。上传失败直接返回 `{ok:false,error}`,不静默降级。流式与非流式两条 body 都带该字段。
- 错误信息里带上**实际请求的 URL 和 model**,方便排查(如 404 时提示 `POST {url} model={model} → 404`)。
- **video** `{ mode, prompt, model, image?, lastFrameImage?, referenceImages?[], referenceVideo?, ratio?, resolution?, duration?, generateAudio?, watermark?, cameraFixed?, seed?, endpointPath? }` —— **Seedance 2.0**(完整契约,port 自 提示词提取/server.js `buildVideoBody`)
- `mode`:`"text"` 文生视频 | `"first"` 首帧 | `"firstlast"` 首尾帧 | `"reference"` 多参考图 | `"video"` 参考视频。旧值 `"image"` 归一为 `"first"`。
- **素材**(主进程 `runVideo` 里经 `resolveMat`:`data:`→兰空图床外链,`http(s)`→原样):`image`(首帧)、`last_frame_image`、`reference_images[]`;**`reference_video` 仅接受公网URL**(图床不托管视频)。
- `input_mode` 仅在 mode≠text 时下发;素材按模式互斥;缺必填素材直接 `{ok:false,error}`。
- `metadata`:`ratio / resolution / generate_audio / watermark / camera_fixed / seed`(auto/-1/空不塞)。
- `model`:用户填写的视频模型(为空返回明确错误)。
- body 组装:`{ model, prompt, ...(mode==='image'?{image}:{}) , size, ratio, duration, ...extra }`(空字段不塞)。
- `endpointPath` 默认 `/v1/videos`,允许覆盖。仍严禁伪造成功。
- **grokVideo** `{ mode, prompt, model, image?, referenceImages?[], resolution?, seconds?, aspectRatio?, endpointPath? }` —— **Grok 视频**。`mode:"reference"` 走 Reference-to-Video:强制 `model:"grok-imagine-video"`、`resolution:"720p"`,把 `referenceImages[]` 解析为 `reference_images: [{url}]`;`grok-imagine-video-1.5` 仅用于单图首帧场景,不用于多参考图。
- **video** `{ mode, prompt, model?, image?, lastFrameImage?, referenceImages?[], ratio?, quality?, seconds?, referenceField?, endpointPath? }` —— **Seedance 2.0**,使用新中转契约。
- 创建端点默认 `/v1/video/generations`;若分组 `baseUrl` 已以 `/v1` 结尾,拼接时不得产生重复的 `/v1/v1`。
- 请求体是扁平结构:`prompt` 必填,固定 `async:true`;`duration` 仅发送整数 `5/10/15`;`ratio`、`quality` 非自动时才发送;`model` 可省略并使用上游默认。
- `mode`:`"text"` 只发送文本 | `"first"` 发送 `image` | `"firstlast"` 将首尾两图作为 `images` | `"reference"` 按 `referenceField` 发送 `images` 或 `ref_assets`。旧值 `"image"` 归一为 `"first"`。
- 图片经 `resolveMat` 处理:`data:` 先上传图床换为公网 URL,`http(s)` 原样发送;上游请求中禁止出现 base64。
- 不发送旧版 `input_mode`、`metadata`、`resolution`、`generate_audio`、`watermark`、`camera_fixed`、`seed`、`reference_video` 字段。
- 创建响应任务 ID 兼容 `request_id/requestId/task_id/taskId/id`,并递归检查 `data/result/task`;字符串和数字 ID 都支持。
- **grokVideo** `{ mode, prompt, model, image?, referenceImages?[], resolution?, seconds?, aspectRatio?, endpointPath? }` —— **Grok 视频**。首帧图生视频默认走 `model:"grok-video-1.5"`,提交体为 `{ model, prompt, seconds:"6", size:"1280x720", image:{url:"https://.../image.png"} }`;上传/上游传来的 dataURL 会先转公网 URL,再放入 `image.url`。`resolution + aspectRatio` 只在本地映射为 `size`(如 `9:16 + 720p -> "720x1280"`),不下发 `duration/resolution/image_url/aspect_ratio`。`mode:"reference"` 走旧 Reference-to-Video:强制 `model:"grok-imagine-video"`、`resolution:"720p"`,把 `referenceImages[]` 解析为 `reference_images: [{url}]`,并下发 `aspect_ratio`。
- **tts** 增加 `model`(默认 `tts-1` 可保留但允许覆盖)、`format`、可选 `speed`。
### 新增 canvasModels(后端 + preload)
@@ -130,26 +129,24 @@ canvasRun: (kind, params) => ipcRenderer.invoke("canvas-run", { kind, params }),
- 最终 `canvasRun` resolve 的成品仍照常渲染覆盖;skeleton 在收到第一帧 partial 后即可替换为图片。
- 仅 image 节点用到渐进预览;video/tts 不变。
## 【v5 增补 · 接入 TokenDun/Seedance 真实视频接口】——本轮必须实现
参照 `/Users/mac/Documents/备份数据/ProjectDown/提示词提取/server.js`(Seedance 视频 API)。异步任务制:建任务→轮询→下载。gwBase = `{分组baseUrl}/v1`。
## 【v5 增补 · 接入 TokenDun/Seedance 真实视频接口】
Seedance 使用异步任务制:建任务→轮询→下载。以下内容按当前 `/v1/video/generations` 文档更新,覆盖早期火山 `metadata/input_mode` 版本。
### 后端 runVideo 重写(canvas.js)——完全替换旧 /v1/videos 逻辑
- 端点:create = `{base}{endpointPath||"/v1/video/generations"}`;poll = `create + "/" + taskId`(GET)。鉴权 `Bearer <key>`(分组由 groupId 解析,同 v3)。
- 端点:create = `joinUrl(base, endpointPath||"/v1/video/generations")`;poll = `create + "/" + taskId`(GET)。鉴权 `Bearer <key>`(分组由 groupId 解析,同 v3)。
- **body 组装**(严格照 Seedance):
- `{ model, prompt }`(model 空→报错"请先填写视频模型")
- `seconds`:字符串,params.seconds 非空才加(值 "-1"=自动/"5"/"10")。
- 文生视频(mode!=='image'):不加 input_mode、不加 image。
- 图生视频(mode==='image'):`input_mode:"first"` + `image: params.imageUrl`。**imageUrl 必须是 http(s) 公网URL**;若为空或以 `data:` 开头 → 直接返回 `{ok:false,error:"图生视频需填公网图片URL(Seedance 不接受 base64/上游图)"}`,**不要把 base64 发出去**。
- `metadata`:{ ratio(≠auto才加), resolution(≠auto才加), generate_audio(bool 才加) },非空才挂到 body.metadata。
- **建任务**:POST create。2xx 才继续;非 2xx 返回 `视频建任务失败(状态):{detail前300}`。解析 taskId:`data.data?.task_id||data.data?.id||data.task_id||data.id||data.result?.task_id||data.result?.id`;无则报错。
- **轮询**:GET poll,每 5s 一次,最多 ~5 分钟(60 次)。用 `normalizeVideoStatus`(port 自 server.js);succeeded → `findVideoUrl`(port,深挖 video_url/result_url/url) → 下载转 base64 → `{ok:true,mime:"video/mp4",dataUrl,meta}`;failed/超时 → 明确错误。仍严禁伪造成功。
- 基础字段 `{ prompt, async:true }`;`model` 可选。
- `params.seconds` 仅为 `5/10/15` 时转成整数 `duration`;`ratio`、`quality` 非 `auto` 时按顶层字段发送。
- 文生视频不发送图片字段;首帧发送 `image:string`;多图发送 `images:string[]`;全能参考发送 `ref_assets:string[]`。
- 图片必须是公网 URL;本地 data URL 先经 `uploadMedia()` 转成公网地址。
- **建任务**:POST create。2xx 才继续;非 2xx 返回状态、实际 URL、model 和上游详情。响应先读取 text 再解析 JSON,确保非 JSON 错误不会丢失原文。任务 ID 由通用 `pickTaskId()` 提取。
- **轮询**:GET poll,每 5s 一次,最多约 10 分钟(120 次)。用 `normalizeVideoStatus`;succeeded → `findVideoUrl`(深挖 `video_url/result_url/url`)→ 下载转 base64 → `{ok:true,mime:"video/mp4",dataUrl,meta}`;failed/超时 → 明确错误。仍严禁伪造成功。
- port 两个纯函数 `normalizeVideoStatus`、`findVideoUrl` 到 canvas.js。全程 try/catch。
### 前端视频节点(renderer/*)
- 模式:文生视频 / 图生视频(保留)。
- **图生视频时显示「公网图片URL」输入框** `.cv-field-imgurl`(hint:Seedance 需公网图片URL,上游 base64 图无法直接用)。文生视频时隐藏。
- 比例(16:9/9:16/1:1/4:3) + 分辨率(480p/720p/1080p/自动) 保留。
- **时长改为**:自动(-1) / 5s / 10s(对应 seconds 值 "-1"/"5"/"10","自动"传 "-1" 或空)。
- **加「生成音频」勾选** `.cv-field-audio`(→ generateAudio bool)。
- 高级:接口地址(默认 /v1/video/generations)。
- 运行 params:`{ mode:'text'|'image', prompt, model, ratio, resolution, seconds, generateAudio, imageUrl(仅image), endpointPath, groupId }`。图生视频且 imageUrl 空/为 data: → 前端红字拦截。
- 模式:首帧图、双图参考、多参考图/全能参考、文生视频。
- `prompt` 在所有模式必填;模型可留空使用上游默认,也可通过分组拉取当前可用模型。
- 比例:16:9 / 9:16 / 1:1 / 4:3;画质:自动 / `hd`;时长:自动 / 5s / 10s / 15s。
- 高级:接口地址(默认 `/v1/video/generations`)和多参考图字段(`images/ref_assets`)。
- 不展示旧接口已不支持的分辨率、音频、水印、固定镜头、seed、参考视频控件。
- 运行 params:`{ mode, prompt, model?, ratio, quality, seconds, referenceField, image?, lastFrameImage?, referenceImages?, endpointPath?, groupId }`。