You need to enable JavaScript to run this app.
导航

音视频字幕生成

最近更新时间2023.09.06 17:53:12

首次发布时间2021.12.20 14:44:12

1. 流程简介

视频字幕功能整体处理流程分为三个阶段:

  1. 客户端抽取视频中音轨,转成音频文件;
  2. 把音频文件发送至后端集群,获取任务 ID;
  3. 通过任务 ID 访问后端接口获取结果。

非阻塞查询流程

alt

阻塞查询流程

alt

2. 鉴权

设置鉴权内容,请参考鉴权方法

3. 提交音频

3.1 请求

请求地址:https://openspeech.bytedance.com/api/v1/vc/submit

请求方式:HTTP POST

3.1.1 Url 参数

字段
说明
是否必填
备注
appid应用标识用于标识当前应用。
words_per_line每行最多展示字数默认值 46 。
max_lines每屏最多展示行数默认 1 行。
use_itn是否使用数字转换功能默认关闭(False)。
如果设置为开启(True),会将识别结果中的中文数字自动转成阿拉伯数字。
language字幕语言类型支持语种
caption_type字幕识别类型默认值为auto(同时识别说话和唱歌部分) 。
可以选择speech(只识别说话部分),
可以选择singing(只识别唱歌部分)。
use_punc增加标点默认False, 如果设置为True,则会将识别结果中增加标点符号。
当且仅当(caption_type=speech的时候生效)
use_ddc使用顺滑标注水词默认 False,如果设置为 True,则会在返回的 utterances 里增加 text 为空的静音句子,其 attribute 的 event 是 silent。且 words 中可能需要被顺滑的词会被标注出来,如"extra": { "smoothed": "repeat" },smoothed 的值可能为 repeat(重复词)或 filler(口水词)。
boosting_table_id自学习平台热词 IDid 与 name 二选一,只需要提供其中一个即可。同时需要传 asr_appid(与 appid 值一样)
boosting_table_name自学习平台热词的文件名称
asr_appid传给 ASR 的 APPID使用自学习平台热词时必填,与 appid 值一致即可。
with_speaker_info返回说话人信息默认 False,如果设置为 True,则会在 utterance 和 workd 的 attribute 中增加 speaker 信息如"attribute": {"speaker": "1"}

3.1.2 支持语种

语音字幕

序号语言Language Code分句长度推荐值

1

中文普通话(简体)
支持中英混合及以下方言

zh-CN

15

粤语yue15
吴语-上海话wuu15
闽南语nan15
西南官话xghu15
中原官话zgyu15
2维语ug55
3英语(美国)en-US55
4日语ja-JP32
5韩语ko-KR32
6西班牙语es-MX55
7俄语ru-RU55
8法语fr-FR55

歌词字幕

序号语言Language Code分句长度推荐值

1

中文普通话(简体)
支持中英粤混合

zh-CN

15

2英语(美国)en-US55

3.1.3 音频二进制请求方式

