# 数字人 V3 合成 API 接入教程

> 在线效果体验：https://ai.hihookeji.com  
> 提交接口：`https://api.hihookeji.com/api/humanmetav3/index`  
> 查询接口：`https://api.hihookeji.com/api/queryclonehumanv3/index`  
> 返回格式：`application/json`  
> 请求方式：`HTTP POST`

---

## 一、接口概述

本接口将 **原视频** 与驱动 **音频（wav）** 合成数字人开口视频。提交后可通过 `notify_url` 回调拿结果，也可用 **查询接口** 按 `taskid` 轮询状态与成果地址。

| 项目 | 说明 |
|------|------|
| 提交接口 | `https://api.hihookeji.com/api/humanmetav3/index` |
| 查询接口 | `https://api.hihookeji.com/api/queryclonehumanv3/index` |
| 认证方式 | 请求体字段 `key`（你的 API 密钥，与业务参数一并提交） |
| Content-Type | `application/json`（推荐）或 `application/x-www-form-urlencoded` |
| 提交业务判断 | 提交响应以 **`code`** 为准：示例成功为 **`1`**，失败常见为 **`500`** |
| 查询业务判断 | 查询成功时 **`code` 为 `200`** |
| 计费 | **1 元 / 60 秒**，按秒计费 |

---

## 二、请求参数

### 2.1 提交合成（humanmetav3）

| 参数名 | 类型 | 必填 | 描述 | 示例 |
|--------|------|------|------|------|
| key | string | 是 | API 密钥，放在请求体中与其它参数一起提交 | `你的密钥` |
| video_url | string | 是 | 原视频链接（公网可访问），无需训练，直接传入即可驱动 | `https://abc.com/video.mp4` |
| audio_url | string | 是 | 驱动音频地址，须公网可访问，**wav 格式**，建议 URL 以 `.wav` 结尾 | `https://abc.com/audio.wav` |
| notify_url | string | 是 | 合成结果通知地址（需公网可达） | 见下方「回调测试」 |
| video_name | string | 否 | 视频名称 | `测试视频` |


