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

接口说明

最近更新时间2023.08.03 19:52:35

首次发布时间2023.04.14 14:00:57

接入必读

请先查看接入必读了解具体接入方式,再参考此文档完成接入。

功能介绍

LyricsAlignment为用户提供文本转歌曲的歌词对齐能力,该API目前支持非流式、多语种歌词对齐。

  • 输入:支持用户上传带原唱的歌曲以及歌词文件
  • 输出:歌词与歌曲的字级别对齐的时间戳信息,krc格式json结果

接口说明

  • 当前支持通过 HTTP 协议在线调用。

  • 请求内容包括:

    • payloadpayload字段为将请求参数序列化后的json文本

    • data字段为将音频二进制文件按照base64格式编码(标准base64,RFC 4648)的文本

  • 使用备注:

注意项说明

功能

限制说明

  • 目前仅支持语言为中文、葡萄牙语、印尼语

  • 上述语种可间杂英文,但暂不支持纯英文(如:整句英文)

  • 不支持混合语种

  • 歌词避免直接拼接json文本,尽量使用转换库,避免造成歌词文本中"\n"导致json格式错误

输入音频格式支持wav、pcm、mp3、aac等常见格式
音频编码建议采样率大于等于44k、双声道
音频时长限制小于10分钟;建议大于5s,否则会影响对齐效果
音频大小限制小于100MB
输出结果格式json字符串格式,详情请参考响应格式

公共参数

参考详细说明功能调用-通用协议

配置参数

payload配置参数为json字符串格式

字段描述类型是否必传默认值
url服务请求数据的url,若data字段为空,则使用该url下载音频数据。详见功能调用-通用协议-payload.urlstring-
language设置语言类型,支持语言及参数:chinese(中文)、portuguese(葡萄牙语)、indonesian(印尼语)stringchinese
lyrics设置输入歌词文本,句间使用"\n"符号进行连接。(注意避免直接拼接json文本,尽量使用转换库,从而避免造成歌词文本中"\n"导致json格式错误)string-

响应格式

HTTP响应Content-Type: application/json

