{
  "name": "万川 API｜图片生视频（原始图片直传）",
  "nodes": [
    {
      "parameters": {},
      "id": "1f34d824-6e1d-4b77-b137-e8c3232567f0",
      "name": "手动触发",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        -1340,
        220
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "142a01d3-10a6-481d-bc60-7a2e30c6a217",
              "name": "input_file",
              "value": "D:/n8n-input/reference.png",
              "type": "string"
            },
            {
              "id": "c8ac6890-0955-4c26-8111-91a982cfec2c",
              "name": "model",
              "value": "REPLACE_MODEL_FROM_AUTHORIZED_MEDIA_CATALOG",
              "type": "string"
            },
            {
              "id": "wanchuan-parameter-mode-image",
              "name": "parameter_mode",
              "value": "model_defaults",
              "type": "string"
            },
            {
              "id": "005e38a7-a3e9-4257-8677-f139dd190b07",
              "name": "prompt",
              "value": "保持主体和商品外观一致，生成自然、稳定的镜头运动。",
              "type": "string"
            },
            {
              "id": "38acfa88-4009-42bb-b830-5df1fbcd6966",
              "name": "duration",
              "value": 4,
              "type": "number"
            },
            {
              "id": "1f6cfbed-fc98-4a8e-bc4e-8fbc00932358",
              "name": "resolution",
              "value": "480p",
              "type": "string"
            },
            {
              "id": "bf22b489-79c3-45df-a607-a448be77143e",
              "name": "idempotency_nonce",
              "value": "REPLACE_WITH_UNIQUE_RUN_NONCE_MIN_8_CHARS",
              "type": "string"
            },
            {
              "id": "7c4ccb71-6ff3-46a6-a54c-829e19af6af5",
              "name": "provider_processing_acknowledged",
              "value": false,
              "type": "boolean"
            },
            {
              "id": "b1cf8874-c4f4-46c4-8502-c3d21ac17cf7",
              "name": "poll_limit",
              "value": 360,
              "type": "number"
            }
          ]
        },
        "options": {}
      },
      "id": "99b6622e-2e40-4d97-8533-b3a82cae75c8",
      "name": "万川｜运行配置",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        -1120,
        220
      ]
    },
    {
      "parameters": {
        "url": "https://api.wanchuanapi.com/v1/media/models",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {
          "timeout": 45000,
          "redirect": {
            "redirect": {
              "followRedirects": false
            }
          }
        }
      },
      "id": "wanchuan-authorized-media-models-image",
      "name": "万川｜读取授权媒体目录",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        -900,
        -120
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "REPLACE_WITH_WANCHUAN_CREDENTIAL_ID",
          "name": "万川 API Bearer（导入后选择）"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const referenceKind = 'image';\n// Embedded verbatim after referenceKind in both downloadable workflows.\nconst config = $node['万川｜运行配置'].json;\nconst model = String(config.model || '').trim();\nconst prompt = String(config.prompt || '').trim();\nif (!model || model.startsWith('REPLACE_')) throw new Error('请从 GET /v1/media/models 复制当前 API Key 的精确模型 ID，替换 model 占位符');\nif (!prompt) throw new Error('prompt 不能为空');\nconst catalog = $input.first().json;\nif (!Array.isArray(catalog.data)) throw new Error('授权媒体目录不可用；未上传素材或创建任务');\nconst matches = catalog.data.filter(item => item.id === model);\nif (matches.length !== 1) throw new Error('所选 model 不在当前 API Key 的授权媒体目录中');\nconst offer = matches[0];\nconst promptContract = offer.prompt_contract || {};\nconst characters = Array.from(prompt).length;\nif ((promptContract.empty_only && prompt) || characters < (promptContract.minimum_characters || 0)\n    || (promptContract.maximum_characters > 0 && characters > promptContract.maximum_characters)\n    || (promptContract.maximum_utf8_bytes > 0 && Buffer.byteLength(prompt, 'utf8') > promptContract.maximum_utf8_bytes)) throw new Error('提示词超出所选模型的公开限制');\nconst capability = referenceKind + '_to_video';\nif (offer.kind !== 'video_generation' || !offer.capabilities?.includes(capability)) throw new Error('所选模型未开放 ' + capability);\nconst schema = offer.parameters;\nconst reference = schema?.reference;\nif (!reference?.accepted?.includes(referenceKind) || reference.minimum > 1 || reference.maximum < 1) throw new Error('所选模型不支持当前参考素材类型或数量');\nif (config.provider_processing_acknowledged !== true) throw new Error('请先阅读所选模型的数据处理说明并明确开启处理确认');\n// Missing mode preserves the original workflow's explicit parameter behavior.\nconst mode = config.parameter_mode ?? 'explicit';\nif (!['model_defaults', 'explicit'].includes(mode)) throw new Error('parameter_mode 必须是 model_defaults（跟随模型默认）或 explicit（使用手填参数）');\nconst parameters = mode === 'model_defaults' ? {} : {duration: Number(config.duration), resolution: String(config.resolution || '').trim(), generate_audio: false};\nif (mode === 'explicit' && referenceKind === 'video' && config.aspect_ratio !== 'auto') parameters.aspect_ratio = String(config.aspect_ratio || '').trim();\nif (reference.aspect_ratio_follows_first_frame && Object.hasOwn(parameters, 'aspect_ratio')) throw new Error('所选模型比例跟随首帧，请把 aspect_ratio 设为 auto');\nfor (const [name, value] of Object.entries(parameters)) {\n  const rule = schema?.[name];\n  if (!rule) throw new Error('所选模型不支持参数 ' + name + '；未忽略该参数');\n  let valid = false;\n  if (Array.isArray(rule.options)) valid = rule.options.some(option => option.value === value);\n  else if (Number.isInteger(rule.minimum) && Number.isInteger(rule.maximum)) {\n    const step = rule.step ?? 1;\n    valid = Number.isInteger(value) && value >= rule.minimum && value <= rule.maximum && Number.isInteger(step) && step > 0 && (value - rule.minimum) % step === 0;\n    if (Array.isArray(rule.allowed_values) && rule.allowed_values.length) valid = valid && rule.allowed_values.includes(value);\n  } else if (typeof rule.default === 'boolean') valid = typeof value === 'boolean';\n  if (!valid) throw new Error('参数 ' + name + ' 不符合所选模型的公开能力');\n}\nreturn [{json: {...config, model, prompt, request_parameters: parameters}}];\n"
      },
      "id": "wanchuan-validate-media-model-image",
      "name": "万川｜核对模型与参数",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -680,
        -120
      ]
    },
    {
      "parameters": {
        "operation": "read",
        "fileSelector": "={{ $node[\"万川｜运行配置\"].json.input_file }}",
        "options": {}
      },
      "id": "c9a4fb86-5bc5-4fc6-b040-566a4a062783",
      "name": "万川｜读取参考图",
      "type": "n8n-nodes-base.readWriteFile",
      "typeVersion": 1,
      "position": [
        -900,
        220
      ]
    },
    {
      "parameters": {
        "jsCode": "const item = $input.first();\nconst config = $node['万川｜核对模型与参数'].json;\nif (!item.binary?.data) throw new Error('缺少 binary.data 参考图');\nif (config.provider_processing_acknowledged !== true) {\n  throw new Error('请在阅读万川所选模型的数据处理说明后，在“万川｜运行配置”中明确开启处理确认');\n}\nconst nonce = String(config.idempotency_nonce || '').trim();\nif (!/^[A-Za-z0-9._:-]{8,96}$/.test(nonce) || nonce.startsWith('REPLACE_')) {\n  throw new Error('请为本次逻辑任务设置 8–96 位唯一 idempotency_nonce；响应超时后重跑必须保持不变，新任务必须更换');\n}\nconst binary = item.binary.data;\nconst contentType = String(binary.mimeType || '').trim().toLowerCase();\nif (!['image/png', 'image/jpeg', 'image/webp'].includes(contentType)) throw new Error('参考图必须是 PNG、JPEG 或 WebP');\nlet contentLength;\nif (binary.id) {\n  const metadata = await this.helpers.getBinaryMetadata(binary.id);\n  contentLength = Number(metadata?.fileSize);\n} else {\n  const bytes = await this.helpers.getBinaryDataBuffer(0, 'data');\n  contentLength = bytes.length;\n}\nif (!Number.isSafeInteger(contentLength) || contentLength <= 0) throw new Error('无法从 n8n binary metadata 取得准确图片字节数');\nconst duration = Number(config.duration);\nconst pollLimit = Number(config.poll_limit);\nif (!Number.isInteger(pollLimit) || pollLimit < 1 || pollLimit > 1440) throw new Error('运行配置 poll_limit 必须是 1 到 1440 的整数');\nconst model = String(config.model || '').trim();\nconst prompt = String(config.prompt || '').trim();\nif (!model || !prompt) throw new Error('运行配置 model 和 prompt 不能为空');\nreturn [{\n  json: {\n    request_parameters: config.request_parameters,\n    content_type: contentType,\n    content_length: contentLength,\n    idempotency_nonce: nonce,\n    model,\n    prompt,\n    duration,\n    resolution: String(config.resolution || '').trim(),\n    poll_limit: pollLimit\n  },\n  binary: item.binary\n}];"
      },
      "id": "2c68730b-5785-4945-8ce6-5ee7930c318f",
      "name": "万川｜准备图片上传",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -680,
        220
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.wanchuanapi.com/v1/media/inputs",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Idempotency-Key",
              "value": "={{ ('n8n-input-' + $node[\"万川｜准备图片上传\"].json.idempotency_nonce).slice(0, 128) }}"
            },
            {
              "name": "Content-Type",
              "value": "={{ $json.content_type }}"
            },
            {
              "name": "Content-Length",
              "value": "={{ String($json.content_length) }}"
            }
          ]
        },
        "sendBody": true,
        "contentType": "binaryData",
        "inputDataFieldName": "data",
        "options": {
          "timeout": 600000,
          "redirect": {
            "redirect": {
              "followRedirects": false
            }
          }
        }
      },
      "id": "da209f7b-f497-45e0-b9fc-ae924a682b1d",
      "name": "万川｜上传媒体输入",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        -440,
        100
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "REPLACE_WITH_WANCHUAN_CREDENTIAL_ID",
          "name": "万川 API Bearer（导入后选择）"
        }
      }
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.wanchuanapi.com/v1/media/jobs",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Idempotency-Key",
              "value": "={{ ('n8n-job-' + $node[\"万川｜准备图片上传\"].json.idempotency_nonce).slice(0, 128) }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ (() => { const input = $node[\"万川｜上传媒体输入\"].json.input; if (input?.status !== \"ready\" || !/^[A-Za-z0-9_-]{1,160}$/.test(input?.id || \"\")) throw new Error(\"媒体输入未返回有效 ready ID；未创建任务\"); return JSON.stringify({ kind: \"video_generation\", model: $node[\"万川｜准备图片上传\"].json.model, prompt: $node[\"万川｜准备图片上传\"].json.prompt, parameters: { ...$node[\"万川｜准备图片上传\"].json.request_parameters, input_asset_ids: [input.id], provider_processing_acknowledged: true } }); })() }}",
        "options": {
          "timeout": 45000,
          "redirect": {
            "redirect": {
              "followRedirects": false
            }
          }
        }
      },
      "id": "104a78d8-a83b-4023-998c-348f70c1ae8e",
      "name": "万川｜创建图片生视频任务",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        520,
        220
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "REPLACE_WITH_WANCHUAN_CREDENTIAL_ID",
          "name": "万川 API Bearer（导入后选择）"
        }
      }
    },
    {
      "parameters": {
        "amount": 5,
        "unit": "seconds"
      },
      "id": "c6eb88a5-ddde-4514-af5d-c95adfcd5c27",
      "name": "等待 5 秒",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        760,
        220
      ],
      "webhookId": "23fa8d34-773d-4a39-befa-90f9c393b5d4"
    },
    {
      "parameters": {
        "url": "={{ 'https://api.wanchuanapi.com/v1/media/jobs/' + encodeURIComponent($node[\"万川｜创建图片生视频任务\"].json.id) }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {
          "timeout": 45000,
          "redirect": {
            "redirect": {
              "followRedirects": false
            }
          }
        }
      },
      "id": "533580b7-a0b5-4acd-a112-e7cad6ab8f95",
      "name": "万川｜查询任务",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1000,
        220
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "REPLACE_WITH_WANCHUAN_CREDENTIAL_ID",
          "name": "万川 API Bearer（导入后选择）"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const job = $input.first().json;\nconst status = String(job.status || '').toLowerCase();\nif (['failed', 'cancelled', 'canceled', 'expired', 'submission_uncertain'].includes(status)) {\n  const safeMessage = String(job.error_message || '未返回详情').replace(/https?:\\/\\/\\S+/gi, '[redacted-url]').slice(0, 500);\n  throw new Error(`万川任务失败（${job.error_code || status}）：${safeMessage}`);\n}\nconst done = ['succeeded', 'completed', 'success'].includes(status);\nconst pollLimit = Number($node['万川｜运行配置'].json.poll_limit);\nconst pollCount = $runIndex + 1;\nif (!done && pollCount >= pollLimit) throw new Error(`已达到万川轮询上限 ${pollLimit} 次；工作流不会自动重提收费任务`);\nif (done && !job.asset_id) {\n  throw new Error('任务已完成但没有返回 asset_id；为避免绕开万川连接，本模板不会改连其他下载节点');\n}\nreturn [{ json: { ...job, output_url: undefined, poll_count: pollCount, done } }];"
      },
      "id": "ffac0b00-cb59-4604-97e6-a54d52714d7b",
      "name": "万川｜检查任务状态",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1240,
        220
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "debcf047-bdd0-4a15-84f9-6b15520202be",
              "leftValue": "={{ $json.done }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "2756625d-4b4b-4218-971c-22cb88d66ea3",
      "name": "万川｜是否完成",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1480,
        220
      ]
    },
    {
      "parameters": {
        "url": "={{ 'https://api.wanchuanapi.com/v1/media/assets/' + encodeURIComponent($json.asset_id) }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {
          "response": {
            "response": {
              "responseFormat": "file",
              "outputPropertyName": "data"
            }
          },
          "timeout": 120000,
          "redirect": {
            "redirect": {
              "followRedirects": false
            }
          }
        }
      },
      "id": "665149c1-7634-4af0-89d2-d3b245223cb1",
      "name": "万川｜下载生成结果",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1720,
        120
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "REPLACE_WITH_WANCHUAN_CREDENTIAL_ID",
          "name": "万川 API Bearer（导入后选择）"
        }
      }
    }
  ],
  "pinData": {},
  "connections": {
    "手动触发": {
      "main": [
        [
          {
            "node": "万川｜运行配置",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "万川｜运行配置": {
      "main": [
        [
          {
            "node": "万川｜读取授权媒体目录",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "万川｜读取参考图": {
      "main": [
        [
          {
            "node": "万川｜准备图片上传",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "万川｜准备图片上传": {
      "main": [
        [
          {
            "node": "万川｜上传媒体输入",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "万川｜上传媒体输入": {
      "main": [
        [
          {
            "node": "万川｜创建图片生视频任务",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "万川｜创建图片生视频任务": {
      "main": [
        [
          {
            "node": "等待 5 秒",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "等待 5 秒": {
      "main": [
        [
          {
            "node": "万川｜查询任务",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "万川｜查询任务": {
      "main": [
        [
          {
            "node": "万川｜检查任务状态",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "万川｜检查任务状态": {
      "main": [
        [
          {
            "node": "万川｜是否完成",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "万川｜是否完成": {
      "main": [
        [
          {
            "node": "万川｜下载生成结果",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "等待 5 秒",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "万川｜读取授权媒体目录": {
      "main": [
        [
          {
            "node": "万川｜核对模型与参数",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "万川｜核对模型与参数": {
      "main": [
        [
          {
            "node": "万川｜读取参考图",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1",
    "saveDataErrorExecution": "none",
    "saveDataSuccessExecution": "none",
    "saveManualExecutions": false,
    "saveExecutionProgress": false,
    "redactionPolicy": "all"
  },
  "versionId": "85df3c7c-fc5e-446c-860a-675504b432a6",
  "meta": {
    "templateCredsSetupCompleted": false
  },
  "tags": []
}