Header 需要加入内容类型标识(匹配 audio/* 都可以):

Content-Type: audio/wav

Body 直接传输音频二进制数据

请求示例:

POST /api/v1/vc/submit?appid=test&words_per_line=20&max_lines=2 HTTP/1.1
Host: openspeech.bytedance.com
Content-Type: audio/wav
Connection: keep-alive
Content-Length: xxxxx

[[wav binary data]]

3.1.4 音频地址请求方式

Header 需要加入内容类型标识:

Content-Type: application/json

Body 为 JSON 格式字符串,参数如下所示:
字段
说明
是否必填
备注
url音频地址用于标识当前应用。

请求示例:

POST /api/v1/vc/submit?appid=test&token=tmp_token&words_per_line=20&max_lines=2 HTTP/1.1
Host: openspeech.bytedance.com
Content-Type: application/json
Connection: keep-alive
Content-Length: xxxxx

{"url":"http://xxx.xxx.com/obj/video-caption/phone.mp3"}

3.2 应答

应答格式: JSON

应答字段:

字段
说明
层级
格式
是否必填
备注
code状态码1int0 为成功,非 0 为失败。
message状态信息1string失败时标记失败原因。
id任务 ID1string仅当提交成功时填写。

应答示例:

{
    "code": "0",
    "message": "Success",
    "id": "fc5aa03e-6ae4-46a3-b8cf-1910a44e0d8a"
}

4. 查询结果

4.1 请求

请求地址:https://openspeech.bytedance.com/api/v1/vc/query

请求方式:HTTP GET

字段
说明
是否必填
备注
appid应用标识用于标识当前应用。
id任务 ID这里填写的是submit接口返回的id。
blocking查询结果时是否阻塞0表示非阻塞,1表示阻塞(默认是阻塞模式)。

请求示例:

GET /api/v1/vc/query?appid=lv&id=fc5aa03e-6ae4-46a3-b8cf-1910a44e0d8a HTTP/1.1
Host: openspeech.bytedance.com

4.2 应答

应答格式:JSON

应答字段:

字段
说明
层级
格式
是否必填
备注
id任务 ID1string
code状态码1int0 为成功,非 0 为失败。详情请参考错误码
message状态信息1string
utterances分句结果1list仅当成功时填写。
start_time起始时间2int距离音频开始的毫秒偏移值。
end_time结束时间2int距离音频开始的毫秒偏移值。
text文本2string
words词粒度信息2list

应答示例:

{
    "id": "d22cca84-8c8a-4d15-aa2c-ac550518d5ae",
    "code": 0,
    "message": "Success",
    "duration": 5.3174375,
    "utterances": [
        {
            "text": "如果您没有其他需要举报的话这边就先挂断了",
            "start_time": 0,
            "end_time": 3197,
            "words": [
                {
                    "text": "如",
                    "start_time": 0,
                    "end_time": 208
                },
                {
                    "text": "果",
                    "start_time": 208,
                    "end_time": 317
                },
                {
                    "text": "您",
                    "start_time": 322,
                    "end_time": 460
                },
                {
                    "text": "没",
                    "start_time": 460,
                    "end_time": 580
                },
                {
                    "text": "有",
                    "start_time": 580,
                    "end_time": 717
                },
                {
                    "text": "其",
                    "start_time": 722,
                    "end_time": 877
                },
                {
                    "text": "他",
                    "start_time": 882,
                    "end_time": 1037
                },
                {
                    "text": "需",
                    "start_time": 1042,
                    "end_time": 1180
                },
                {
                    "text": "要",
                    "start_time": 1180,
                    "end_time": 1317
                },
                {
                    "text": "举",
                    "start_time": 1322,
                    "end_time": 1477
                },
                {
                    "text": "报",
                    "start_time": 1482,
                    "end_time": 1637
                },
                {
                    "text": "的",
                    "start_time": 1642,
                    "end_time": 1780
                },
                {
                    "text": "话",
                    "start_time": 1780,
                    "end_time": 1917
                },
                {
                    "text": "这",
                    "start_time": 2042,
                    "end_time": 2180
                },
                {
                    "text": "边",
                    "start_time": 2180,
                    "end_time": 2317
                },
                {
                    "text": "就",
                    "start_time": 2322,
                    "end_time": 2460
                },
                {
                    "text": "先",
                    "start_time": 2460,
                    "end_time": 2597
                },
                {
                    "text": "挂",
                    "start_time": 2602,
                    "end_time": 2757
                },
                {
                    "text": "断",
                    "start_time": 2802,
                    "end_time": 2957
                },
                {
                    "text": "了",
                    "start_time": 3042,
                    "end_time": 3197
                }
            ]
        },
        {
            "text": "祝您生活愉快再见",
            "start_time": 3442,
            "end_time": 4877,
            "words": [
                {
                    "text": "祝",
                    "start_time": 3442,
                    "end_time": 3580
                },
                {
                    "text": "您",
                    "start_time": 3580,
                    "end_time": 3717
                },
                {
                    "text": "生",
                    "start_time": 3722,
                    "end_time": 3877
                },
                {
                    "text": "活",
                    "start_time": 3882,
                    "end_time": 4020
                },
                {
                    "text": "愉",
                    "start_time": 4020,
                    "end_time": 4157
                },
                {
                    "text": "快",
                    "start_time": 4162,
                    "end_time": 4317
                },
                {
                    "text": "再",
                    "start_time": 4562,
                    "end_time": 4717
                },
                {
                    "text": "见",
                    "start_time": 4722,
                    "end_time": 4877
                }
            ]
        }
    ]
}

5. 反馈结果

5.1 请求

请求地址:https://openspeech.bytedance.com/api/v1/vc/feedback

请求方式:HTTP POST

Header 需要加入内容类型标识:

Content-Type: application/json

Url 参数:

字段
说明
是否必填
备注
appid应用标识用于标识当前应用。
id任务 ID

Body 参数:

字段
说明
层级
格式
是否必填
备注
video_url视频URL1string视频链接。
utterances分句结果1list
start_time起始时间2int
end_time结束时间2int
text文本2string

请求示例:

POST /api/v1/vc/feedback?appid=lv&id=fc5aa03e-6ae4-46a3-b8cf-1910a44e0d8a HTTP/1.1
Host: openspeech.bytedance.com
Content-Type: application/json
Connection: keep-alive
Content-Length: xxxxx

{"utterances":[{"text":"这里是字节跳动","start_time":1500,"end_time":3000},{"text":"今日头条母公司","start_time":4000,"end_time":6350}]}

5.2 应答

应答格式: JSON

应答字段:

字段
说明
层级
格式
是否必填
备注
code状态码1int0 为成功,非 0 为失败,详情请参考错误码
message状态信息1string失败时标记失败原因。
id任务 ID1string仅当提交成功时填写。

应答示例:

{
    "code": "0",
    "message": "Success",
    "id": "fc5aa03e-6ae4-46a3-b8cf-1910a44e0d8a"
}

6. 错误码
类别/状态号
含义
说明
0成功
2000正在处理任务处理中。
请求类
1001请求参数无效请求参数缺失必需字段 / 字段值无效 / 重复请求。
1002无访问权限token 无效 / 过期 / 无权访问指定服务。
1003访问超频当前 appid 访问 QPS 超出设定阈值。
1004访问超额当前 appid 访问次数超出限制。
1005服务器繁忙服务过载,无法处理当前请求。
1008 - 1009保留号段待定。
音频类
1010音频过长音频数据时长超出阈值。
1011音频过大音频数据大小超出阈值。
1012音频格式无效音频 header 有误 / 无法进行音频解码。
1013音频静音音频未识别出任何文本结果。
1014 - 1019保留号段待定。
识别类
1020识别等待超时识别等待过程超时。
1021识别处理超时识别处理过程超时。
1022识别错误识别过程中发生错误。
1023 - 1029保留号段待定。
1099未知错误未归类错误。
7. Demo

以下 demo 均使用 token 鉴权方式。

curl

submit

curl -X POST -H 'Accept: */*' -H 'Authorization: Bearer; ${your_token}' -H 'Connection: keep-alive' -H 'User-Agent: python-requests/2.22.0' -H 'content-type: application/json' -d '{"url": "${your_url}"}' 'https://openspeech.bytedance.com/api/v1/vc/submit?appid=${your_appid}&language=zh-CN&use_itn=True&use_capitalize=True&max_lines=1&words_per_line=15'

query

curl -X GET -H 'Accept: */*' -H 'Authorization: Bearer; ${your_token}' -H 'Connection: keep-alive' -H 'User-Agent: python-requests/2.22.0' 'https://openspeech.bytedance.com/api/v1/vc/query?appid=en0yefmkvssp&id=fbf5edcf-378e-4f54-8a7f-5d8fd5e5e2f1'

CPP

#include <bits/stdc++.h>
#include <curl/curl.h>
#include <nlohmann/json.hpp>
using namespace std;

const char QUERY_URL[] = "https://openspeech.bytedance.com/api/v1/vc/query?appid=%s&id=%s";
const char SUBMIT_URL[] = "https://openspeech.bytedance.com/api/v1/vc/submit?appid=%s&language=%s&use_itn=True&use_capitalize=True&max_lines=1&words_per_line=15";
const char APPID[] = "";
const char TOKEN[] = "";
const char LANGUAGE[] = "zh-CN";
const char AUTH_HEADER[] = "Authorization: Bearer; %s";
const char FILE_URL[] = "https://www.iflyrec.com/AudioStreamService/v1/outLinks/2a7e5980004145ea8a01fb73e88b4ddd?action=play";


size_t write_data(void *buffer, size_t size, size_t nmemb, void *userp){
  char *d = (char*)buffer;
  string *b = (string*)(userp);
  int result = 0;
  if (b != NULL){
      b->append(d, size*nmemb);
      result = size*nmemb;
  }
  return result;
}

std::string submit() {
  CURL *curl;
  CURLcode res;
  curl = curl_easy_init();
  char target_url[2000] = "";
  char auth_header[2000] = "";
  sprintf(target_url, SUBMIT_URL, APPID, LANGUAGE);
  sprintf(auth_header, AUTH_HEADER, TOKEN);
  std::string result;
  if(curl) {
     curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST");
     curl_easy_setopt(curl, CURLOPT_URL, target_url);
     curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
     curl_easy_setopt(curl, CURLOPT_DEFAULT_PROTOCOL, "https");
     struct curl_slist *headers = NULL;
     headers = curl_slist_append(headers, "Accept: */*");
     headers = curl_slist_append(headers, auth_header);
     headers = curl_slist_append(headers, "Connection: keep-alive");
     headers = curl_slist_append(headers, "content-type: application/json");
     curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
     char data[2000] = "";
     sprintf(data, "{\"url\": \"%s\"}", FILE_URL);
     curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data);
     curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &write_data);
     curl_easy_setopt(curl, CURLOPT_WRITEDATA, &result);
     curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10);
     res = curl_easy_perform(curl);
  }
  curl_easy_cleanup(curl);
  std::cout << result << std::endl;
  auto job_id = nlohmann::json::parse(result)["id"];
  return job_id;
}