**回调测试**：联调异步回调时，建议先打开 [https://webhook.site/](https://webhook.site/) 获取自己的唯一 URL，将该地址填入 `notify_url`。提交任务后可在网页上直接查看回调字段与成果链接，无需先部署自己的回调服务。

### 2.2 查询结果（queryclonehumanv3）

| 参数名 | 类型 | 必填 | 描述 | 示例 |
|--------|------|------|------|------|
| key | string | 是 | API 密钥，放在请求体中与其它参数一起提交 | `你的密钥` |
| taskid | string | 是 | 任务 ID；若查训练结果则传 `mode_id` | `374` |
| tasktype | string | 是 | `huamanclonev3`：查询合成结果；`huamanclonev1`：V1 查询 | `huamanclonev3` |

> `tasktype` 取值请按接口约定原样传递（含拼写 `huamanclonev3` / `huamanclonev1`）。新接入合成任务一般用 **`huamanclonev3`** + 提交返回的 `data.taskid`。  
> 合成不会马上完成：提交后请 **每隔 60 秒** 主动调一次本查询接口，直到 `status` 为 `3`（成功）或 `4`（失败），不要提交后立刻只查一次。

---

## 三、更新说明

### 2025-12-03

1. 新增 `video_url`：可直接传原视频链接，**无需训练**


---

## 四、返回结果说明

### 4.1 任务提交成功

立即返回任务受理结果，合成视频通过 `notify_url` 回调。

| 字段 | 说明 |
|------|------|
| code | 成功时示例为 `1` |
| msg | 说明信息，如 `ok` |
| time | 时间戳 |
| data.taskid | 视频任务 ID，与回调关联 |

```json
{
  "code": 1,
  "msg": "ok",
  "time": "1722863371",
  "data": {
    "taskid": 374
  }
}
```

### 4.2 任务提交失败

```json
{
  "code": 500,
  "msg": "不能为空"
}
```

### 4.3 异步回调 Notify

服务端合成完成后，向 `notify_url` **POST** 推送结果（表单字段；**不是** JSON Body）。

| 字段 | 说明 |
|------|------|
| errcode | **`0` 成功**；**`1001` 失败**（与提交响应的 `code` 不同，回调按此处判断） |
| taskid | 视频任务 ID |
| target_file | 成果视频链接（如 `https://1234.com/video.mp4`），请及时下载 |
| video_time | 视频时长，单位：秒 |
| msg | 通知信息 |

> 本接口回调的 `target_file` 为 **视频 URL 字符串**，不是文件流上传。收到后请尽快下载保存。

### 4.4 查询合成结果成功（tasktype=huamanclonev3）

查询成功时 `code` 为 **`200`**。

| data 字段 | 说明 |
|-----------|------|
| taskid | 任务 ID |
| status | 合成状态：`-1` 编辑中，`1` 排队中，`2` 合成中，`3` 合成成功，`4` 合成失败 |
| audio_url | 驱动音频地址 |
| mode_id | 模型 ID（历史字段，新流程以 `video_url` 提交为主） |
| result_video_url | 成果视频地址（成功时有值） |
| video_time | 视频时长（秒） |

```json
{
  "code": 200,
  "msg": "任务查询成功",
  "data": {
    "taskid": 123456,
    "status": 3,
    "audio_url": "https://oml.img.vodeshop.com/uploads/voice/20250224/20250224114152f79b25015.wav",
    "mode_id": 363546,
    "result_video_url": "https://opendigman.oss-cn-hangzhou.aliyuncs.com/aivideo/202502/24/20250224_1150261740369026196629.mp4",
    "video_time": 11.21
  }
}
```

当 `status=3` 时，用 `data.result_video_url` 下载成果视频即可。查询非即时有结果：建议 **每 60 秒轮询一次**，直到成功或失败。


---

## 五、接入流程建议

1. 准备公网可访问的原视频（`video_url`）与驱动音频（`audio_url`，标准 wav，URL 建议以 `.wav` 结尾）。
2. 申请 API Key，作为请求体字段 `key` 与其它参数一起提交。
3. 填入 `notify_url`；联调可用 [webhook.site](https://webhook.site/) 唯一地址。
4. 调用提交接口，成功后记录 `data.taskid`。
5. 结果获取二选一或并用：  
   - 等待 `notify_url` 回调（`errcode=0` 后下载 `target_file`）；  
   - 或主动轮询查询接口：`taskid` + `tasktype=huamanclonev3`，**每隔 60 秒查一次**，直到 `status=3` 再下载 `result_video_url`（`status=4` 则失败结束）。
6. 成果链接请及时下载落盘。

---

## 六、Python 完整代码

依赖：`requests`（`pip install requests`）

```python
# -*- coding: utf-8 -*-
"""
数字人 V3 合成 API - Python 完整示例
接口: https://api.hihookeji.com/api/humanmetav3/index
"""

import json
import os
import time

import requests

SUBMIT_URL = "https://api.hihookeji.com/api/humanmetav3/index"
QUERY_URL = "https://api.hihookeji.com/api/queryclonehumanv3/index"
API_KEY = "你的密钥"  # 替换为真实密钥


def create_human_video(
    video_url,
    audio_url,
    notify_url,
    video_name="测试视频",
    timeout=60,
):
    """提交数字人合成任务（异步）。"""
    payload = {
        "key": API_KEY,
        "video_url": video_url,
        "audio_url": audio_url,
        "notify_url": notify_url,
        "video_name": video_name,
    }
    resp = requests.post(
        SUBMIT_URL,
        json=payload,
        headers={"Content-Type": "application/json"},
        timeout=timeout,
    )
    resp.raise_for_status()
    return resp.json()


def query_human_video(taskid, tasktype="huamanclonev3", timeout=60):
    """
    查询任务结果。
    tasktype: huamanclonev3=查询合成结果；huamanclonev1=V1 查询
    """
    payload = {
        "key": API_KEY,
        "taskid": str(taskid),
        "tasktype": tasktype,
    }
    resp = requests.post(
        QUERY_URL,
        json=payload,
        headers={"Content-Type": "application/json"},
        timeout=timeout,
    )
    resp.raise_for_status()
    return resp.json()


def wait_human_video(taskid, interval_sec=60, max_tries=60, tasktype="huamanclonev3"):
    """
    每隔 interval_sec 秒查询一次，直到成功(status=3)、失败(status=4) 或超过次数。
    合成不会马上有结果，默认间隔 60 秒。
    """
    last = None
    for i in range(max_tries):
        last = query_human_video(taskid, tasktype=tasktype)
        data = last.get("data") or {}
        status = data.get("status")
        print(f"第 {i + 1} 次查询 status={status}:", json.dumps(last, ensure_ascii=False))
        if int(last.get("code") or 0) == 200 and status is not None:
            status = int(status)
            if status == 3:
                return last
            if status == 4:
                raise RuntimeError(f"合成失败: {last.get('msg')}")
        time.sleep(interval_sec)
    raise TimeoutError(f"超过轮询次数仍未完成，最后结果: {last}")


def download_result_video(video_url, save_path):
    """根据回调 target_file 或查询 result_video_url 下载成果视频。"""
    r = requests.get(video_url, timeout=120)
    r.raise_for_status()
    parent = os.path.dirname(save_path)
    if parent:
        os.makedirs(parent, exist_ok=True)
    with open(save_path, "wb") as f:
        f.write(r.content)
    return save_path


# ---------------------------------------------------------------------------
# Flask 异步回调接收示例（可选）
# 安装: pip install flask
# ---------------------------------------------------------------------------
def create_notify_app(save_dir="./callback_video"):
    from flask import Flask, request, jsonify

    app = Flask(__name__)
    os.makedirs(save_dir, exist_ok=True)

    @app.route("/Notify", methods=["POST"])
    def notify():
        # 回调为表单字段，不是 JSON Body
        taskid = request.form.get("taskid")
        msg = request.form.get("msg")
        errcode = request.form.get("errcode")  # 成功 0，失败 1001
        target_file = request.form.get("target_file") or ""
        video_time = request.form.get("video_time")

        if str(errcode) == "0" and target_file:
            path = os.path.join(save_dir, f"{taskid}.mp4")
            download_result_video(target_file, path)
            return jsonify({
                "ok": True,
                "taskid": taskid,
                "saved": path,
                "video_time": video_time,
                "errcode": errcode,
            })

        return jsonify({
            "ok": False,
            "taskid": taskid,
            "msg": msg,
            "errcode": errcode,
        })

    return app


if __name__ == "__main__":
    result = create_human_video(
        video_url="https://abc.com/video.mp4",
        audio_url="https://abc.com/audio.wav",
        notify_url="https://webhook.site/你的唯一ID",
        video_name="测试视频",
    )
    print("任务提交:", json.dumps(result, ensure_ascii=False, indent=2))
    # 成功时 code==1，记录 data.taskid

    taskid = (result.get("data") or {}).get("taskid")
    if taskid:
        # 不会马上有结果：默认每 60 秒查一次，直到 status=3/4
        query = wait_human_video(taskid, interval_sec=60)
        url = (query.get("data") or {}).get("result_video_url")
        if url:
            path = download_result_video(url, f"./output/{taskid}.mp4")
            print("已下载:", path)

    # ---------- 启动回调服务（需要时取消注释）----------
    # app = create_notify_app()
    # app.run(host="0.0.0.0", port=8080)
```

---

## 七、PHP 完整代码

需开启 `curl` 扩展。

```php
<?php
/**
 * 数字人 V3 合成 API - PHP 完整示例
 * 接口: https://api.hihookeji.com/api/humanmetav3/index
 */

define('SUBMIT_URL', 'https://api.hihookeji.com/api/humanmetav3/index');
define('QUERY_URL', 'https://api.hihookeji.com/api/queryclonehumanv3/index');
define('API_KEY', '你的密钥'); // 替换为真实密钥

/**
 * 通用 JSON POST
 */
function postJson($url, array $payload, $timeout = 60)
{
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_POST           => true,
        CURLOPT_HTTPHEADER     => ['Content-Type: application/json'],
        CURLOPT_POSTFIELDS     => json_encode($payload, JSON_UNESCAPED_UNICODE),
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => $timeout,
        CURLOPT_SSL_VERIFYPEER => true,
    ]);

    $body = curl_exec($ch);
    if ($body === false) {
        $err = curl_error($ch);
        curl_close($ch);
        throw new Exception('请求失败: ' . $err);
    }
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    $result = json_decode($body, true);
    if (!is_array($result)) {
        throw new Exception("响应非 JSON，HTTP={$httpCode}, body={$body}");
    }
    return $result;
}

/**
 * 提交数字人合成任务
 */
function createHumanVideo(array $params, $timeout = 60)
{
    return postJson(SUBMIT_URL, [
        'key'        => API_KEY,
        'video_url'  => $params['video_url'],
        'audio_url'  => $params['audio_url'],
        'notify_url' => $params['notify_url'],
        'video_name' => isset($params['video_name']) ? $params['video_name'] : '测试视频',
    ], $timeout);
}

/**
 * 查询任务结果
 * tasktype: huamanclonev3=合成结果；huamanclonev1=V1 查询
 */
function queryHumanVideo($taskid, $tasktype = 'huamanclonev3', $timeout = 60)
{
    return postJson(QUERY_URL, [
        'key'      => API_KEY,
        'taskid'   => (string)$taskid,
        'tasktype' => $tasktype,
    ], $timeout);
}

/**
 * 每隔 $intervalSec 秒查询一次，直到成功(status=3)或失败(status=4)
 * 合成不会马上有结果，默认间隔 60 秒
 */
function waitHumanVideo($taskid, $intervalSec = 60, $maxTries = 60, $tasktype = 'huamanclonev3')
{
    $last = null;
    for ($i = 0; $i < $maxTries; $i++) {
        $last = queryHumanVideo($taskid, $tasktype);
        $status = isset($last['data']['status']) ? (int)$last['data']['status'] : null;
        echo "第 " . ($i + 1) . " 次查询 status={$status}\n";
        if ((int)$last['code'] === 200 && $status !== null) {
            if ($status === 3) {
                return $last;
            }
            if ($status === 4) {
                throw new Exception('合成失败: ' . (isset($last['msg']) ? $last['msg'] : ''));
            }
        }
        sleep($intervalSec);
    }
    throw new Exception('超过轮询次数仍未完成');
}

/**
 * 下载回调中的成果视频
 */
function downloadResultVideo($videoUrl, $savePath)
{
    $ch = curl_init($videoUrl);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_TIMEOUT        => 120,
    ]);
    $bin = curl_exec($ch);
    if ($bin === false) {
        $err = curl_error($ch);
        curl_close($ch);
        throw new Exception('下载失败: ' . $err);
    }
    curl_close($ch);

    $dir = dirname($savePath);
    if (!is_dir($dir) && !mkdir($dir, 0755, true)) {
        throw new Exception('无法创建目录: ' . $dir);
    }
    if (file_put_contents($savePath, $bin) === false) {
        throw new Exception('写入失败: ' . $savePath);
    }
    return $savePath;
}

try {
    $result = createHumanVideo([
        'video_url'  => 'https://abc.com/video.mp4',
        'audio_url'  => 'https://abc.com/audio.wav',
        'notify_url' => 'https://webhook.site/你的唯一ID',
        'video_name' => '测试视频',
    ]);
    echo "任务提交:\n" . json_encode($result, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n";

    if (!empty($result['data']['taskid'])) {
        // 不会马上有结果：默认每 60 秒查一次，直到 status=3/4
        $query = waitHumanVideo($result['data']['taskid'], 60);
        echo "任务查询完成:\n" . json_encode($query, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n";
        if (!empty($query['data']['result_video_url'])) {
            $path = downloadResultVideo(
                $query['data']['result_video_url'],
                './output/' . $result['data']['taskid'] . '.mp4'
            );
            echo "已下载: {$path}\n";
        }
    }
} catch (Exception $e) {
    echo '错误: ' . $e->getMessage() . "\n";
}

/*
======================== notify.php 异步回调接收 ========================
将下方代码单独保存为 notify.php，部署后地址填入 notify_url。

<?php
header('Content-Type: application/json; charset=utf-8');

$saveDir = __DIR__ . '/callback_video';
if (!is_dir($saveDir)) {
    mkdir($saveDir, 0755, true);
}

$taskid      = isset($_POST['taskid']) ? $_POST['taskid'] : null;
$msg         = isset($_POST['msg']) ? $_POST['msg'] : null;
$errcode     = isset($_POST['errcode']) ? $_POST['errcode'] : null; // 成功 0，失败 1001
$targetFile  = isset($_POST['target_file']) ? $_POST['target_file'] : '';
$videoTime   = isset($_POST['video_time']) ? $_POST['video_time'] : null;

if ((string)$errcode === '0' && $targetFile !== '') {
    $path = $saveDir . '/' . ($taskid ?: uniqid('task_', true)) . '.mp4';
    $bin = file_get_contents($targetFile);
    if ($bin !== false) {
        file_put_contents($path, $bin);
        echo json_encode([
            'ok'         => true,
            'taskid'     => $taskid,
            'saved'      => $path,
            'video_time' => $videoTime,
            'errcode'    => $errcode,
        ], JSON_UNESCAPED_UNICODE);
        exit;
    }
}

echo json_encode([
    'ok'      => false,
    'taskid'  => $taskid,
    'msg'     => $msg,
    'errcode' => $errcode,
], JSON_UNESCAPED_UNICODE);
*/
```

---

## 八、Java 完整代码

依赖：JDK 8+，使用内置 `HttpURLConnection`，无需第三方库。

```java
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

/**
 * 数字人 V3 合成 API - Java 完整示例
 * 接口: https://api.hihookeji.com/api/humanmetav3/index
 */
public class HumanMetaV3Demo {

    private static final String SUBMIT_URL = "https://api.hihookeji.com/api/humanmetav3/index";
    private static final String QUERY_URL = "https://api.hihookeji.com/api/queryclonehumanv3/index";
    private static final String API_KEY = "你的密钥"; // 替换为真实密钥

    public static class HumanRequest {
        public String videoUrl;
        public String audioUrl;
        public String notifyUrl;
        public String videoName = "测试视频";
    }

    public static String createHumanVideo(HumanRequest req, int timeoutMs) throws IOException {
        return postJson(SUBMIT_URL, toSubmitJson(req), timeoutMs);
    }

    /** tasktype: huamanclonev3=合成结果；huamanclonev1=V1 查询 */
    public static String queryHumanVideo(String taskid, String tasktype, int timeoutMs) throws IOException {
        StringBuilder sb = new StringBuilder();
        sb.append("{");
        sb.append("\"key\":").append(quote(API_KEY)).append(",");
        sb.append("\"taskid\":").append(quote(taskid)).append(",");
        sb.append("\"tasktype\":").append(quote(tasktype));
        sb.append("}");
        return postJson(QUERY_URL, sb.toString(), timeoutMs);
    }

    private static String postJson(String apiUrl, String json, int timeoutMs) throws IOException {
        URL url = new URL(apiUrl);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod("POST");
        conn.setConnectTimeout(15000);
        conn.setReadTimeout(timeoutMs);
        conn.setDoOutput(true);
        conn.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
        conn.setRequestProperty("Accept", "application/json");

        byte[] body = json.getBytes(StandardCharsets.UTF_8);
        conn.setRequestProperty("Content-Length", String.valueOf(body.length));
        try (OutputStream os = conn.getOutputStream()) {
            os.write(body);
        }

        int httpCode = conn.getResponseCode();
        InputStream is = (httpCode >= 200 && httpCode < 300)
                ? conn.getInputStream()
                : conn.getErrorStream();
        String resp = readFully(is);
        conn.disconnect();
        return resp;
    }

    public static Path downloadResultVideo(String videoUrl, String savePath) throws IOException {
        URL url = new URL(videoUrl);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setConnectTimeout(15000);
        conn.setReadTimeout(120000);
        conn.setRequestMethod("GET");
        try (InputStream is = conn.getInputStream()) {
            byte[] bytes = readBytes(is);
            Path path = Paths.get(savePath);
            Path parent = path.getParent();
            if (parent != null && !Files.exists(parent)) {
                Files.createDirectories(parent);
            }
            Files.write(path, bytes);
            return path;
        } finally {
            conn.disconnect();
        }
    }

    private static String toSubmitJson(HumanRequest r) {
        StringBuilder sb = new StringBuilder();
        sb.append("{");
        sb.append("\"key\":").append(quote(API_KEY)).append(",");
        sb.append("\"video_url\":").append(quote(r.videoUrl)).append(",");
        sb.append("\"audio_url\":").append(quote(r.audioUrl)).append(",");
        sb.append("\"notify_url\":").append(quote(r.notifyUrl)).append(",");
        sb.append("\"video_name\":").append(quote(r.videoName));
        sb.append("}");
        return sb.toString();
    }

    private static String quote(String s) {
        if (s == null) {
            return "null";
        }
        String escaped = s
                .replace("\\", "\\\\")
                .replace("\"", "\\\"")
                .replace("\n", "\\n")
                .replace("\r", "\\r")
                .replace("\t", "\\t");
        return "\"" + escaped + "\"";
    }

    private static String readFully(InputStream is) throws IOException {
        return new String(readBytes(is), StandardCharsets.UTF_8);
    }

    private static byte[] readBytes(InputStream is) throws IOException {
        if (is == null) {
            return new byte[0];
        }
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        byte[] buf = new byte[4096];
        int n;
        while ((n = is.read(buf)) != -1) {
            bos.write(buf, 0, n);
        }
        return bos.toByteArray();
    }

    /*
     * 回调 Servlet 伪代码：
     * @WebServlet("/Notify")
     * 表单字段: errcode(成功0/失败1001), taskid, target_file(视频URL), video_time, msg
     * 成功时下载 target_file 保存为本地 mp4
     */

    public static void main(String[] args) throws Exception {
        HumanRequest req = new HumanRequest();
        req.videoUrl = "https://abc.com/video.mp4";
        req.audioUrl = "https://abc.com/audio.wav";
        req.notifyUrl = "https://webhook.site/你的唯一ID";
        req.videoName = "测试视频";

        String resp = createHumanVideo(req, 60000);
        System.out.println("任务提交: " + resp);
        // 成功时 code==1，记录 data.taskid
        // 合成非即时：每隔 60 秒调用 queryHumanVideo(taskid, "huamanclonev3", 60000)
        // 直到 status==3 取 result_video_url 下载；status==4 失败结束
    }
}
```

---

## 九、常见问题

| 问题 | 处理建议 |
|------|----------|
| 仍传 mode_id | 已废除；请改用 `video_url` 直传原视频，无需训练 |
| 音频格式不对 | `audio_url` 必须是公网可访问的 **wav**；失败可用 ffmpeg 转码：`ffmpeg -i 输入文件 -acodec pcm_s16le -ar 16000 输出.wav` |
| 音频 / 视频链接后缀不对 | 音频 URL 建议以 **`.wav` 结尾**；视频建议 **`.mp4` 结尾**。OSS 带鉴权的长链接不以该后缀结尾时易失败，需换成后缀正确的直链或转存后再提交 |
| 驱动音频无法访问 | 确认 `audio_url`、`video_url` 公网可下载，且无防盗链拦截服务端拉取 |
| 只看 HTTP 200 | 提交接口成功示例为 `code:1`；查询接口成功为 `code:200`，勿混用 |
| 查询一直排队/合成中 | 提交后不会马上有结果：建议 **每 60 秒查一次**；`status`：`1` 排队，`2` 合成中，`3` 成功，`4` 失败 |
| 提交后立刻查询无视频 | 正常现象；请按 60 秒间隔继续轮询，勿只查一次就判定失败 |
| tasktype 填错 | 查合成结果必须传 `huamanclonev3`（按接口拼写原样传） |
| 异步收不到回调 | 确认 `notify_url` 公网可达；也可用查询接口轮询；联调可用 [webhook.site](https://webhook.site/) |
| 回调当成 JSON 解析失败 | 回调是表单 POST，用 form/`$_POST` 取值，不要按 JSON Body 解析 |
| 回调成功却判失败 | 回调成功 `errcode` 为 **`0`**，失败为 `1001` |
| 成果链接失效 | 回调 `target_file` 或查询 `result_video_url`，收到后及时下载落盘 |
| 计费疑问 | 当前 **1 元 / 60 秒**，按秒计费 |

---

## 十、快速对照

| 项目 | 说明 |
|------|------|
| 提交 | `.../humanmetav3/index`：`key` / `video_url` / `audio_url` / `notify_url` |
| 提交响应 | `code:1` + `data.taskid` |
| 查询 | `.../queryclonehumanv3/index`：`key` / `taskid` / `tasktype=huamanclonev3` |
| 轮询 | 非即时结果，建议 **每 60 秒** 查一次，直到 `status=3/4` |
| 查询成功 | `code:200`，`status=3` 时取 `result_video_url` |
| 回调成果 | `notify_url`：`errcode=0` + `target_file`（视频 URL） |
