双人合影

描述

传入两张图片,调用双人合影算法,返回处理后的合影图片。接口默认异步处理,提交后使用任务 ID 查询结果。

版本

1.0

图片要求

每次请求传入两张图片,通过 URL 提供。

调用 URL

正式环境:https://openapi.meitu.com

任务提交接口:

https://openapi.meitu.com/api/v1/sdk/sync/push

任务名称(task):/v1/couples_photo/default/488650

任务类型(task_type):formula

调用方法

POST

Content-Type: application/json

权限

使用已开通该能力的 AK/SK,通过签名 SDK 生成请求签名。详见开放平台接口签名。

请求参数

是否必选参数名类型参数说明
是taskstring固定为 /v1/couples_photo/default/488650。
是task_typestring固定为 formula。
是init_imagesobject[]两张输入图片。
是paramsstring推理参数;传入空字符串 "" 使用默认参数。
否sync_timeoutinteger默认 -1,不等待生成完成,立即返回任务信息,后续通过任务查询接口获取结果。

init_images 元素

是否必选参数名类型参数说明
是urlstring可访问的图片 URL。
否profileobject图片属性配置。

profile

传入 profile 时,包含以下字段:

是否必选参数名类型参数说明
是media_profilesobject媒体属性。

media_profiles

是否必选参数名类型参数说明
否media_data_typestring默认 url,表示通过 URL 传入图片。

params

本接口支持 params: "" 表示使用默认参数。保持空字符串,不替换为对象、null 或字符串 "{}"。原文未列出可配置的推理参数。

输入值示例

将两张图片的 URL 替换为实际地址:

{
  "task": "/v1/couples_photo/default/488650",
  "task_type": "formula",
  "init_images": [
    {
      "url": "https://example.com/person-1.png",
      "profile": {
        "media_profiles": {
          "media_data_type": "url"
        }
      }
    },
    {
      "url": "https://example.com/person-2.png",
      "profile": {
        "media_profiles": {
          "media_data_type": "url"
        }
      }
    }
  ],
  "params": "",
  "sync_timeout": -1
}

返回值说明

code=0、error_code=0 表示请求处理正常,不代表任务已经完成。检查 data.status:为 10 时读取 data.result.urls;为 9 时继续查询任务。请及时下载保存结果图片。

字段类型说明
request_id / trace_idstring请求 ID、链路追踪 ID。
codeinteger响应中的状态码,0 表示请求处理正常;不是 HTTP 状态码。
error_codeinteger错误码,0 表示无错误。
messagestring响应消息,成功示例为 success。
dataobject / null任务详情,失败时可能为 null。

data

字段类型说明
statusinteger-1 未找到;0 已创建;1 执行中;2 失败;9 需要查询;10 成功。
resultobject任务处理结果。
progressnumber任务进度,范围 0–1,1 表示完成。
predict_elapsednumber预计处理耗时,单位为毫秒。
create_timenumber任务创建时间戳,单位为毫秒。
task_idstring查询使用的网关任务 ID。
custom_task_id / trace_id / client_infostring自定义任务 ID、追踪 ID 和客户端信息。
init_imagesarray / null输入媒体信息,成功示例中为 null。

result

字段类型说明
idstring任务标识,与示例中的 data.task_id 一致。
urls / imagesstring[]结果图片 URL 列表;示例中两者相同。
parametersobject输出参数;可包含 rsp_media_type,url 表示返回 URL,示例为空对象。
dataobject算法返回的详细数据。
msg / msg_idstring算法消息及消息 ID;msg_id 不是任务查询 ID。
mtlab_resobject底层算法兼容结果。
media_info_listobject[]输出媒体信息列表。

result.data

字段类型说明
durationobject各阶段时间和耗时信息,见下表。
error_code / error_msginteger / string算法错误码和消息,error_code=0 表示成功。
extraobject扩展信息。
media_info_listobject[]结果媒体列表。
msg_idstring算法消息 ID。
parameterobject参数回显。

result.data.duration

字段类型说明
alg_process_timenumber算法处理耗时,毫秒。
created_timestamp / pull_timestampnumber创建及资源拉取时间戳,秒。
upload_time / waiting_timenumber上传及排队等待耗时,毫秒。
repost_timenumber原文标为重试耗时,未说明单位。

media_info_list 元素

适用于 data.result.media_info_list 及 data.result.data.media_info_list。

字段类型说明
media_datastring结果图片 URL。
media_extraobject媒体扩展信息。
media_profilesobject媒体属性。
media_profiles.media_data_typestringurl 表示 media_data 为 URL 地址。

result.mtlab_res

字段类型说明
ErrorCode / error_codeinteger底层错误码及兼容字段。
ErrorMsg / error_msgstring底层错误消息及兼容字段。
media_info_listarray / null媒体信息,成功示例中为 null。
msg_idstring算法消息 ID。
parameterobject / null底层参数,成功示例中为 null。

返回值示例

成功及失败结构来自原文,任务 ID、算法消息 ID 和图片 URL 已替换为示意值,非本次实际调用结果。成功示例展示任务完成后的结果,不表示默认异步提交会立即返回成品图片。