void query(const char* job_id) {
  CURL *curl;
  CURLcode res;
  curl = curl_easy_init();
  char target_url[2000] = "";
  char auth_header[2000] = "";
  sprintf(target_url, QUERY_URL, APPID, job_id);
  sprintf(auth_header, AUTH_HEADER, TOKEN);
  if(curl) {
     curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "GET");
     curl_easy_setopt(curl, CURLOPT_URL, target_url);
     curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
     curl_easy_setopt(curl, CURLOPT_DEFAULT_PROTOCOL, "https");
     struct curl_slist *headers = NULL;
     headers = curl_slist_append(headers, "Accept: */*");
     headers = curl_slist_append(headers, auth_header);
     headers = curl_slist_append(headers, "Connection: keep-alive");
     curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
     res = curl_easy_perform(curl);
  }
  curl_easy_cleanup(curl);
}

void work() {
  std::string job_id = submit();
  query(job_id.c_str());
}

int main(int argc, char *argv[]) {
  work();
  return 0;
}

JAVA

package com.bytedance.myapp;

import com.alibaba.fastjson.JSON;
import okhttp3.*;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;


public class myapp {
    static final String SUBMIT_URL = "https://openspeech.bytedance.com/api/v1/vc/submit?appid=%s&language=%s&use_itn=True&use_capitalize=True&max_lines=1&words_per_line=15";
    static final String QUERY_URL = "https://openspeech.bytedance.com/api/v1/vc/query?appid=%s&id=%s";
    static final String LANGUAGE = "zh-CN";
    static final String APPID = "${your_appid}";
    static final String TOKEN = "${your_token}";