字段描述类型
task_id请求任务id,用于链路追踪、问题排查string
namespace服务接口命名空间,比如LyricsAlignmentstring
data请求响应二进制数据,标准base64编码string
payload请求响应文本信息,json字符串格式string
status_code状态码number
status_text状态信息string
  • 响应结果payload为json字符串格式,json内容格式如下:(注:这里的\u003c、\u003e是展示问题,使用json库进行反序列化后并不会存在乱码问题)
{
    "lyrics":"[22080,4290]\u003c0,210,0\u003e早\u003c210,330,0\u003e上\u003c540,210,0\u003e在\u003c750,180,0\u003e河\u003c930,390,0\u003e边\u003c1320,270,0\u003e与\u003c1590,240,0\u003e你\u003c1830,270,0\u003e看\u003c2100,210,0\u003e日\u003c2310,570,0\u003e出\u003c2880,270,0\u003e散\u003c3150,330,0\u003e散\u003c3480,810,0\u003e步\n[26400,4020]\u003c0,270,0\u003e回\u003c270,300,0\u003e家\u003c570,240,0\u003e的\u003c810,210,0\u003e路\u003c1020,360,0\u003e上\u003c1380,270,0\u003e给\u003c1650,180,0\u003e你\u003c1830,330,0\u003e唱\u003c2160,270,0\u003e着\u003c2430,540,0\u003e歌\u003c2970,240,0\u003e牵\u003c3210,300,0\u003e牵\u003c3510,510,0\u003e手\n[30450,4410]\u003c0,210,0\u003e在\u003c210,120,0\u003e河\u003c330,270,0\u003e边\u003c600,270,0\u003e的\u003c870,120,0\u003e码\u003c990,120,0\u003e头\u003c1110,270,0\u003e上\u003c1380,270,0\u003e踏\u003c1650,330,0\u003e过\u003c1980,180,0\u003e了\u003c2160,300,0\u003e一\u003c2460,240,0\u003e连\u003c2700,300,0\u003e串\u003c3000,300,0\u003e的\u003c3300,180,0\u003e两\u003c3480,330,0\u003e双\u003c3810,300,0\u003e脚\u003c4110,300,0\u003e印\n[35130,1890]\u003c0,210,0\u003e有\u003c210,120,0\u003e时\u003c330,60,0\u003e候\u003c390,150,0\u003e变\u003c540,210,0\u003e成\u003c750,90,0\u003e了\u003c840,240,0\u003e一\u003c1080,300,0\u003e双\u003c1380,270,0\u003e脚\u003c1650,240,0\u003e印\n[37020,6720]\u003c0,180,0\u003e那\u003c180,450,0\u003e是\u003c630,240,0\u003e因\u003c870,300,0\u003e为\u003c1170,180,0\u003e我\u003c1350,300,0\u003e抱\u003c1650,330,0\u003e起\u003c1980,390,0\u003e你\u003c2520,240,0\u003e每\u003c2760,300,0\u003e天\u003c3060,180,0\u003e的\u003c3240,330,0\u003e生\u003c3570,300,0\u003e活\u003c3870,180,0\u003e都\u003c4050,270,0\u003e是\u003c4320,270,0\u003e很\u003c4590,210,0\u003e开\u003c4800,600,0\u003e心\u003c5400,390,0\u003e像\u003c5790,270,0\u003e礼\u003c6060,660,0\u003e物\n[43890,2970]\u003c0,240,0\u003e不\u003c240,300,0\u003e会\u003c540,210,0\u003e的\u003c750,330,0\u003e作\u003c1080,270,0\u003e业\u003c1350,300,0\u003e和\u003c1650,210,0\u003e你\u003c1860,240,0\u003e一\u003c2100,270,0\u003e起\u003c2370,600,0\u003e做\n[46860,5220]\u003c0,270,0\u003e看\u003c270,240,0\u003e看\u003c510,540,0\u003e书\u003c1080,210,0\u003e不\u003c1290,90,0\u003e论\u003c1380,180,0\u003e什\u003c1560,120,0\u003e么\u003c1680,240,0\u003e时\u003c1920,270,0\u003e候\u003c2190,240,0\u003e你\u003c2430,270,0\u003e不\u003c2700,270,0\u003e开\u003c2970,270,0\u003e心\u003c3330,210,0\u003e我\u003c3540,270,0\u003e都\u003c3810,240,0\u003e会\u003c4050,300,0\u003e去\u003c4350,270,0\u003e陪\u003c4620,300,0\u003e着\u003c4920,300,0\u003e你\n[52320,3420]\u003c0,240,0\u003e尤\u003c240,210,0\u003e其\u003c450,150,0\u003e是\u003c600,240,0\u003e你\u003c840,180,0\u003e在\u003c1020,330,0\u003e生\u003c1350,330,0\u003e我\u003c1680,150,0\u003e的\u003c1830,1590,0\u003e气\n[56970,3480]\u003c0,270,0\u003e黑\u003c270,330,0\u003e白\u003c600,150,0\u003e的\u003c750,330,0\u003e世\u003c1080,300,0\u003e界\u003c1380,240,0\u003e因\u003c1620,300,0\u003e你\u003c1920,270,0\u003e而\u003c2190,270,0\u003e变\u003c2460,270,0\u003e得\u003c2730,270,0\u003e美\u003c3000,480,0\u003e丽\n[61320,3390]\u003c0,330,0\u003e有\u003c330,240,0\u003e你\u003c570,240,0\u003e在\u003c810,300,0\u003e身\u003c1110,270,0\u003e边\u003c1380,180,0\u003e不\u003c1560,330,0\u003e怕\u003c1890,180,0\u003e刮\u003c2070,360,0\u003e风\u003c2430,150,0\u003e和\u003c2580,420,0\u003e下\u003c3000,390,0\u003e雨\n[65670,4320]\u003c0,240,0\u003e陪\u003c240,150,0\u003e你\u003c390,120,0\u003e一\u003c510,300,0\u003e起\u003c810,330,0\u003e逛\u003c1140,180,0\u003e的\u003c1320,750,0\u003e街\u003c2190,210,0\u003e和\u003c2400,120,0\u003e你\u003c2520,180,0\u003e一\u003c2700,330,0\u003e起\u003c3030,210,0\u003e躲\u003c3240,300,0\u003e的\u003c3540,780,0\u003e雨\n[70020,91440]\u003c0,240,0\u003e只\u003c240,90,0\u003e要\u003c330,270,0\u003e有\u003c600,270,0\u003e你\u003c870,150,0\u003e在\u003c1020,390,0\u003e身\u003c1410,270,0\u003e边\u003c1680,180,0\u003e我\u003c1860,300,0\u003e就\u003c2160,240,0\u003e会\u003c17190,210,0\u003e感\u003c89160,240,0\u003e觉\u003c90360,120,0\u003e很\u003c90510,330,0\u003e甜\u003c90840,600,0\u003e蜜"
}