请求成功返回示例

{
  "request_id": "",
  "trace_id": "",
  "code": 0,
  "error_code": 0,
  "message": "success",
  "data": {
    "status": 10,
    "result": {
      "id": "example-task-id",
      "urls": [
        "https://example.com/couples-photo.png"
      ],
      "parameters": {},
      "data": {
        "duration": {
          "alg_process_time": 2396,
          "created_timestamp": 1775548648,
          "pull_timestamp": 1775548648,
          "repost_time": 0,
          "upload_time": 74,
          "waiting_time": 58
        },
        "error_code": 0,
        "error_msg": "success",
        "extra": {},
        "media_info_list": [
          {
            "media_data": "https://example.com/couples-photo.png",
            "media_extra": {},
            "media_profiles": {
              "media_data_type": "url"
            }
          }
        ],
        "msg_id": "example-algorithm-msg-id",
        "parameter": {}
      },
      "msg": "success",
      "msg_id": "example-algorithm-msg-id",
      "images": [
        "https://example.com/couples-photo.png"
      ],
      "mtlab_res": {
        "ErrorCode": 0,
        "ErrorMsg": "",
        "error_code": 0,
        "error_msg": "",
        "media_info_list": null,
        "msg_id": "example-algorithm-msg-id",
        "parameter": null
      },
      "media_info_list": [
        {
          "media_data": "https://example.com/couples-photo.png",
          "media_extra": {},
          "media_profiles": {
            "media_data_type": "url"
          }
        }
      ]
    },
    "progress": 1,
    "predict_elapsed": 10000,
    "create_time": 1775548616543,
    "task_id": "example-task-id",
    "custom_task_id": "",
    "trace_id": "",
    "client_info": "",
    "init_images": null
  }
}

需要查询返回示例

以下为待查询状态的精简结构示意。异步提交或同步等待未完成时,保存 data.task_id,按任务查询接口继续查询,不要重复提交,也不要使用 msg_id 查询。

{
  "request_id": "",
  "trace_id": "",
  "code": 0,
  "error_code": 0,
  "message": "success",
  "data": {
    "status": 9,
    "result": {
      "id": "example-task-id"
    },
    "progress": 0,
    "task_id": "example-task-id"
  }
}

请求失败返回示例

原文失败示例为 HTTP 400:

{
  "error_code": 20001,
  "message": "PROCESS_ERROR",
  "data": null
}

通用的错误代码与信息

20001 / PROCESS_ERROR 表示处理错误。其他错误详见 API 错误码。

SDK 调用示例

以下示例内嵌完整请求体,无需额外 JSON 文件。按对应文档引入签名 SDK 后,替换 AK、SK 和两张图片的 URL。所有示例保留 params="" 和 sync_timeout=-1。

示例提交一次并打印响应;任务未完成时通过任务查询接口获取结果。签名和发送使用相同的 URL、方法和请求体。

Python

Python 签名 SDK 接入文档

import json

import requests
from sign_sdk import sign

def main():
    key = "YOUR_ACCESS_KEY"
    secret = "YOUR_SECRET_KEY"
    url = "https://openapi.meitu.com/api/v1/sdk/sync/push"
    payload = {
        "task": "/v1/couples_photo/default/488650",
        "task_type": "formula",
        "init_images": [
            {
                "url": "https://example.com/person-1.png",
                "profile": {
                    "media_profiles": {
                        "media_data_type": "url"
                    }
                }
            },
            {
                "url": "https://example.com/person-2.png",
                "profile": {
                    "media_profiles": {
                        "media_data_type": "url"
                    }
                }
            }
        ],
        "params": "",
        "sync_timeout": -1
    }
    body = json.dumps(payload, ensure_ascii=True)
    headers = {
        "Content-Type": "application/json",
        sign.HeaderHost: "openapi.meitu.com",
    }
    signer = sign.Signer(key, secret)
    signed_request = signer.sign(url, "POST", headers, body)
    with requests.Session() as session:
        response = session.send(
            signed_request, timeout=(10, 60), verify=True, allow_redirects=False
        )
        print("Status:", response.status_code)
        print("Response:", response.text)

if __name__ == "__main__":
    main()

Go

Go 签名 SDK 接入文档

package main

import (
    "fmt"
    "io"
    "net/http"
    "time"

    "github.com/formula/api/signer"
)