    public static void main(String[] args) throws IOException {
        String job_id = submit();
        query(job_id);
    }

    public static void query(String job_id) throws IOException {
        OkHttpClient client = new OkHttpClient().newBuilder().build();
        String url = String.format(QUERY_URL, APPID, job_id);
        System.out.println(url);
        Request request = new Request.Builder()
                .url(url)
                .method("GET", null)
                .addHeader("Accept", "*/*")
                .addHeader("Authorization", String.format("Bearer; %s", TOKEN))
                .addHeader("Connection", "keep-alive")
                .build();
        Response response = client.newCall(request).execute();
        if (response.code() != 200) {
            throw new IOException(response.toString());
        }
        System.out.println(response.body().string());
    }

    public static String submit() throws IOException {
        OkHttpClient client = new OkHttpClient().newBuilder().build();
        MediaType mediaType = MediaType.parse("application/json");
        RequestBody body = RequestBody.create(mediaType, "{\"url\": \"https://www.iflyrec.com/AudioStreamService/v1/outLinks/2a7e5980004145ea8a01fb73e88b4ddd?action=play\"}");
        Request request = new Request.Builder()
                .url(String.format(SUBMIT_URL, APPID, LANGUAGE))
                .method("POST", body)
                .addHeader("Accept", "*/*")
                .addHeader("Authorization", String.format("Bearer; %s", TOKEN))
                .addHeader("Connection", "keep-alive")
                .addHeader("content-type", "application/json")
                .build();
        Response response = client.newCall(request).execute();
        Map resp = JSON.parseObject(response.body().byteStream(), HashMap.class);
        System.out.println(resp.toString());
        return resp.get("id").toString();
    }
}

Python

import time

import requests

base_url = 'https://openspeech.bytedance.com/api/v1/vc'
appid = ""
access_token = ""

language = 'zh-CN'
file_url = ''


def log_time(func):
    def wrapper(*args, **kw):
        begin_time = time.time()
        func(*args, **kw)
        print('total cost time = {time}'.format(time=time.time() - begin_time))
    return wrapper


@log_time
def main():
    response = requests.post(
                 '{base_url}/submit'.format(base_url=base_url),
                 params=dict(
                     appid=appid,
                     language=language,
                     use_itn='True',
                     use_capitalize='True',
                     max_lines=1,
                     words_per_line=15,
                 ),
                 json={
                    'url': file_url,
                 },
                 headers={
                    'content-type': 'application/json',
                    'Authorization': 'Bearer; {}'.format(access_token)
                 }
             )
    print('submit response = {}'.format(response.text))
    assert(response.status_code == 200)
    assert(response.json()['message'] == 'Success')

    job_id = response.json()['id']
    response = requests.get(
            '{base_url}/query'.format(base_url=base_url),
            params=dict(
                appid=appid,
                id=job_id,
            ),
            headers={
               'Authorization': 'Bearer; {}'.format(access_token)
            }
    )
    print('query response = {}'.format(response.json()))
    assert(response.status_code == 200)

if __name__ == '__main__':
    main()