字幕水印一键全消

描述

提交视频进行一键清屏处理。算法异步执行,可通过任务查询接口获取处理结果。

版本

1.0

视频要求

仅支持通过 URL 传入视频。原文未明确视频编码、文件大小、分辨率或时长限制。

调用 URL

任务提交接口:

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

任务名称(task):/v1/Video_Remove/496462

任务类型(task_type):formula

调用方法

POST

Content-Type: application/json

权限

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

请求参数

是否必选参数名类型参数说明
是taskstring固定为 /v1/Video_Remove/496462。
是task_typestring固定为 formula。
是init_imagesobject[]输入视频列表,字段名保留为 init_images。
是paramsstring算法参数对象序列化后的 JSON 字符串。
否sync_timeoutinteger同步等待时长,单位为秒,默认值为 30。

init_images 元素

是否必选参数名类型参数说明
是urlstring输入视频 URL,不进行 Base64 编码。
是profileobject视频属性信息。

profile

是否必选参数名类型参数说明
是versionstring网关媒体配置版本,固定为 v1。
是media_profilesobject视频传输和处理属性。

media_profiles

是否必选参数名类型参数说明
是media_data_typestring本文按原文请求示例使用 url,表示输入为视频 URL。
否skip_framesarray[a,b]:开头跳过 a 帧,结尾跳过 b 帧,用于切片。默认值为 [0,0]。
否encoder_rate_scalenumber视频编码码率膨胀系数。原文未说明默认值或取值范围。

params

params 必须为 JSON 字符串。解码后包含以下字段:

是否必选参数名类型参数说明
是parameterobject一键清屏算法参数。
否extraobject算法协议中的额外处理信息,无需传入时可省略。

parameter

是否必选参数名类型参数说明
否userboxesstring用户交互区域框的坐标字符串。每个框使用四个相对坐标点,顺序为左上、右上、右下、左下;默认无。
否protectboxesstring保护区域框的坐标字符串。每个框的坐标顺序同 userboxes;默认无。

单个框的字符串值示例:

[[0.1,0.1],[0.2,0.1],[0.2,0.2],[0.1,0.2]]

需要传入区域框时,params 解码后的对象示例如下。注意 userboxes 和 protectboxes 仍为字符串,不是数组:

{
  "parameter": {
    "userboxes": "[[0.1,0.1],[0.2,0.1],[0.2,0.2],[0.1,0.2]]",
    "protectboxes": "[[0.6,0.6],[0.8,0.6],[0.8,0.8],[0.6,0.8]]"
  }
}

原文说明可传多个框,但未明确多个框之间的分组结构,本文不扩展多框示例。原文参数表中的输入媒体类型为 MP4,与其请求示例的 url 不一致;本文输入采用原文示例的 URL 方式。

输入值示例

以下示例通过 userboxes 指定一个交互区域框,不设置保护框。请按需替换视频 URL 和区域坐标。

{
  "task": "/v1/Video_Remove/496462",
  "task_type": "formula",
  "init_images": [
    {
      "url": "https://example.com/input.mp4",
      "profile": {
        "media_profiles": {
          "media_data_type": "url",
          "skip_frames": [
            0,
            0
          ]
        },
        "version": "v1"
      }
    }
  ],
  "params": "{\"parameter\":{\"userboxes\":\"[[0.1,0.1],[0.2,0.1],[0.2,0.2],[0.1,0.2]]\"}}",
  "sync_timeout": 30
}

返回值说明

以下采用统一网关的任务封装说明;本 task 的实际结果封装层级尚未通过响应样本核验。原文明确的算法结果字段单独列于下文。

字段类型说明
codeinteger网关业务状态码。0 表示请求处理正常,不代表任务已经完成。
messagestring网关响应信息。
dataobject任务状态和结果。
data.task_idstring网关任务 ID,用于任务查询。
data.statusinteger任务状态,见下表。
data.resultobject结果封装;下文结构示例将算法结果放在 data.result.data。
data.status说明
-1未找到任务。
0任务创建成功。
1执行中。
2失败。
9同步等待超时,继续查询,不代表算法任务失败。
10成功。

result

字段类型说明
idstring网关任务标识。
codeinteger算法状态码。
msgstring算法响应消息。
msg_idstring算法请求标识,不用于替代网关 task_id。
dataobject算法原始响应。

算法结果字段

字段类型说明
msg_idstring算法请求标识。原文说明该标识及算法结果保留 24 小时,之后删除。
media_info_listobject[]处理结果列表。
media_info_list[].media_datastring结果媒体数据;本文 URL 结构示例中为视频地址。
media_info_list[].media_profilesobject结果媒体属性。
parameterobject输出类型、算法版本及消除状态。
parameter.rsp_media_typestring输出方式;本文结构示例使用 url。
parameter.versionstring算法版本号。
parameter.exist_textboolean是否对视频进行了消除。未检测到待消除内容时,不执行消除。
error_codeinteger原文失败响应中的算法错误码。
error_msgstring原文失败响应中的算法错误信息。

