字幕就是把转写文本切成一段段,每段在说出的那几秒里显示,长度要放得进屏幕。英语切起来很容易。字幕条在空格处结束,一个对语言一无所知的工具也能切出可读的行。泰语却把整个短语写成一串连续的字符。按字符数切分的工具会从词的中间切开,观众在一行里读到半个词,两秒后才在下一行读到另一半。
泰语字幕条可以直接用 Paxa STT Lite 返回的词级时间戳来构建。每个时间区间都是一个完整的泰语词,带有开始和结束时间,这些位置恰好就是字幕条所有可以切分的地方。把区间转换成字幕条的规则一页就能写完,得到的是任何播放器都能读取的字幕文件。
什么是时间区间#
将 timestamps 设为 "word",响应就会在转写文本旁附带一个 words 数组。每一项是一个词,以及它以秒为单位的开始和结束时间。泰语的词在输出前已经切分好。一个区间总是覆盖完整的词,不会停在某个字符上。所有区间按顺序拼接,就能还原转写文本,一个字符也不少。
AUDIO=$(base64 < meeting.m4a | tr -d '\n')
curl -X POST https://api.paxalabs.com/v1/stt \
--max-time 300 \
-H "Authorization: Bearer $PAXA_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"audio\": \"$AUDIO\", \"model\": \"paxa-stt-lite-v1-preview\", \"timestamps\": \"word\"}"{
"text": "สวัสดีค่ะคุณสมชายยอดชำระของท่านจำนวน พันสองร้อยห้าสิบ บาทครบกำหนดวันที่ สิบห้า สิงหาคมนี้ชำระผ่านแอปได้ตลอด ยี่สิบสี่ ชั่วโมงค่ะ",
"words": [
{
"text": "สวัสดี",
"start": 0.12,
"end": 0.44
},
{
"text": "ค่ะ",
"start": 0.52,
"end": 0.66
},
{
"text": "คุณ",
"start": 1.08,
"end": 1.24
},
{
"text": "สมชาย",
"start": 1.32,
"end": 1.88
},
{
"text": "ยอด",
"start": 2.36,
"end": 2.56
},
{
"text": "ชำระ",
"start": 2.6,
"end": 2.879
},
{
"text": "ของ",
"start": 3,
"end": 3.14
},
{
"text": "ท่าน",
"start": 3.2,
"end": 3.359
},
{
"text": "จำนวน",
"start": 3.48,
"end": 3.879
},
{
"text": "พันสองร้อยห้าสิบ",
"start": 4.24,
"end": 5.4
},
{
"text": "บาท",
"start": 5.56,
"end": 5.759
},
{
"text": "ครบ",
"start": 6.32,
"end": 6.52
},
{
"text": "กำหนด",
"start": 6.56,
"end": 6.84
},
{
"text": "วัน",
"start": 6.92,
"end": 7.039
},
{
"text": "ที่",
"start": 7.04,
"end": 7.119
},
{
"text": "สิบห้า",
"start": 7.24,
"end": 7.48
},
{
"text": "สิงหาคม",
"start": 7.68,
"end": 8.26
},
{
"text": "นี้",
"start": 8.36,
"end": 8.5
},
{
"text": "ชำระ",
"start": 9.08,
"end": 9.36
},
{
"text": "ผ่าน",
"start": 9.48,
"end": 9.68
},
{
"text": "แอป",
"start": 9.76,
"end": 9.92
},
{
"text": "ได้",
"start": 10,
"end": 10.14
},
{
"text": "ตลอด",
"start": 10.2,
"end": 10.46
},
{
"text": "ยี่สิบสี่",
"start": 10.56,
"end": 10.96
},
{
"text": "ชั่วโมง",
"start": 11.08,
"end": 11.4
},
{
"text": "ค่ะ",
"start": 11.52,
"end": 11.66
}
],
"usage": {
"seconds": 11.952,
"credits": 1.66
}
}把区间切成字幕条#
按顺序遍历区间,把它们收进当前字幕条,直到触及以下三个限制之一。这时字幕条在最后一个区间的结束时间结束,下一条从下一个区间的开始时间开始。
- 长度。泰语一行在三十五个字符左右读起来比较舒服,一条字幕最多两行。统计已收进的词的字符数,在会让长度超限的那个词之前切分。
- 时长。一条字幕在屏幕上停留超过约六秒,看起来就像卡住了。只要下一个词会让字幕条超过时长限制就切分,即使这一行还有空间。
- 停顿。前一个区间结束到下一个区间开始之间的间隔超过约半秒,就是一次换气或一句话的结束。在这里切分,字幕条会随说话人一起停下。
在停顿处结束的字幕条,比按字数截断的读起来更顺。为了切在停顿处,稍微超出长度限制也值得。如果一条字幕有两行,就在条内区间之间最宽的间隔处换行,那里通常是短语的边界。
words
วันนี้ 0.00 0.38
เรา 0.38 0.55
จะ 0.55 0.70
คุย 0.70 0.98
เรื่อง 0.98 1.30
งบประมาณ 1.30 1.92
ปีหน้า 1.92 2.40
กัน 2.40 2.61
นะครับ 2.61 3.05
cues
1 00:00:00,000 --> 00:00:01,300
วันนี้เราจะคุยเรื่อง
2 00:00:01,300 --> 00:00:03,050
งบประมาณปีหน้ากันนะครับ应当请求的书写格式#
字幕需要口语体,也就是默认格式。观众听到 สองพันสามร้อยห้าสิบบาท,读到的也是同样的词。说话人念出七个音节时,屏幕上却是一串数字,读起来就对不上。用书面体构建的字幕条还会丢失数字内部的区间边界,因为这些数字是作为一个词元返回的。
出于同样的原因,请保留逐字风格。观众听到说话人说错重来,读到的却是整理过的句子,会察觉到差别。精简风格适合脱离音频单独阅读的转写文本。
写入文件#
SRT 为每条字幕编号,把开始和结束时间写成时、分、秒和毫秒,毫秒前用逗号,后面跟上文本。WebVTT 与之相同,只是毫秒前用句点,并多一行文件头。两者都是纯文本,都以 UTF-8 保存泰语,无需额外设置。把区间里的秒数保留三位小数写进去,文件就完成了。
要制作翻译字幕轨,就把每条字幕的文本作为一个分段发给翻译 API,并让 id 随结果原样返回。时间信息跟着 id 走。泰语视频由此得到一条切分点相同的英语字幕轨,两条字幕轨保持对齐。