参考示例

调用方式为:POST /api/v1/invoke

Golang

// Code sample:
// use http client to invoke SAMI HTTP Service
package main

import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"time"
)

type InvokeRequest struct {
	Data    string  `thrift:"data,4" json:"data,omitempty"`
	Payload *string `thrift:"payload,3" json:"payload,omitempty"`
}

type LyricsAlignmentPayload struct {
	Language string `json:"language"`
	Lyrics   string `json:"lyrics"`
}

type InvokeResponse struct {
	StatusCode int32   `form:"status_code,required" json:"status_code,required" query:"status_code,required"`
	StatusText string  `form:"status_text,required" json:"status_text,required" query:"status_text,required"`
	TaskId     string  `form:"task_id,required" json:"task_id,required" query:"task_id,required"`
	Namespace  string  `form:"namespace,required" json:"namespace,required" query:"namespace,required"`
	Payload    *string `form:"payload,omitempty" json:"payload,omitempty" query:"payload,omitempty"`
	Data       []byte  `form:"data,omitempty" json:"data,omitempty" query:"data,omitempty"`
	State      *string `form:"state,omitempty" json:"state,omitempty" query:"state,omitempty"`
}

type LARespPayload struct {
	Lyrics string `json:"lyrics"`
}

const (
	domain = "https://sami.bytedance.com"

	appkey = "your_appkey"

	// SAMI method
	version   = "v4"
	namespace = "LyricsAlignment"
	// dump output
	payloadOutputFile = "LyricsAlignment.json"
	isDump            = true
)

