vList7: 浏览器渲染srt?可行!

in 前后端开发 with 0 comment

众所周知,浏览器只支持vtt字幕,但是“本是同根生”的srt压根不理
怎么办?网上99%的教程都是一个套路:使用JS/TS库
的确可以这么做,但是无法体验浏览器带来的高运行效率
不要急,这个时候,我们可以另辟蹊径!且看vList5.7的新特色:原生SRT字幕!

01.webp

源码

首先如果你赶时间,直接拿走不谢
https://github.com/imzlh/vList5/blob/main/src/utils/subsrt.ts#L1

分析

首先我们比较一下srt和vtt(个人见解)
VTT MDN官方文档
对于字幕,最重要的是定位、时间轴,次要的是动画、各种样式,因此我们首先了解一下SRT字幕格式

该格式基于纯文本,以 CR+LF 作为行分隔符(尽管有时会发现 Unix 样式的 LF 换行符)。标题中有时会使用一些 HTML 标签来表示粗体或斜体文本。

每个字幕都表示为一组行(用空行与其他字幕分隔)。第一行有一个数字(按顺序分配给每个标题);第二行是视频中字幕的时间戳范围,时间以“小时:分钟:秒,毫秒”的格式表示,范围的开始和结束用 --> 分隔 。

时间戳范围后面可以可选地跟随着像素的特定定位,形式为。 X1:number Y1:number X2:number Y2:number

接下来的几行包含实际的字幕文本,以空行结尾。允许使用HTML <b><i><u><font> 标签。

翻译自 http://fileformats.archiveteam.org/wiki/SubRip_text_file_format

比如我拿出我 珍藏的 ffmpeg得来的字幕

435
00:20:53,990 --> 00:20:55,390
<font face="HYXuanSong 75S" size="78">这个重量感…</font>

436
00:20:56,630 --> 00:20:59,490
<font face="HYXuanSong 75S" size="78">祭…祭里</font>

437
00:21:05,580 --> 00:21:06,380
<font face="HYXuanSong 75S" size="78">为什么</font>

438
00:21:07,380 --> 00:21:09,610
<font face="HYXuanSong 75S" size="78">为什么会这么大…</font>

439
00:21:09,610 --> 00:21:10,580
<font face="HYXuanSong 75S" size="78">不对</font>

440
00:21:10,960 --> 00:21:12,580
<font face="HYXuanSong 75S" size="78">为什么会变成女孩子啊</font>

441
00:21:12,920 --> 00:21:15,500
<font face="HYXuanSong 75S" size="78">男生的祭里哪去了?</font>

442
00:21:14,590 --> 00:21:15,010
<font face="HYXuanSong 75S" size="69">{\an8}…不是</font>

443
00:21:16,980 --> 00:21:19,100
<font face="HYXuanSong 75S" size="78">总之 先冷静 铃</font>

444
00:21:19,520 --> 00:21:21,790
<font face="HYXuanSong 75S" size="78">内在姑且还是我本人啦</font>

很好,VTT支持大部分这类标签,于是重点放在了互通上
可以看到,有一个碍眼的 \an 标签在

选择行的对齐方式。如果没有设定 位置 或 移动 ,对齐方式决定了行的位置。如果设定了位置或移动,对齐方式决定了位置和移动的参考点。

\an 标签的 pos 参数使用小键盘布局,其值可以取小键盘上的数字,对应的位置就是该数字在小键盘上的相对位置。

  1. 屏幕左下角
  2. 屏幕底部中间
  3. 屏幕右下角
  4. 屏幕中间左侧
  5. 屏幕正中央
  6. 屏幕中间右侧
  7. 屏幕左上角
  8. 屏幕顶部中间
  9. 屏幕右上角

| Here is 参考文档 |

开始写代码

首先我们需要正则分离每个字幕组。考虑到Linux的LF换行,\r可选

content.split(/(?:\r?\n){2,}/)

接下来是逐行分析

line.match(/^(\d+)\r?\n(\d{2}):(\d{2}):(\d{2}),(\d{3})\s*-->\s*(\d{2}):(\d{2}):(\d{2}),(\d{3})\s*([\w\W]*)\s*$/);

这个正则看起来很长,其实很简单很简单,你看:
匹配结果

440
00:21:10,960 --> 00:21:12,580
<font face="HYXuanSong 75S" size="78">为什么会变成女孩子啊</font>

于是,我们想要的都有了,导入VttCue

const start = parseInt(res[2]) * 3600 + parseInt(res[3]) * 60 + parseInt(res[4]) + parseInt(res[5]) / 1000,
    end = parseInt(res[6]) * 3600 + parseInt(res[7]) * 60 + parseInt(res[8]) + parseInt(res[9]) / 1000,
    cue = new VTTCue(start, end, '');
video.textTrack[0].addCue(cue);

完成!撒花撒花

等等,位置跑哪去了

好问题,接下来我们解析一下ASS特色标签
由于VTT可以更改的比较少,想要搭配CSS样式比较麻烦,我们暂且不考虑。如果需要一些特色样式,请参考 Github ASS2VTT
或者在线体验 https://ass2vtt.imzlh.top/

cue.text = res[10].replace(/\{(\\[a-z].+?)+\}/g, (_, match) => {
    for (const tag of match.split('\\').filter(Boolean)) {
        // \an, \a 字幕位置
        if (tag.startsWith('a')) {
            const mode = parseInt(tag.substring(2));
            switch (Math.floor(mode / 3)) {
                case 1:
                    cue.line = -1;
                    break;
                case 2:
                    cue.line = 0.5;
                    break;
                case 3:
                    cue.line = 0;
                    break;
            }
            switch (mode % 3) {
                case 1:
                    cue.align = 'left';
                    break;
                case 2:
                    cue.align = 'center';
                    break;
                case 3:
                    cue.align = 'right';
                    break;
            }
            continue;
        }
    }

    return '';
});

具体我不介绍了,代码使用两个switch,很易理解

Responses