原文返回字段说明仍包含 jpg / Base64 表述,没有提供完整的视频成功响应 JSON;本文不据此声明支持 Base64 视频输出。

网关查询使用 data.task_id,不是算法 msg_id。请及时查询并保存结果;原文的 24 小时说明针对算法标识及结果,不是本 task 的网关留存承诺。

返回值示例

以下为根据统一网关模板和原文算法字段整理的结构示例,非实际调用记录。响应层级、具体值及错误信息需以该 task 的实际响应为准。

成功结构示例

{
  "code": 0,
  "message": "success",
  "data": {
    "status": 10,
    "task_id": "example-task-id",
    "result": {
      "id": "example-task-id",
      "code": 0,
      "msg": "success",
      "msg_id": "example-message-id",
      "data": {
        "msg_id": "example-message-id",
        "media_info_list": [
          {
            "media_data": "https://example.com/result.mp4",
            "media_profiles": {
              "media_data_type": "url"
            }
          }
        ],
        "parameter": {
          "rsp_media_type": "url",
          "version": "1.0",
          "exist_text": true
        }
      }
    }
  }
}

需要查询结构示例

当 data.status = 9 时,使用 data.task_id 调用任务查询接口,不要因同步等待超时重复提交任务。

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

失败结构示例

以下以 20001 / ALGO_MODEL_CRASH 说明失败结构,不表示所有失败都返回该错误。

{
  "code": 20001,
  "message": "ALGO_MODEL_CRASH",
  "data": {
    "status": 2,
    "task_id": "example-task-id",
    "result": {
      "id": "example-task-id",
      "code": 20001,
      "msg": "ALGO_MODEL_CRASH",
      "msg_id": "example-message-id",
      "data": {
        "error_code": 20001,
        "error_msg": "ALGO_MODEL_CRASH",
        "msg_id": "example-message-id"
      }
    }
  }
}

通用的错误代码与信息

详见 API 错误码。

SDK 调用示例

以下示例内嵌完整请求体,无需额外 JSON 文件。按对应文档引入签名 SDK 后,替换 AK、SK 和视频 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"
    params = {
        "parameter": {
            "userboxes": "[[0.1,0.1],[0.2,0.1],[0.2,0.2],[0.1,0.2]]"
        }
    }
    payload = {
        "task": "/v1/Video_Remove/496462",
        "task_type": "formula",
        "init_images": [
            {
                "url": "https://example.com/input.mp4",
                "profile": {
                    "media_profiles": {
                        "media_data_type": "url",
                        "skip_frames": [
                            0,
                            0
                        ]
                    },
                    "version": "v1"
                }
            }
        ],
        "params": json.dumps(params, ensure_ascii=True),
        "sync_timeout": 30
    }
    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/mtlab/api/signer"
)

func main() {
    body := `{
  "task": "/v1/Video_Remove/496462",
  "task_type": "formula",
  "init_images": [
    {
      "url": "https://example.com/input.mp4",
      "profile": {
        "media_profiles": {
          "media_data_type": "url",
          "skip_frames": [
            0,
            0
          ]
        },
        "version": "v1"
      }
    }
  ],
  "params": "{\"parameter\":{\"userboxes\":\"[[0.1,0.1],[0.2,0.1],[0.2,0.2],[0.1,0.2]]\"}}",
  "sync_timeout": 30
}`
    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';

$params = json_encode([
    'parameter' => [
        'userboxes' => '[[0.1,0.1],[0.2,0.1],[0.2,0.2],[0.1,0.2]]',
    ],
]);
$body = json_encode([
    'task' => '/v1/Video_Remove/496462',
    'task_type' => 'formula',
    'init_images' => [
        [
            'url' => 'https://example.com/input.mp4',
            'profile' => [
                'media_profiles' => [
                    'media_data_type' => 'url',
                    'skip_frames' => [0, 0],
                ],
                'version' => 'v1',
            ],
        ],
    ],
    'params' => $params,
    'sync_timeout' => 30,
], 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/Video_Remove/496462\",\n" +
                "  \"task_type\": \"formula\",\n" +
                "  \"init_images\": [\n" +
                "    {\n" +
                "      \"url\": \"https://example.com/input.mp4\",\n" +
                "      \"profile\": {\n" +
                "        \"media_profiles\": {\n" +
                "          \"media_data_type\": \"url\",\n" +
                "          \"skip_frames\": [\n" +
                "            0,\n" +
                "            0\n" +
                "          ]\n" +
                "        },\n" +
                "        \"version\": \"v1\"\n" +
                "      }\n" +
                "    }\n" +
                "  ],\n" +
                "  \"params\": \"{\\\"parameter\\\":{\\\"userboxes\\\":\\\"[[0.1,0.1],[0.2,0.1],[0.2,0.2],[0.1,0.2]]\\\"}}\",\n" +
                "  \"sync_timeout\": 30\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();
        }
    }
}