func main() {
	// Get token
	token := "your_token"

	// Construct HTTP request
	// 1. Read local audio file
	audioPath := "/your/audio/path"
	content, err := ioutil.ReadFile(audioPath)
	if err != nil {
		log.Fatalf("failed to read file: %v", err)
	}
	data := base64.StdEncoding.EncodeToString(content)

	// 2. Set HTTP json body
	lyrics := "早上在河边与你看日出散散步\n" +
		"回家的路上给你唱着歌牵牵手\n" +
		"在河边的码头上踏过了一连串的两双脚印\n" +
		"有时候变成了一双脚印\n" +
		"那是因为我抱起你每天的生活都是很开心像礼物\n" +
		"不会的作业和你一起做\n" +
		"看看书不论什么时候你不开心我都会去陪着你\n" +
		"尤其是你在生我的气\n" +
		"黑白的世界因你而变得美丽\n" +
		"有你在身边不怕刮风和下雨\n" +
		"陪你一起逛的街和你一起躲的雨\n" +
		"只要有你在身边我就会感觉很甜蜜\n"
	payload := LyricsAlignmentPayload{
		Language: "chinese",
		Lyrics:   lyrics,
	}
	p, _ := json.Marshal(payload)
	pStr := string(p)

	invokeRequest := InvokeRequest{
		Data:    data,
		Payload: &pStr,
	}
	body, _ := json.Marshal(invokeRequest)
	urlPath := fmt.Sprintf(
		"%v/api/v1/invoke?version=%v&token=%v&appkey=%v&namespace=%v",
		domain, version, token, appkey, namespace,
	)
	log.Printf("invoke request: %v", urlPath)

	// 3. HTTP POST request
	start := time.Now()
	resp, err := http.Post(urlPath, "application/json", bytes.NewBuffer(body))
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

	// Parse HTTP response
	ret, err := ioutil.ReadAll(resp.Body)
	if err != nil || resp.StatusCode != http.StatusOK {
		panic(string(ret))
	}
	log.Printf("http invoke: cost=%vms resp=%v", time.Since(start).Milliseconds(), string(ret))

	// parse SAMI response
	samiResp := InvokeResponse{}
	payloadStr := ""
	if err = json.Unmarshal(ret, &samiResp); err != nil {
		log.Println("parse response failed", string(ret), err)
		panic(err)
	}
	if samiResp.Payload != nil {
		payloadStr = *samiResp.Payload
	}
	var respPayload LARespPayload
	err = json.Unmarshal([]byte(payloadStr), &respPayload)
	if err != nil {
		panic(err)
	}
	log.Printf("response task_id=%v, \npayload=%v\n, data=[%d]byte", samiResp.TaskId, respPayload.Lyrics, len(samiResp.Data))
	if isDump && samiResp.Payload != nil {
		_ = ioutil.WriteFile(payloadOutputFile, []byte(*samiResp.Payload), 0644)
	}
}

Python

# Code sample:
# use http client to invoke SAMI HTTP Service
import base64
import json
import sys

import requests

# Construct HTTP request
output_format = 1
payload = json.dumps({
    "language": "chinese",
    "lyrics": '早上在河边与你看日出散散步\n'
              '回家的路上给你唱着歌牵牵手\n'
              '在河边的码头上踏过了一连串的两双脚印\n'
              '有时候变成了一双脚印\n'
              '那是因为我抱起你每天的生活都是很开心像礼物\n'
              '不会的作业和你一起做\n'
              '看看书不论什么时候你不开心我都会去陪着你\n'
              '尤其是你在生我的气\n'
              '黑白的世界因你而变得美丽\n'
              '有你在身边不怕刮风和下雨\n'
              '陪你一起逛的街和你一起躲的雨\n'
              '只要有你在身边我就会感觉很甜蜜\n'
})
with open("/your/audio/path", "rb") as f:
    data = f.read()
    data = base64.b64encode(data).decode('utf-8')
req = {
    "appkey": "your_appkey",
    "token": "your_token",
    "namespace": "LyricsAlignment",
    "payload": payload,
    "data": data
}

if __name__ == "__main__":
    # HTTP POST request
    resp = requests.post("https://sami.bytedance.com/api/v1/invoke", json=req)

    # Parse HTTP SAMI response
    try:
        sami_resp = resp.json()
        if resp.status_code != 200:
            print(sami_resp)
            sys.exit(1)
    except:
        print(resp)
        sys.exit(1)

    print("response task_id=%s status_code=%d status_text=%s" % (
        sami_resp["task_id"], sami_resp["status_code"], sami_resp["status_text"]), end="\n")
    if "payload" in sami_resp and len(sami_resp["payload"]) > 0:
        result = json.loads(sami_resp["payload"])
        print("result=%s" % result["lyrics"], end="\n")
    if "data" in sami_resp and len(sami_resp["data"]) > 0:
        # Save audio data into file
        data = base64.b64decode(sami_resp["data"])
        print("data=[%d]bytes" % len(data))
        with open("output.wav", "wb") as f:
            f.write(data)

常见问题