func main() {
    body := `{
  "task": "/v1/couples_photo/default/488650",
  "task_type": "formula",
  "init_images": [
    {
      "url": "https://example.com/person-1.png",
      "profile": {
        "media_profiles": {
          "media_data_type": "url"
        }
      }
    },
    {
      "url": "https://example.com/person-2.png",
      "profile": {
        "media_profiles": {
          "media_data_type": "url"
        }
      }
    }
  ],
  "params": "",
  "sync_timeout": -1
}`
    url := "https://openapi.meitu.com/api/v1/sdk/sync/push"
    headers := make(http.Header)
    headers.Set("Content-Type", "application/json")
    headers.Set(signer.HeaderHost, "openapi.meitu.com")
    signObj := signer.NewSigner("YOUR_ACCESS_KEY", "YOUR_SECRET_KEY")
    req, err := signObj.Sign(url, http.MethodPost, headers, body)
    if err != nil {
        fmt.Println("Sign request failed:", err)
        return
    }
    client := &http.Client{
        Timeout: 60 * time.Second,
        CheckRedirect: func(req *http.Request, via []*http.Request) error {
            return http.ErrUseLastResponse
        },
    }
    resp, err := client.Do(req)
    if err != nil {
        fmt.Println("Send request failed:", err)
        return
    }
    defer resp.Body.Close()
    responseBody, err := io.ReadAll(resp.Body)
    if err != nil {
        fmt.Println("Read response failed:", err)
        return
    }
    fmt.Println("Status:", resp.StatusCode)
    fmt.Println("Response:", string(responseBody))
}

PHP

PHP 签名 SDK 接入文档

<?php
require_once __DIR__ . '/signer.php';

$body = json_encode([
    'task' => '/v1/couples_photo/default/488650',
    'task_type' => 'formula',
    'init_images' => [
        [
            'url' => 'https://example.com/person-1.png',
            'profile' => [
                'media_profiles' => [
                    'media_data_type' => 'url'
                ]
            ]
        ],
        [
            'url' => 'https://example.com/person-2.png',
            'profile' => [
                'media_profiles' => [
                    'media_data_type' => 'url'
                ]
            ]
        ]
    ],
    'params' => '',
    'sync_timeout' => -1
], JSON_UNESCAPED_SLASHES);
$url = 'https://openapi.meitu.com/api/v1/sdk/sync/push';
$headers = [
    'Content-Type' => 'application/json',
    'Host' => 'openapi.meitu.com',
];
$signer = new Signer('YOUR_ACCESS_KEY', 'YOUR_SECRET_KEY');
$curl = $signer->sign($url, 'POST', $headers, $body);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_HEADER, false);
curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 10);
curl_setopt($curl, CURLOPT_TIMEOUT, 60);
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, false);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2);
$response = curl_exec($curl);
if ($response === false) {
    echo 'Error: ' . curl_error($curl) . PHP_EOL;
} else {
    echo 'Status: ' . curl_getinfo($curl, CURLINFO_HTTP_CODE) . PHP_EOL;
    echo 'Response: ' . $response . PHP_EOL;
}
curl_close($curl);

Java

Java 签名 SDK 接入文档

import com.meitu.openai.common.Signer;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) throws Exception {
        String body =
                "{\n" +
                "  \"task\": \"/v1/couples_photo/default/488650\",\n" +
                "  \"task_type\": \"formula\",\n" +
                "  \"init_images\": [\n" +
                "    {\n" +
                "      \"url\": \"https://example.com/person-1.png\",\n" +
                "      \"profile\": {\n" +
                "        \"media_profiles\": {\n" +
                "          \"media_data_type\": \"url\"\n" +
                "        }\n" +
                "      }\n" +
                "    },\n" +
                "    {\n" +
                "      \"url\": \"https://example.com/person-2.png\",\n" +
                "      \"profile\": {\n" +
                "        \"media_profiles\": {\n" +
                "          \"media_data_type\": \"url\"\n" +
                "        }\n" +
                "      }\n" +
                "    }\n" +
                "  ],\n" +
                "  \"params\": \"\",\n" +
                "  \"sync_timeout\": -1\n" +
                "}";
        String url = "https://openapi.meitu.com/api/v1/sdk/sync/push";
        Map<String, String> headers = new HashMap<>();
        headers.put("Content-Type", "application/json");
        headers.put(Signer.HeaderHost, "openapi.meitu.com");
        Signer signer = new Signer("YOUR_ACCESS_KEY", "YOUR_SECRET_KEY");
        Map<String, String> signedHeaders = signer.sign(url, "POST", headers, body);

        HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
        try {
            connection.setRequestMethod("POST");
            connection.setConnectTimeout(10000);
            connection.setReadTimeout(60000);
            connection.setInstanceFollowRedirects(false);
            connection.setDoOutput(true);
            for (Map.Entry<String, String> entry : signedHeaders.entrySet()) {
                connection.setRequestProperty(entry.getKey(), entry.getValue());
            }
            byte[] bodyBytes = body.getBytes(StandardCharsets.UTF_8);
            connection.setFixedLengthStreamingMode(bodyBytes.length);
            try (OutputStream output = connection.getOutputStream()) {
                output.write(bodyBytes);
            }
            int status = connection.getResponseCode();
            System.out.println("Status: " + status);
            InputStream responseStream = status >= 400
                    ? connection.getErrorStream() : connection.getInputStream();
            if (responseStream != null) {
                try (InputStream input = responseStream;
                     ByteArrayOutputStream output = new ByteArrayOutputStream()) {
                    byte[] buffer = new byte[4096];
                    int length;
                    while ((length = input.read(buffer)) != -1) {
                        output.write(buffer, 0, length);
                    }
                    System.out.println("Response: " + output.toString("UTF-8"));
                }
            }
        } finally {
            connection.disconnect();
        }
    }
}