{
  "name": "万川 API｜视频生视频（H.264 MP4 原始视频直传）",
  "nodes": [
    {
      "parameters": {},
      "id": "d93ecf6c-0dcf-4d3f-a344-0a37285b6191",
      "name": "手动触发",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        -1340,
        220
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "cf707f02-b7bc-445c-a0f6-c4a317d7ae82",
              "name": "input_file",
              "value": "D:/n8n-input/reference.mp4",
              "type": "string"
            },
            {
              "id": "14cda259-f032-407d-aeb6-46e78702966a",
              "name": "model",
              "value": "REPLACE_MODEL_FROM_AUTHORIZED_MEDIA_CATALOG",
              "type": "string"
            },
            {
              "id": "wanchuan-parameter-mode-video",
              "name": "parameter_mode",
              "value": "model_defaults",
              "type": "string"
            },
            {
              "id": "5ba1fd83-e304-49ed-9af4-d36e9960a386",
              "name": "prompt",
              "value": "保持原视频主体、动作连续性和画面结构，生成自然稳定的风格变化。",
              "type": "string"
            },
            {
              "id": "eb47d751-e135-489c-9b93-657f93d7fa85",
              "name": "duration",
              "value": 4,
              "type": "number"
            },
            {
              "id": "31afe5dc-64cd-4e27-b905-021c05f7fa21",
              "name": "resolution",
              "value": "480p",
              "type": "string"
            },
            {
              "id": "e57f3b5e-b0f7-4aaf-9d2f-ae75ad519c5e",
              "name": "aspect_ratio",
              "value": "auto",
              "type": "string"
            },
            {
              "id": "cbaea48b-af21-4420-92f6-7a01b586e586",
              "name": "idempotency_nonce",
              "value": "REPLACE_WITH_UNIQUE_RUN_NONCE_MIN_8_CHARS",
              "type": "string"
            },
            {
              "id": "924896c3-7fb8-431d-af8f-dc607aee643f",
              "name": "provider_processing_acknowledged",
              "value": false,
              "type": "boolean"
            },
            {
              "id": "4afde411-9be9-4936-9495-eddd5c1d6725",
              "name": "poll_limit",
              "value": 360,
              "type": "number"
            }
          ]
        },
        "options": {}
      },
      "id": "eeb1e350-c755-451a-91b8-140e441859d1",
      "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-video",
      "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 = 'video';\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-video",
      "name": "万川｜核对模型与参数",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -680,
        -120
      ]
    },
    {
      "parameters": {
        "operation": "read",
        "fileSelector": "={{ $node[\"万川｜运行配置\"].json.input_file }}",
        "options": {}
      },
      "id": "9dc2ccfc-c301-43ed-b8f6-ae61c540e7a0",
      "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 (contentType !== 'video/mp4') throw new Error('参考视频必须预先转换为 H.264 MP4，MIME type 必须是 video/mp4');\nif (!binary.id) throw new Error('大视频必须使用 n8n filesystem 或企业 external binary mode；本模板拒绝把 inline binary 全量载入内存');\nconst metadata = await this.helpers.getBinaryMetadata(binary.id);\nconst contentLength = Number(metadata?.fileSize);\nif (!Number.isSafeInteger(contentLength) || contentLength <= 0) throw new Error('无法从 n8n binary metadata 取得准确视频字节数');\nif (contentLength > 500 * 1024 * 1024) throw new Error('参考视频不能超过万川 500 MiB 接收上限');\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    aspect_ratio: String(config.aspect_ratio || '').trim(),\n    poll_limit: pollLimit\n  },\n  binary: item.binary\n}];"
      },
      "id": "e761057d-d204-4a2b-866c-e8e0a10382a6",
      "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-video-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": "2c6766b7-cc3f-40d7-b01a-c11c52cbce8d",
      "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-video-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": "dc23d364-a09e-4305-9363-d3d355033106",
      "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": "153a6218-b6a8-4659-a47f-3cdbf5dd9922",
      "name": "等待 5 秒",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        760,
        220
      ],
      "webhookId": "6bce63c9-0eb4-43d0-b4fc-aa59a585eb2d"
    },
    {
      "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": "f5cf6f32-d4b9-4a4c-b3cf-afbdb3c4bd08",
      "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": "e033e732-7739-417a-b4b8-5fa462c047a7",
      "name": "万川｜检查任务状态",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1240,
        220
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "e6b8b25a-335d-4f33-86c7-887090aed910",
              "leftValue": "={{ $json.done }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "8d025e2d-61a6-4f1a-817f-e08d66d0dc90",
      "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": "52055643-1e3c-4152-b11b-05dc4f215077",
      "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": "f839801f-f0fd-4311-b6de-048605475520",
  "meta": {
    "templateCredsSetupCompleted": false
  },
  "tags": []
}
