Thursday 25 February 2021

中文和英文双字幕观看 YouTube 视频

我英文差, 但是又不能只看中文(讲话的人是英文,失去原味),所以打算双字幕观看。英文字幕在 mpv 播放器下方,看到不懂的字可以望上面的中文字幕。

虽然此视频  IlU-zDU6aQ0 网页版有英文字幕以及自动翻译的中文字幕,可是旧版本的 youtube-dl 和随便两个网站 (https://downsub.comhttps://savesubs.com) 都只能拿到英文字幕。



虽然最新版本的  youtube-dl 已经 fix 了,无论如何,要手动完成此任务,也行的:

1. 下载英文字幕 (.vtt): youtube --skip-download --write-sub --sub-lang en https://www.youtube.com/watch?v=IlU-zDU6aQ0 。

2. 在 Youtube 播放器, 变改语言边 inspect network, 得到 `api/timedtext?` 链接。

用 curl 跑链接获得 .json 格式的字幕。

3.  转换 .json 去 .srt 不需要重造轮子,浏览 https://zhuanlan.zhihu.com/p/337934938 下载某人写的 node.js 代码, `parse_subtitle.js`, 加上这三条 line:

    let sourcePath = process.argv[2]
    let resultPath = process.argv[3]
    let isTime = true

4. 然后把这代码: `s.segs[0].utf8` 改成 `s.segs[0].utf8.match(/.{1,21}/g).join('\n')` ,

以便能在 mpv 播放器, 每 21 个中文字 wrap,避免一行长过播放器宽度,因为 mpvlibass 不支持没空白的中文字 wrap。

5. 运行 `node parse_subtitle.js IlU-zDU6aQ0.sub.zh-Hans.json IlU-zDU6aQ0.sub.zh-Hans.srt` ,

把 .json 转换去 .srt

6. 运行 `mpv --sub-file="IlU-zDU6aQ0.sub.zh-Hans.srt" --sub-file="Marty Lobdell - Study Less Study Smart-20110722-IlU-zDU6aQ0.en.vtt" --sid=2 --secondary-sid=1 'Marty Lobdell - Study Less Study Smart-20110722-IlU-zDU6aQ0.mp4` ,

播放 .srt 中文字幕在上方, 以及 .vtt 英文字幕在下方。



youtube-dl fix 了不表示这 web API 就没用,其实两 API 的翻译是有出入的。

web API 是 53 个中文字翻译(我切为三行,但翻译是一起的),而 youtube-dl 是 27 个中文字翻译。

理论上 53 一行肯定比 27 一行能翻译比较准确, 不过两者都没考虑上下文的句号来断句翻译, 所以翻译可能怪怪的。

当然,句号翻译的前提是原文是有句号的, 否则没句号却强行变成同一行翻译也会有问题。

youtube-dl 原字幕(上图), ,web API 自动中文翻译(左下), youtube-dl 自动中文翻译(右下):





我也曾尝试 Android 11 的 Live Caption (即时字幕) 功能以达到类似效果。
可是 Live caption当前只有英文:



,且 player 的该视频只有英文选项。



只要其一有中文即可(不过如果只能选 player 自动翻译的中文,意味着两个都是机器翻译,准确性也大打折扣),偏偏两个都是英文,任务失败。

Android Live caption + 视频字幕效果图, Live caption (移去上方), 英文原字幕 (下方):


不过桌面版的 Chrome 也有  Live Caption 啊!

只不过必须安装 Chrome Canary(否则就算启动设置也是没用的)

,可是 Linux 却没有 Chrome Canary(除非... 用 wine/虚拟机... 囧我不奉陪了)。



算了,试下打开封尘不懂几年的 Windows。

安装好 Chrome Canary 后,去 `chrome://flags/#enable-accessibility-live-captions`(没有出现 "Live Caption" 就搜 "Live" 关键字) 设置 "Enabled" 它。
(Android 的 Chrome Canary 没有该设置... Android 再次任务失败)




Enabled 后下面提示你 "Relaunch" Chrome。

重启 Chrome 后,然后去 settings -> accessibility 启动 Live Caption

,抑或去视频页面的右上方 "" 图标会能给你实时启动/关闭 Live Caption。



第一次启动 Live Caption 后会花一些时间下载语言分析包, 不过当前只有英文所以不会下载太久。

然后就完成啦。视频播放器字幕没办法永久移动, 所以只能移动 Live caption 字幕去上方。

由于 Live caption 只有英文,那么中文的就必须是播放器字幕,就形成了英文必须在上方的情况
(必须全屏,否则头被完全盖着囧
那个占位的箭头有点鸡肋,展开又太多行眼花缭乱,不展开又嫌少行, 也不能自定义多少行):



无论如何,Live Caption 用 speech 翻译的准确性肯定比原文翻译低。

如上图, "Ontogeny" 变成 "On to me"。

当然,播放器的自动翻译也无法保证百分百,但起码能比 speech 翻译准确。

除非本来就没字幕,就可能打平, 但实时翻译真的能比得上预先翻译?

Live caption 还有说了该段话才显示的局限性
(无法一次性理解整体, 当然你也可以说有利弊,可以了解当前讲的话是哪一个词)

, 些许延迟(我只观察一下)。

最后,避免日后知乎删除 .json 转 .srt 的代码文章,所以备份在这 (//orig 注释表示原版, //hole 表示我改的):

let sourcePath = process.argv[2] //'IlU-zDU6aQ0.sub.zh-Hans.json'
let resultPath = process.argv[3] //'IlU-zDU6aQ0.sub.zh-Hans.srt'
let isTime = true

//orig: https://zhuanlan.zhihu.com/p/337934938

var fs = require('fs')

//2. 处理 json

 fs.readFile(sourcePath, 'utf8', function (err, data) {
        if (err) console.log(err);
        var test1 = JSON.parse(data);//读取的值
        // console.log(test1.events)
        subtitle = test1.events
    
        if (fs.existsSync(resultPath)) {
            fs.writeFileSync(resultPath, '')
        }
        for (let i = 0; i < subtitle.length; i++) {
            const s = subtitle[i];
            // console.log(s.segs[0].utf8) //字幕内容
            // i :字幕的位置
            // console.log(s.tStartMs) 起始时间
            // console.log(s.tStartMs + s.dDurationMs) 结束时间
    
            // 字幕格式
            // 1
            // 00:00:09,779 --> 00:00:15,899
            // 内容
            // 空行
    
            if(isTime){
                fs.appendFileSync(resultPath, i + '\n' + convertTime(s.tStartMs) + ' --> ' + convertTime(s.tStartMs + s.dDurationMs)
                //+ '\n' + s.segs[0].utf8 + '\n\n',err => {     //orig
                + '\n' + s.segs[0].utf8.match(/.{1,21}/g).join('\n') + '\n\n',err => {  //hole
                    if(err){
                        console.log(err)
                    }
                })
            }else{
                fs.appendFileSync(resultPath, s.segs[0].utf8 + '\n\n',err => {  //orig
                    if(err){
                        console.log(err)
                    }
                })
            }
               
        }
    
    });

//3. 处理时间的方法

  function convertTime(time) {
    var s, m, h
    h = Math.floor(time / 1000 / 60 / 60)
    console.log('h:' + h)
    m = Math.floor(((time / 1000 / 60 / 60) % 1) * 60)
    // console.log('m:' + m)


    s = ((((time / 1000 / 60 / 60) % 1) * 60) % 1) * 60// 通过与 1 取余获取小数部分
    s = s.toFixed(3)
    s = s.toString().replace('.', ',').substring(0, 6)
    console.log('s:' + s)
    if (h < 10) {
        h = '0' + h
    }
    if (m < 10) {
        m = '0' + m
    }
    var timeString = h + ':' + m + ':' + s
    // console.log(timeString)
    return timeString
}





Saturday 6 February 2021

Trace python program from log perspective

回答了 -vvv debug pip:


Just a reminder to whom google this error and come here.

Let's say I get this error:

$ python3 example.py
Traceback (most recent call last):
  File "example.py", line 7, in <module>
    import aalib
ModuleNotFoundError: No module named 'aalib'

Since it mentions aalib, I was thought to try aalib:

$ python3.8 -m pip install aalib
ERROR: Could not find a version that satisfies the requirement aalib (from versions: none)
ERROR: No matching distribution found for aalib

But it actually wrong package name, ensure pip search(service disabled at the time of writing), or google, or search on pypi site to get the accurate package name:

enter image description here

Then install successfully:

$ python3.8 -m pip install python-aalib
Collecting python-aalib
  Downloading python-aalib-0.3.2.tar.gz (14 kB)
...

As pip --help stated:

$ python3.8 -m pip --help
...
  -v, --verbose               Give more output. Option is additive, and can be used up to 3 times.

To have a systematic way to figure out the root causes instead of rely on luck, you can append -vvv option of pip command to see details, e.g.:

$ python3.8 -u -m pip install aalib -vvv
User install by explicit request
Created temporary directory: /tmp/pip-ephem-wheel-cache-b3ghm9eb
Created temporary directory: /tmp/pip-req-tracker-ygwnj94r
Initialized build tracking at /tmp/pip-req-tracker-ygwnj94r
Created build tracker: /tmp/pip-req-tracker-ygwnj94r
Entered build tracker: /tmp/pip-req-tracker-ygwnj94r
Created temporary directory: /tmp/pip-install-jfurrdbb
1 location(s) to search for versions of aalib:
* https://pypi.org/simple/aalib/
Fetching project page and analyzing links: https://pypi.org/simple/aalib/
Getting page https://pypi.org/simple/aalib/
Found index url https://pypi.org/simple
Getting credentials from keyring for https://pypi.org/simple
Getting credentials from keyring for pypi.org
Looking up "https://pypi.org/simple/aalib/" in the cache
Request header has "max_age" as 0, cache bypassed
Starting new HTTPS connection (1): pypi.org:443
https://pypi.org:443 "GET /simple/aalib/ HTTP/1.1" 404 13
[hole] Status code 404 not in (200, 203, 300, 301)
Could not fetch URL https://pypi.org/simple/aalib/: 404 Client Error: Not Found for url: https://pypi.org/simple/aalib/ - skipping
Given no hashes to check 0 links for project 'aalib': discarding no candidates
ERROR: Could not find a version that satisfies the requirement aalib (from versions: none)
Cleaning up...
Removed build tracker: '/tmp/pip-req-tracker-ygwnj94r'
ERROR: No matching distribution found for aalib
Exception information:
Traceback (most recent call last):
  File "/usr/lib/python3/dist-packages/pip/_internal/cli/base_command.py", line 186, in _main
    status = self.run(options, args)
  File "/usr/lib/python3/dist-packages/pip/_internal/commands/install.py", line 357, in run
    resolver.resolve(requirement_set)
  File "/usr/lib/python3/dist-packages/pip/_internal/legacy_resolve.py", line 177, in resolve
    discovered_reqs.extend(self._resolve_one(requirement_set, req))
  File "/usr/lib/python3/dist-packages/pip/_internal/legacy_resolve.py", line 333, in _resolve_one
    abstract_dist = self._get_abstract_dist_for(req_to_install)
  File "/usr/lib/python3/dist-packages/pip/_internal/legacy_resolve.py", line 281, in _get_abstract_dist_for
    req.populate_link(self.finder, upgrade_allowed, require_hashes)
  File "/usr/lib/python3/dist-packages/pip/_internal/req/req_install.py", line 249, in populate_link
    self.link = finder.find_requirement(self, upgrade)
  File "/usr/lib/python3/dist-packages/pip/_internal/index/package_finder.py", line 926, in find_requirement
    raise DistributionNotFound(
pip._internal.exceptions.DistributionNotFound: No matching distribution found for aalib

From above log, there is pretty obvious the URL https://pypi.org/simple/aalib/ 404 not found. Then you can guess the possible reasons which cause that 404, i.e. wrong package name. Another thing is I can modify relevant python files of pip modules to further debug with above log. To edit .whl file, you can use wheel command to unpack and pack.



由于答案篇幅有点长且可以多种 debug 方式,我就没说更进一阶的方式。

由于 pip 使用 logging 来 print 的,更改 logging module 的输出函数可以更直接知道这个 log 是谁 print 的, 从而轻松修改相应的 python 脚本。

  


即:

    print(''.join(['\n\x1b[7;36m[holeL] name: ', repr(name), ' lvl: ', repr(level), ' fn: \x1b[0m\x1b[K\x1b[17;36m', repr(fn), '\x1b[0m\x1b[K\x1b[7;36m lno: ', repr(lno), ' msg: ', repr(msg), ' args: ', repr(args), ' exc_info: ', repr(exc_info), ' func: ', repr(func), ' sinfo: ', repr(sinfo), '\x1b[0m\x1b[K' ]))

比起 trace 模块方便输出大量的 log 要去想排除哪一个又怕排除错,轻松得多:






之后想到修改 trace 模块以包含 print 和 write,  之后想到有 stack 才能清楚 log 与程序的上下关系。

不过为了想到避免重复的 stacks, 尝试了各种复杂的代码, 之后想到 Prev 重复的 Prev 重复很难 refer。

最后想到用 [NEW] 和 [EQU] 不同 tags 标记新部分和重复部分,一目了然且不会看起来重复冗长。

 `NEW` 和 `EQU` 同样三个字的宽度才容易区分上下同一个文件。




... 省略



字体颜色也并非随着个人喜好而随便选的。

绿表示新/diff 的 +(显示时间都是新的,所以也是绿),和平深蓝表示打平/相等/重复。

白底黑字代码比较一目了然阅读(原先的水绿背景虽然美但太难阅读了),且与一般黑底白字的 log 相反,容易区分。

至于文件名字和行数都是同一个水绿色(不会太明显而扰乱代码的阅读,也不会太深很难看见)。

行数连着主打背景白色的源代码,
所以水绿用在背景色

不过不同终端的颜色可能有点不同。gnome-terminal 的标记和时间会闪, konsole 不会, 之前在 konsole 做的时候没发现不同。

由于冗长的代码需要长时间的阅读,我倾向于不闪, 不过就给用户两种选择吧。




源代码: https://github.com/limkokhole/pylogtrace