浏览器对full range视频的支持

为什么会去折腾full range video这个小众的东西?这次还真不是我闲的蛋疼,事实是我发现很多Twitch直播已经开始用full range了(例子有现在火热进行中的Supermajor(PGL)和之前的Epicenter),然后用Firefox看就发现颜色不对,对比度爆表。

之前有“控诉”过Firefox对视频的支持程度落后Chrome一个世纪,当时提出了几个Chorme支持、Firefox不支持的东西:

  • RGB视频
  • 4:4:4或者4:4:2视频
  • Full range视频
  • 对nV显卡的支持不好

当然,这几条还是对的,但是我今天发现Chrome对full range的支持也不是完美,所以还是客观起见,详细说明一下。

说到这个话题,不得不先重复一遍我之前在各种场合提过多次的所谓“对nV显卡的支持不好”具体啥意思。其实,就是在Win 7 + nVidia显卡的默认设置下,Firefox播放视频的色域(color range)会错误。这里的“错误”,严谨地说是limited range(就是99%的视频)不会正确拉伸到full range。

这个问题的产生原因是Win7不支持D3D11 DXVA,而Firefox没有对D3D9 DXVA进行优化。其实,之前Chrome也有这个bug,但是在我提出之后Chrome几个月后就修复了。这个问题的解决方案,除了换A/I GPU或者升级到Win 10这种废话,就是

  1. 软解视频,即把media.hardware-video-decoding.enabled设为false
  2. 修改nVidia控制面板的一个设置,从“通过视频播放器设置”改成手动设为full range:

QQ图片20180609164804

我之前一直是用2,倒是不会影响本地播放器的播放(本地播放器我本来就没开DXVA)。

现在说回full range视频的解码。先说Chrome那边:软解的情况下(设置chrome://flags/#disable-accelerated-video-decode 为Disabled),完美无缺。硬解就不太行了,会把视频强行进行一次伸张,导致颜色被clip。我发的bug ticket在此,这里有个视频可以测试:

当然,你也可以去上述的控制面板手动改成“limited”,来让他不伸张,所以正好得到正确的颜色,但是很显然这样又会毁掉所有的limited range的视频的颜色,所以还是老老实实软解吧。

Firefox除了硬解和Chrome有一样的问题以外,软解也不行——我发的ticket在此

基本内容就是这些了,顺便讲下上面那个视频是怎么造出来的。首先自然是ps里把那个图画出来,然后用FFMPEG做。上次已经提到了记得要加-vf scale=out_color_matrix=bt709来保证输出的YUV是BT.709的,那么full range怎么处理?我网上搜了下意外地发现相关信息相当少,一开始搜到一个什么-color_range 2(这个参数ffmpeg的documentation根本没提到啊喂),后来发现效果其实就是强行在元数据里塞个full range,结果视频的像素数值还都是16-235范围内的,出来就是个灰暗的视频囧。

最后在这贴搜到原来要用-pix_fmt yuvj420p才能输出0-255的视频。-color_range 2都不用加,出来的视频自动metatag都是full range了。我用的完整命令行如下:

ffmpeg -loop 1 -i colortest_hd.bmp -vf scale=out_color_matrix=bt709 -color_primaries 1 -color_trc 1 -colorspace 1 -t 30 -pix_fmt yuvj420p out_420_709_full.mp4

Python在code page 65001命令行下print 非ASCII字符的问题

之前不是写过一个自动化破解Kindle提取图片的脚本嘛,里面有一部是调用别人的一个py2脚本来抽取res里的HD图片。

那个脚本我琢磨着不是很复杂,于是想想能不能改成py3,这样就可以直接当模块调用了,elegant很多。我先用2to3转了一波,把里面的printdict.keys()list(dict)之类的比较简单的转换了。跑了下果然还不行,然后就照着报错信息一点点改。

其实,绝大部分的问题都源自字符串的处理。Py2里字符串默认是byte,py3是Unicode字符串。这个其实再做一些低级操作(比如这里的抽取文件头啥的)反而是py2比较方便,抽出来就是byte直接就能操作。不过既然我们要转py3了,那就逐一处理吧。里面如果本来就是当字符串用的不用处理,那些从byte读出来的则实质是byte类型,所以如果要print或者和其他真·字符串进行合并之类的,要先decode()一下;大部分其实都是一些ASCII的控制字符,所以decode()默认的utf-8就行。原本的从Unicode encode到byte的直接去掉encode()unicode()要变str().encode('hex')(假设前面的变量是byte)直接换.hex()。其实,直接print byte也无所谓,就是会显示成b'abc'而已,但是强迫症表示受不了。

然后我着手处理之前懒得处理的print(self.title)崩掉的问题。其实转换成py3之后,这个问题就很少出现了,但是我发现当你把输出redirect到文档(比如x.py > 1.txt)还会有问题。研究了下,发现在输出到文档的时候,sys.stdout.encoding会变成非U的locale,而不是utf-8。虽然我平时也不这么干,但是保险起见搞了个

try: 
    print(self.title)
except: # It will have problem otherwise in certain env, such as when redirect output to '> 1.txt' 
    print(self.title.encode(sys.stdout.encoding, "ignore").decode(sys.stdout.encoding))

应付。这么搞了一波之后学校测试了一番没问题,很开心。结果回家之后一跑脚本报错了。

由于报错之后直接就关掉了,我于是先启动cmd然后里面再运行我的命令,结果这次报错的地方都变了。我不断地注释语句,最后得出的结论是,凡是print的地方都有可能出错——但是用VS Code的控制台(CMD)运行同样的命令就不会错。

折腾了很久之后,我想起之前的一个现象,用ebook-extract的时候,他会很自作多情地先把CMD的code page切成65001(理论上应该是UTF-8,但是微软的实现有很多缺陷经常被社区诟病)——我能发现是因为字体点阵高度会变,导致窗口变得很矮。我手动切CP(CMD命令:chcp 65001)试了下:

Active code page: 65001

C:\Users\Administrator>py -3 -c print('\u0142')
Traceback (most recent call last):
 File "<string>", line 1, in <module>

C:\Users\Administrator>

果然Python在65001的CMD下,输出任何非ASCII的字符都会直接报错(return?)。搜了下Python的bug tracker,开发者说这是Windows的bug,具体来说是在CP65001下,Win对Unicode字符错误地按ANSI来准备buffer,导致buffer大小不足导致。

其实calibre的所有命令行工具都有这个毛病。暂时不是很清楚为什么一定要切换到CP65001操作,而且最可恶的是用完还不切回来。顺便一提,还有个bug是运行完calibre的CLI工具后,在同一个CMD窗口进行复制操作会直接崩掉CMD,也是酷炫。

不过很奇怪地是,在VS Code里的CMD就不会有这个问题,不过VS Code的console一直都比较robust就是了。

既然知道了原因,解决方法也简单粗暴:直接在ebook-convert跑完后自行把CP切回来。我的函数是

import os, locale
def changeCodePageBack(): 
    cp = locale.getpreferredencoding().replace('cp','')    
    os.system('chcp '+cp)

这里用locale.getpreferredencoding()可以读取到当前电脑的默认非Unicode环境。

哦对了,我修改完的Py3版抽取res的脚本在这里。Rev.1是原py 2版,可以看diff。由于属于盗版原版,不要到处传播哈。

DVD压制:PAR, VFR, film/hybird…

(……能不能换个好点的标题。)

最近利用空闲时间把手头经常看的一些DVDISO都压一份观看用,结果发现这里面的小问题还不少。结果一折腾起来就没个头了。

这篇文章比较长,稍微给个简介。本文主要讲了这么几个问题(不按顺序):

  • DVD的PAR处理
  • Color matrix相关
  • DVD压制实践:
    • film DVD、hybrid DVD的压制细节
    • DVD回放时FPS问题(0.1%)以及带来的章节desync问题探究
    • 几款不同软件压制DVD的对比

Pixel aspect ratio(PAR)

第一个问题就是PAR。拿常见的可变宽荧幕NTSC来说,之前我都是压制的时候手动resize成848(最接近853的mod 14)×480之类的,没怎么细究过。最近想换用别人推荐的视频SAR不动,靠容器指定DAR的方式。结果用megui随便跑了一个,怎么出来的结果是…876×480?

打开megui的AVS creator一看,才发现默认的DAR是什么“ITU 16:9 NTSC (1.823169)”,而不是16:9。稍微研究了下,发现这个问题相当复杂,而且你去看ITU的Rec.是不能直接得出什么结论的——ITU是描述的analog(信号是多少xx赫兹之类的)的情况,所以到底是什么PAR其实是根据ITU的recommendation(主要是601和470,可能还有别的)推算出来的。

好在,这种问题有很多人已经研究过了,这里先提供一些链接(维基百科也可以先看看,看了下虽然没有明显错误,但是说得颇混乱):

简单总结下。

  • NTSC的4:3的PAR应该是4320/4739,或者更准确的数字:38800/42651。这是用信号扫描时间推算出来的。如果是anamorphic widescreen(可变宽屏),那就乘以4/3变成5760/4739(更准确的数字:155200/127953)。
  • 之所以是这个数字,是因为NTSC数字视频的实际视频区域(active picture)应该是710.85×486(这个数字是用标准中的analog的sample rate/time推算出来的)——这个区域应该被display为4:3或16:9。稍微算下就知道,5760/4739的PAR可以保证710.85/486的SAR正好变成16:9的DAR。
  • 1.823169(8640/4739)这个数字,正是套用同样的PAR,去乘720/480这个DVD的SAR得到的DAR(720/480*5760/4739=8640/4739)。
  • 可以看到,由于DVD的720×480的宽是大于有效区域的。所以,理论上DVD视频的左右两边应该有黑边。
  • 等等,那486比480多出来的那6条线呢?那6条线在制作DVD的时候直接出血掉了。实践上,可能近年制作/录制的NTSC视频来说,这6条线其实从一开始就不存在。你可以理解为把上面那个实际视频区域710.85×486按比例缩放——变成702又2/27×480就好。剩下的自然是黑边啦,所以你有时候也会见到704×480的DVD(少见),因为要完整呈现有效区域,没必要720(720/704/480这些都是16的倍数。MPEG1/2年代编码器的限制之一。要不然也不用折腾这么多了)。
  • 所以,当你入手一个720×480的DVD,保证正确比例的做法是用5760/4739的PAR来encode为一个876×480的视频(875更接近,不过如果是encode一般有mod 2限制。或者直接指定DAR)。结果应该是视频内容部分是真·16:9,两边多黑边。

但是!这些都是理想情况,实践上有一个非常重要的区别。现在,许多(可以说绝大多数)新的DVD早已经是原生digital制作,也因此已经完全按照720×480无黑边制作了——也就是说,正确的播放方法不应该遵守ITU,而是直接拉成16:9播放/压制(相当于PAR=32/27)。当然,如果你发现视频左右两侧有不小的黑边,那应该考虑用ITU试试(或者简单地直接切黑边后拉到16:9)。至于4:3,似乎还是遵守ITU的比较多,毕竟都是很老的碟子。可以参考这个用户的rule of thumb。另外,据我观察播放器在playback DVD的时候都是直接16:9/4:3了事,没有用ITU resizing的。还好对播放比较新的DVD没影响。另外,不要小看这一点AR变形,如果对比观看,用对了和用错了还是相当明显的。

编辑:关于上面这段,不一定一定正确。请参见补遗文

Color相关

这个其实和DVD没啥关系,但是到戏肉之前先垫垫场(啥)。

其实就是我前几天没事儿干观察了下手机拍摄的视频的元数据,发现居然是:

Color range : Full
Color primaries : BT.601 PAL
Transfer characteristics : BT.601
Matrix coefficients : BT.470 System B/G

这里的奇怪之处有:1. 居然是full range;2. 居然是PAL;3. 居然是BT.601。

前两个只是奇怪,最后一个就不太对,因为HD视频理应用BT.709。但是,如果录制的时候的转换矩阵确实是用601,至少有tag,播放的时候不会有问题。

这其实不是重点,只是因为这个,我突然好奇起来Color primaries、Transfer characteristics、Matrix coefficients这仨有啥区别,尤其是这里还出现了个我没见过的BT.470 System B/G。

Doom9这个帖子非常有帮助。不过我只是浅尝辄止,所以下面非常粗略地总结下。

  • Color primaries:指定gamut的范围(三个原色[即“primaries”]的位置,白点位置)。理论上回放时只和校准(显示器)颜色有关,所以绝大部分播放器不会使用这个属性。
  • Transfer characteristics:指定gamma ramp。譬如,你可以指定为linear等。几乎所有播放器都无视,直接用那个标准的nonlinear的ramp(见下文)。
  • Matrix coefficients:这个是比较关键的也几乎是唯一会被播放器读取并采用的,指定了YUV和RGB的转换矩阵。

这几个属性的标准方面:

Color primaries有多种标准(维基有个):

  • BT.601 NTSC = SMPTE 170M = SMPTE C
  • 老的NTSC 1953 primaries = BT.470 System M = FCC 1953
  • BT.601 PAL = BT.470 System B/G
  • SMPTE 240M:BT.709之前的一个HD标准,未被广泛使用
  • BT.709
  • BT.2020 = BT.2100

Transfer characteristics:

  • BT.601 = BT.470 System B/G (PAL) = BT.470 System M (NTSC) =SMPTE 170M (NTSC) = BT.709,即:所有常见标准的gamma曲线其实都一样。
  • BT.2020:根据维基百科,也一样,只不过常数精度更高

Matrix coefficients:

  • BT.601 = BT.470 B/G (PAL) = SMPTE 170M (NTSC) ,至少在H264标准里是这样,即SD的都一样。写成不同的tag一般只是便于识别源是PAL还是NTSC
  • BT.709
  • BT.2020的没细看,八成不一样就是。

Progressive soft-telecine DVD压制

我在前文提过这种DVD,说白了就是实际是23.976p,靠加tag来soft-telecine到29.97播放。也提到实际PC播放的时候其实还是按23.976放。现在知道这种视频一般在业内叫“film”,下面也这么叫。

压制这种视频其实不是很复杂,我们以every♥ing!专辑《Colorful Shining Dream First Date♥》的DVD为例。这个DVD里收录了一个MV和Live 1的特辑,长度很长(这个后面很关键)。这个视频是纯film,用dgindex做index,显示99.99% FILM。用megui的d2v+avs+x264组合压制的话,会以23.796恒定帧率压制,出来的视频也OK。但是有问题的,也是我纠结的部分,是章节文件。

megui压制film DVD的章节错位问题

为了便于叙述,先简介一下这视频的一些基本信息。首先,这个DVD分为三个title,title0是全部播放,19章节;title1是只播放MV,1章节;title 2是只播放演唱会,18章节。我主要是在压制title 2。使用的工具是megui的OneClick encoder,但是本质就是上面的工具链,多了一部自动提取chapter封装MKV而已。

对于压出来的视频,我们记录下:帧数,时长,帧间隔。

统计帧数有多种方法,比如用mediainfo

mediainfo --Output="Video;%FrameCount%" filename.mkv

另外,对于MKV格式,用mkvextract可以获取timecode表:

mkvextract.exe timecodes_v2 filename.mkv 0:timecodes.txt

这个相当实用,列出了每一帧的时间,用此可以推算出上面的所有需要的信息。数据的数量会比mediainfo显示的帧数多一个,相信是因为最后一个数字是最后一帧的结束时间。注意,不要用PotPlayer的OSD等方法来看帧数,那个对于VFR的视频完全不准,无法依赖(后叙)。据说用ffprobe也能抽取类似的信息,不过我试了下不是很好使没仔细研究。

这里,我这个视频的第一帧(帧0?)是从0ms开始,总帧数是115273。最后一帧的结束时间是4807844.708,也就是1:20:07.845(视频长度)。可以算下fps:

fps = 115273/[(4807844.708-0)/1000]=23.97602398

符合。我们也需要观察一下每帧的间隔。这里,每帧之间的差距都是41或者42ms。

但是问题出在chapter上,这里,这个视频的chapter是:

00:00:00.000 : en:Chapter 01
00:06:44.501 : en:Chapter 02
中略
01:29:10.006 : en:Chapter 17
01:37:49.923 : en:Chapter 18

可以看到,这最后几个chapter的开始时间根本就比视频长度还长了,肯定不对头。于是,回过头来看看DVD里的本来的chapter长啥样。手头能直接从IFO里提取chapter的工具只有megui自带的chapter creator和chapter grabber两个。megui提取的结果如下:

CHAPTER01=00:00:00.000
CHAPTER01NAME=Chapter 01
CHAPTER02=00:05:23.601
CHAPTER02NAME=Chapter 02
中略
CHAPTER17=01:11:20.009
CHAPTER17NAME=Chapter 17
CHAPTER18=01:18:15.943
CHAPTER18NAME=Chapter 18

看上去靠谱多了。这里要注意到megui这块显示“29.97”,我选成23.976,就会转换成上面那个错误的时间戳。看来,megui虽然识别出了视频是film,但是对chapter没有做正确的处理,多进行了一次不必要的29.97/23.976转换

Chapter grabber提取出来的时间也是和megui的chapter creator一致的。但是chapter grabber似乎有点弱鸡:无法正确读取VIDEO_TS.IFO只能选VTS_01_0.IFO,而且只能打开title0。

那么,我们直接提取出这个时间戳试试。封入MKV,就会发现……时间还是不对。这次不是滞后,而是提前,但是提前的不多。

DVD回放FPS的1000/1001问题

百思不得其解之下,我直接打开DVD播放,确认了下里面的章节时间确实是最后一个为01:18:15.943没错——但是时间点在这里就是对的。仔细定睛一看:怎么总时长是01:20:03?!

和我们压制出来的视频的1:20:07.845长度对比下,相信眼尖的可以发现——两者的差距正好是24和23.976。也就是说,这种FILM的DVD,虽然vob的元数据写的清清楚楚23.976,但是在按DVD播放的时候,却是按照24fps来播放的。我试了Pot/MPV/MPC-HC/BE,全都一样。同理,如果播放title0(整个DVD),会显示1:25:21,压片出来则是1:25:26,错了5秒。我还尝试了用dgindex提取音轨,长度确实是01:25:26。

对NTSC稍微有点了解的应该知道这个1.001倍差是怎么来的,这里不赘述。但是应该和我们遇到的现象无关——既然元数据是23.976,为什么要自作多情按24放呢?我在网上搜了半天,确实有搜到一个人提到这个问题:他遇到的是标准的60fps/59.94fps(或30/29.97)的情况下,直接回放DVD和megui压制出时间不同外加chapter偏移。可见这个问题是普遍的,和film其实没关系。更重要的是,他是用物理的Sony DVD player播放出60fps的,所以可见至少我们这些软件是真实模拟了DVD碟机。

虽然知道了问题的普遍性,但是依然不清楚为什么。不过这个差距实在是太小,所以在片子短的时候根本就很难注意到。另外,似乎直接播放VOB文件也会有这样的问题,但是时间太短看不真切。

顺便一提,获取真实帧率除了上面说的timecode大法,可以利用PotPlayer开启转换滤镜后的OSD

QQ图片20180602012354.png

(不开转换滤镜没括号那个数字,看不出来。)当然有波动,所以这种方法对于区分24和23.976还是无能为力了,主要用来看是24还是30。这里可以看到,视频的元数据还是29.97,最后的输出则都垂直同步到60fps左右了。

既然知道了原因,接下来我们的处理方式就很简单了:将DVD里的chapter直接抽出来(规避上述Megui的不必要的29.97/23.976转换BUG),然后手动做一次24/23.976转换(变慢)。得到的结果chapter文件:

CHAPTER01=00:00:00.000
CHAPTER01NAME=Chapter 01
CHAPTER02=00:05:23.924
CHAPTER02NAME=Chapter 02
CHAPTER03=00:09:11.552
中略
CHAPTER17=01:11:24.293
CHAPTER17NAME=Chapter 17
CHAPTER18=01:18:20.643
CHAPTER18NAME=Chapter 18

合成到之前的MKV里(记得删除老的chapter),眼睛收货,基本完美。

其他软件对比

虽然算是搞定了,但是我还是好奇用其他软件进行转换会怎样,能自动处理好吗?

makeMKV

首先让我们来试试几个纯remux的选项。MakeMKV一直是我比较信赖的软件,而且对多title支持很好。我转了一份title02出来,帧数为115273,OK;最后一帧结束时间是4807836(=1:20:07.836)倒是稍有出入,不过换算下来fps是23.9760674依然OK。

但是MakeMKV出来的视频有个问题,就是帧间隔不一致。和之前megui压制出来的不同,其帧间隔表现为33/50/34/50……的循环序列。虽然平均值还是23.976fps,但是如果真的这样播放,岂不是会很怪?可惜,肉眼是看不出有什么区别,还是得稍后求救doom9大神。

补记:Doom9没大神救我,不过MKVToolnix的作者称此为正常现象(但是没回答我是否会影响播放的问题:()。

Chapter方面,则是

CHAPTER01=00:00:00.000
CHAPTER01NAME=Chapter 01
CHAPTER02=00:05:23.923
CHAPTER02NAME=Chapter 02
中略
CHAPTER17=01:11:24.280
CHAPTER17NAME=Chapter 17
CHAPTER18=01:18:20.629
CHAPTER18NAME=Chapter 18

虽然和我之前24/23.976转换之后的基本一致,也是准确的。但是还是有个趋势我不是很喜欢:从chapter1只错1ms到最后一个chapter错了13ms为止,两者的差距正好是个单调增,这给人的感觉是其中一个有在转换的时候浮点精度不够导致的(比如用了23.976而不是24000/1001)。还好,1个半小时的视频都只有这点差距,即使是强迫症的我也能接受。

MakeMKV还有一点,就是视频的元数据是保留了那些tag(variable即VFR后来讲):

Frame rate mode : Variable
Frame rate : 23.976 FPS
Scan type : Progressive
Scan order : 2:3 Pulldown

所以还会被播放器识别为29.97(不影响播放)。

mkvtoolnix

另外一个remux的选项就是mkvtoolnix。由于mkvtoolnix只能傻傻地拖vob进去(倒是如果你拖VTS_01_1.VOB会自动识别后面的是append,但是注意别拖VTS_01_0.VOB进去,0是菜单那一帧),所以我们只能比较title0(全部播放)。具体就不赘述了,出来的帧数122905、最后一帧5126154(1:25:26.154)、fps=23.97606471和MakeMKV封title0完全一致。另外帧间隔不一致的问题也是一样,虽然两者的timecode并不是100%一致(大概每1秒钟就会有前后两帧各错1ms,囧):

001

不过我比较了下两者的元数据…

Writing library : libebml v1.3.6 + libmatroska v1.4.9
Writing library : libmakemkv v1.12.2 (1.3.5/1.4.7) win(x64-release)

……喂你这“libmakemkv”括号里的版本号很可疑啊,其实你就是用的完全一样的libebml和libmatroska来制作MKV的吧啊喂。说不定把两者版本更新为一致连上述的不一致都不存在了。

哦还有就是mkvtoolnix这么搞是没有chapter的,也不支持IFO中提取。我还顺便看了下remux出来的PCM的音轨的timecodes,是精确的30fps。

HandBrake

试完这个我们再来试试HandBrake(下称:HB)。HB也是个free(的“一键压片”工具,之前算是用过一次。虽然压制功能弱一些,但是对DVD的处理应该还是有其独到之处。

如果要和我们之前的结果进行比较,有个地方要稍微注意下,就是FPS的设定。默认的选项是peak=30fps的VFR(可变帧率),所以压出来的片子有可能会有部分非23.976的部分。最好改成CFR=same as source的方式。这里,我两种都试了,不过变量没控制好,CFR使用的编码器是x265 slow crf 18,VFR用的是x264 slow crf 18,不过根据另外一组实验,encoder的不同对结果没影响。

结果方面,两者的characteristics分别如下:

获取方法 参数 CFR VFR
Video timecodes 开始ms 0 21
 (from mkvextract) 结束ms 4808053.708 4808085.708
Diff. 4808053.708 4808064.708
Frame Count 115278 115273
折算FPS 23.97602169 23.97492692
mediainfo Video dur. 4808058 4808058
Audio dur. 4808054 4808077
General dur. 4808054 4808077
Frame Count 115278 115278

这张表里主要有以下几个奇怪之处。

  1. VFR那个视频,用mediainfo获取到的frame count和数time codes的结果在不一样,差了5帧。个人认为timecodes这个数据是准的,而mediainfo的结果是建立在CFR的假设下得出的。
  2. 其实,这个“VFR”版只有第一帧是非23.796——帧0的时间是21ms,而帧1的时间是282ms,间隔了261ms。按照23.976p的正常40-41ms间隔计算下,正好差不多可以补上那5帧的差别(这1帧顶别人6帧)。当然,由于VFR的存在,折算fps和23.976稍微有点偏差,这是正常现象。
  3. 可以看到,Mediainfo汇报的各种duration和timecodes报告的有细微差别。首先我不懂这个“duration”到底是简单的最后的结束时间,还是还要去除掉开始时间。但是从VFR那个可以看到,不管去不去掉21ms都对不上。即使是CFR那边也一样,反而和audio的能对上…相比之下,megui压制版用Mediainfo汇报出来的数据就完全一样。
  4. 可以看到,VFR版的Video是从21ms开始,同时,其audio部分则有个-21ms(相对video)的delay tag,所以audio其实是从0ms开始。个人觉得这视频不从0ms开始正是为了和音频同步而已。

另外,如果和上面其他几款软件的数据对比,会发现反而是VFR的和之前的frame count相同(115273),CFR的不同。对比时间戳的话,HB的CFR的时间戳几乎和megui的结果完全重叠,只是在最后比megui版多出来5帧(约200ms)。看了下HB的log,有这么一段(这是CFR的):

[06:16:51] reader: done. 1 scr changes
[06:16:53] work: average encoding speed for job is 15.803467 fps
[06:16:53] vfr: 115278 frames output, 0 dropped and 5 duped for CFR/PFR
[06:16:53] vfr: lost time: 0 (0 frames)
[06:16:53] vfr: gained time: 0 (0 frames) (0 not accounted for)
[06:16:53] mpeg2video-decoder done: 115273 frames, 0 decoder errors
[06:16:53] sync: got 115273 frames, 115157 expected
[06:16:53] sync: framerate min 23.981 fps, max 29.970 fps, avg 23.976 fps

基本来说,源MPEG2那边确实只有115273帧,但是是VFR。所以,为了VFR->CFR,复制了5帧出来共计115278。至于megui那边则似乎直接没有这样操作,强行把当做CFR逐帧转了。

同理,VFR则不需要对帧的数量进行调整,直接保证帧间隔和原来的源一致就行了。这是VFR的:

[08:52:09] reader: done. 1 scr changes
[08:52:10] work: average encoding speed for job is 62.888802 fps
[08:52:10] vfr: 115273 frames output, 0 dropped and 0 duped for CFR/PFR
[08:52:10] vfr: lost time: 0 (0 frames)
[08:52:10] vfr: gained time: 0 (0 frames) (0 not accounted for)
[08:52:10] mpeg2video-decoder done: 115273 frames, 0 decoder errors
[08:52:10] sync: got 115273 frames, 115157 expected
[08:52:10] sync: framerate min 23.981 fps, max 29.970 fps, avg 23.976 fps

哦差点忘了说,无论是CFR还是VFR,帧间隔也是正常的40/41,而不是直接remux那种奇怪的不等距的间距。

让我们来看看chapter时间,毕竟最开始我们就是在研究这个。

CFR那边是

00:00:00.250 : :Chapter 1
00:05:24.157 : :Chapter 2
中略
01:11:24.530 : :Chapter 17
01:18:20.863 : :Chapter 18

VFR那边是

00:00:00.261 : :Chapter 1
00:05:24.127 : :Chapter 2
中略
01:11:24.500 : :Chapter 17
01:18:20.832 : :Chapter 18

虽然稍有区别,但是实际时间点都是精准的。准确地说,CFR版本和megui、makeMKV提取等最后一章节的开始点是完全一致的一帧;VFR版要提前一帧。但是我感觉VFR版的更对,因为VFR正好是在转场(片尾credit的)开始的第一帧,别的都是第二帧。嘛,这种小细节并没有别人在意……

哦我还看了下HB的log,他对chapter是这么操作的

[04:15:22] scan: chap 1 c=0->0, b=188473->379436 (190964), 323600 ms
[04:15:22] scan: chap 2 c=1->1, b=379437->514336 (134900), 227400 ms
[04:15:22] scan: chap 3 c=2->2, b=514337->657180 (142844), 244000 ms
……

这是读取原始的。

[04:15:23] sync: first pts audio 0xa0bd is 0
[04:15:23] sync: first pts video is 19770
[04:15:23] sync: "Chapter 1" (1) at frame 1 time 19770
[04:24:26] sync: "Chapter 2" (2) at frame 7767 time 29171392
[04:31:23] sync: "Chapter 3" (3) at frame 13225 time 49659360
……

这是写入的时候。虽然看不懂但是有个专门的category叫sync就感觉很专业!

这里感慨一句,所有的这种细枝末节,都是软件(不限于HB本身,还有各种lib等)作者一点点抠出来的啊。想写一个这种项目,尤其是比较底层、又是video这种历史包袱估计比山还高的,真是不容易。

几种压制方式的画质比较

既然片子都压出来了,我们顺便比较一下几种方式出来的画质几何,也给以后做个参考。对于这种我还会保留原盘的,其实只要画质不太差就行,所以我都是无脑slow crf 18。参与比较的有megui压出来的x264 10bit(hi10p)、x265以及handBrake压出来的x264和x265。

比较的结果比较出人意料,差别还挺大的。1:1播放(LAV+MadVR渲染,不过既然大家都一样应该算公平),细节最好的明显是x264 hi10p:

001

比较黄框部分,尤其是在HB的x265里,衣服的兜儿的线直接就没了……明明哪边都没有开denoise。当然你可以说x265的压缩噪声少,但是这种少法我不要啊!

这里有个奇怪之处是同样的参数为啥megui和HB的x265的区别肉眼可见。我比较了下log:

002

除了发现HB的版本老得快有代差以外(嗯,我承认我才发现我的HB是去年4月的旧版…)别的没啥特殊之处。不过最后出来的码率低了快10%,有点画质区别也是正常吧。

x264(hb)和megui的x264 hi10p的区别倒不算很大。体积方面(只计算视频),

HB-x264: 1.23 GB
HB-x265: 844 MB
megui-x264-10bit: 1.18 GB
megui-x265: 952 MB

我于是在想,同样的crf参数,在x265和x264到底能否代表同样质量?但是无论如何,按理说HEVC的目的不是号称同样质量减少一半体积嘛,这点程度的体积差给我打败x264啊!

Film的反交错问题

我们已经知道了film是p,所以不用反交错。用megui压制的时候,那个自动反交错功能会根据DGIndex的汇报判断出是99.99% film所以禁用反交错。

用HB则稍微有点问题。默认的情况下,开启的是decomb滤镜。但是他这个检测感觉并不是很强壮,这是我压title2 chapter1的结果:

[06:56:15] comb detect: heavy 490 | light 1752 | uncombed 5523 | total 7765
[06:56:15] decomb: deinterlaced 490 | blended 1752 | unfiltered 5523 | total 7765

可以看到,其检测结果是有约2000多帧有heavy或light的comb,所以进行了deinterlace和blend。这显然不是我们想要的,因为理论上会降低画质。换成yadif的话没有对应的log,所以不知道是如何处理的,但是估计也差不多。所以,这里我们最好手动关闭deinterlace才行。

(不过,我稍微对比了下画质,虽然有不同但是也很难讲到底有变差多少,囧)

外一则:播放多title DVD的几个现象

在本文撰写过程中(好吧其实是半个月前),我发现各大播放器对于多title的DVD支持,尤其是进度条,都有各种谜之BUG。这里以Pot和MPC举例,其他播放器根据我的实验,也多少都有问题。播放的片还是上面那个。

问题1:直接播放VTS_01_0.IFO的问题

实话说我也没懂VIDEO_TS.IFO和VTS_01_0.IFO这俩有啥区别,总之如果直接播放VIDEO_TS.IFO,一切正常,可以在菜单里选不同的titile等。但是如果直接播放VTS_01_0.IFO,虽然内容看上去是title0(所有内容,从MV开始),但Pot里视频长度会变成奇怪的1:31:10(变长了),MPC-HC那边会变成1:20:08(也就是莫名是title2的23.976速度下的长度——不知道是不是巧合)。具体为什么我没细究,我记得之前对比了下播放的帧速也真的不太一样。

另外一个现象是拖进度条和播放到某个时间点的结果不一样。比如,在MPC里,如果你正常播放,到4:10还依然是MV(MV的长度是05:18左右);但是如果你手动拖进度条到4:10,会发现已经在Live的部分了……Pot那边虽然没这诡异的问题,但是chapter的时间点也完全不对。

问题2:直接播放VOB

这个仅限于直接播放VOB1,估计也是因为多title导致的。VOB1在MPC、mediainfo等显示duration只有09:29,这个根据体积计算就知道明显不对,Pot里显示14:28稍微靠谱些,但是实际上单独VOB1用mkvtoolnix封装下就知道真实长度应该是14:47左右。然后MPC那边又有“拖进度条和播放到某个时间点的结果不一样”的问题——again,还是多title复用视频片段的锅。所以,看DVD的时候,尤其是比较长的有多章节、多title的情况下,还是老老实实用VIDEO_TS.IFO启动,或者makeMKV封装下再看。

Hybrid DVD压制

上面说了这么多还只是纯film的视频。还有一种更恶心的:混合film和普通29.97fps interlaced视频的。这手头也有个例子:TrySail的一专《Sail Canvas》初回的DVD。不过,这次只有一个title0,两个chapter。其中chapter1是MV,是film;chapter2是花絮,是普通的29.97 interlaced,最后几秒Aniplex的logo又变回film。

Remux

这种视频,用MakeMKV remux是会自动使用VFR的方法来处理,播放时没有问题。Interlacing的问题至少我这边LAV可以实时监测并tag,给下流处理,其他解码器不清楚。

※刚发现其实有点小问题:如果你按照顺序播放,前面(根据MadVR的OSD)会标记interlacing off,后面自动变成on,正常;如果直接拖动到后半部分,也会切换成on,正常;但是你一旦你on了之后,再拖回part1,就不会off了囧,不知道是纯OSD显示BUG,还是会真的影响播放(比如对progressive的内容强行加了deinterlacing),姑且去Doom9问了。

需要注意,上面提到的各种“问题”都还在,比如MakeMKV压出来在23.976p的部分奇怪的不等间距time code;比如chapter要加1‰偏移(这次的chapter 2的开始时间,在DVD是4:30.000,用MakeMKV封装之后就会自动变成4:300.270)。

Encode

压制的话,用HandBrake处理起来是最得心应手的——可以直接压制成VFR的视频。这个也是我比较推荐的做法。chapter的处理也全自动。HB也可以选成CFR输出,我试了下如果选“same as source”会选择23.976(毕竟上来先是这个),后面的会被转换一次帧率。但是30转24的效果比较差,可以感觉到卡卡的,不推荐——即使真的想转成CFR,也用29.97吧。


补记:HB压制VFR的MKV的时候,metatag会不对(会显示CFR+一个错误的帧率,一般是原始视频的起始帧率)。虽然不影响播放,但是有强迫症的可以选择用mp4输出;然后用MKVtoolnix封装回MKV。即使你已经用MKV输出了,你也可以用ffmpeg先remux成MP4,再封装回MKV。注意几点:

  • 直接重新remux MKV到MKV不行。
  • MP4->MKV要用MKVtoolnix,不能用ffmpeg。Ffmpeg也会有tag写成CFR的问题(我怀疑HB用的就是ffmpeg的lib)。
  • 这样操作一番还能解决一个问题,就是HB压出来的MKV的metatag还没有每个stream的bit rate的问题。

补记的补记:去HandBrake问了,原来其实不是metatag不对。其实,MKV的规范根本就没有“帧率”这个header——或者说MKV这个容器根本就没有帧率这个概念,因为具体的帧时间控制完全是靠timecodes来的。我们能Mediainfo里看到的tag纯粹是MI自己猜测的,所以没有参考性。至于为啥MP4 remux回MKV就有,那是因为原video stream本身的这类信息可以被继承下来并被MI读取到。同理,HB压出来的MKV没有bitrate也是同理——规范根本没有这个header。不过,MKVToolnix默认会自动计算bitrate(以及一些其他)并存到“tag”(一种可以往MKV添加任意自定义元数据的方法)里,所以MKVToolNix封出来的MKV一般还是能显示bitrate的。但是HB并不适用MKVToolnix来封装的,所以就欠奉了。


至于deinterlacing,由于HB这边选择不多,就用默认的decomb吧,其实我这DVD我看了下,后面的又是假interlaced(即两场同时曝光,实质progressive),所以什么都不加直接让编码器weave都没问题,也没法比较了。

megui那边就比较麻烦,据说用了AVS脚本压,就只能CFR;所以如果想压VFR,要么直接送x264,要么用AVS压完后自行搞出timecodes封装。我不是很想折腾,就试了下直接压CFR。开启auto deinterlacing的前提下,出来的结果还是没大问题的,CFR 29.97fps,23.976fps的部分会被比较正确地转换成29.97。如果不开deinterlacing,23.976p的部分会被直接telecine (2:3 pulldown) 成29.97,经典的5烂2效果我就不用说了(顺便一提用DGIndex预览记得去掉“Honor pull down flags”否则也是一样)。不过别忘了章节的时间问题,还得手动操作一下。

我也试了下用megui去直接encode MakeMKV remux的mkv,和直接用DVD压好像没啥区别,至少VFR的MKV当source看来是能处理。

哦还有一点,千万不要用megui OneClick encoder的“Don’t encode video”(remux)功能,完全狗屎,第一,他会按原始视频的平均FPS压片,出来个28.84 CFR的鬼东西,视频速度全乱了,音频也desync。其次,不知道为啥,这个remux的速度慢的出奇,甚至比一些preset下的encode还慢……

分段处理

其实还有个不错的方案是直接分割,然后两段分别用23.976 / 29.97 CFR。但是用megui按章节分割又是难死,所以还是用HB做这个吧,可以选chapter压制很便利。就是记得1点:由于第一部分和第二部分开始于同一个VOB里,所以元数据只有第一部分的,所以如果你压第二部分选CFR(same as source),会很尴尬地变成23.976fps。所以手动选一下29.97吧,或者直接上VFR,本质也会是29.97。其实推荐VFR,鬼知道手动选29.97会不会出现某些帧没对齐所以进行多余操作之类的,但是选了VFR之后元数据里的nominal FPS又会依然是23.976看着很不爽……

哦对,HB我升级了新版,发现也支持x264 hi10p了,很不错哦。

结语

写这文章的时间都能写出一篇论文了,光excel都做了7个表,用了几十G硬盘,现在什么也写不动了。总之我的结语就是DVD必须死,NTSC必须死。

最近遇到的几个视频相关的问题(二)

本来想直接在前文里追记,但是正好有点别的东西一并写了。

Progressive的DVD

上文中说到“发现近年的DVD大多都是progressive的了”,其实相当不准确。感谢boday的指点,其实准确地说:近年的DVD依然大多是交错的,但是确实存在一种,(例如TrySail的好几张碟都这样)实质是Progressive的DVD,元数据长这样:

Width : 720 pixels
Height : 480 pixels
Display aspect ratio : 16:9
Frame rate : 23.976 (24000/1001) FPS
Color space : YUV
Chroma subsampling : 4:2:0
Bit depth : 8 bits
Scan type : Progressive
Scan order : 2:3 Pulldown

这种应该叫做“软Telecine”,可以看到帧率是23.976p而不是30,其原理就是完全按照Progressive来存储,但是加了个2:3 Pulldown的metatag,所以DVD player播放的时候会即时进行telecine来pull到30i。当然,对于电脑播放的情况,解码器会直接按照24p播放,也不需要进行IVTC。

看不出交错的隔行视频

前文提到有些HDTV明明是隔行却看不出任何交错现象。这个问题后来想想简直太弱智了:因为人家就是用逐行拍摄、然后把每一帧拆成两场罢了,由于根本就是同时曝光的两场,所以weave回去自然不会有交错条纹……

MKV的音频Delay“问题”

后来问了作者了,作者到也很直接:“我没什么好说的,有很多原因可能导致这个,比如sequence不是从视频关键帧开始等等”。总之我理解就是他是说这个delay区别并不是什么bug,而是有技术原因在背后罢了。

上面算是补记,下面是一点点新东西。

PotPlayer使用外挂滤镜的截图问题

Pot使用外挂解码器(Lav)之后,截图功能就变得稳定性极差。估计多出自Pot截图的时间点和渲染的步骤不同步/不一致造成。最基本的错帧问题大家都知道了,就不提了,提几个别的。

其中最滑稽的问题是,如果开启了Pot的转换滤镜,会出现播放中截图暂停后截图结果不一样的现象。其不一样之处主要是缩放质量,缩小播放尺寸+用实画面截图(ctrl+alt+C)就可以发觉。

001
Lav+Pot转换滤镜实画面截图对比:先暂停 vs 播放时直接截图

顺便一提,如果不开启Pot的转换滤镜,倒是没有暂停和不暂停的区别了,但是截图出来的依然和你实际MadVR渲染出的高质量缩放是不一样的,在缩放倍数较大的时候很明显。当然,这个倒也是意料之中了。

002
实画面截图 vs 手动prtScn(200% NN放大)

说回开启了Pot转换滤镜会出现播放中截图和暂停后截图的结果不一样的现象,其实有另外一个更奇葩的:播放时截图Pot的yuv->rgb的转换矩阵和暂停截图不一致。这个现象经常在没有很鲜艳的颜色时不是很明显,所以容易被坑。

具体说来,在“暂停后截图”时,Pot会和正常编码器/渲染器一样(?),自行根据视频metatag或者尺寸选择正确的转换。然而,在“播放中截图”时,Pot会套用这个设置:

003

这里的坑爹之处在于,和前文提到过的让Pot处理YUV->RGB的BUG一样,这里设成Auto也会永远用BT.601,即使视频是HD、且有显式Tag的。由于HD视频更为常见,所以我手动改成了BT.709,但是自然这样会导致SD视频又有问题。所以,如果真的要用Pot截图,还是老老实实暂停了再截吧。

MadVR缩放算法

另外没事儿干对比了下MadVR的缩放算法。简单来说就是chroma upscaling用NGU比Lanczos在某些大色块高锐度(比如我一直用的测试视频)还是勉强有肉眼可见的区别,虽然实际放片还是区别很小。我暂时改用NGU+low quality了,能把GPU的load压在20%左右,还算能接受。由于上述各种笔油鸡,下面截图用的PrtScn。

004

Downscaling/upscaling那边,我比较了几个没看出大区别,NGU太耗资源不考虑,我相对喜欢锐度高一点的,所以继续用Lanczos+anti-ring filter。

(顺便吐槽自己干的一个蠢事:用50%大小播片,然后好奇为啥NGU完全不耗资源……)

自动化破解Kindle图书到图片

21年3月3日更新:

最近老有人问,正好DeDRM Tool也升级到py3了,公布下自己写的一键提取脚本:

https://github.com/fireattack/extract_kindle

有问题去那里发issue吧。


今天购入了日亚的Kindle Unlimited,瞬间有一大堆电子版的杂志要转换格式。

数量多了再用上文提到的手动方法就不行了,会烦死。于是琢磨着怎么能自动化。首先我想到一个简单的办法:既然高清图片都在.res文件里,那就直接把.res文件关联到那个python2脚本,然后直接extract图片就完了。

方法么自然就是修改注册表,也很简单,就加这么一句:

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\.res\shell\extract\command]
@="py -2 \"E:\\sync\\code\\python\\_example\\DumpAZW6_v01.py\" \"%1\""

(根据自己具体目录修改)也好使,使用之后会自动把图片提取到和输入文件相同的目录里。

然而好景不长,我刚搞了两本,就发现有的是只有部分图片有高清图——所以你得从.azw里提取所有的标清图片,然后把有高清版那些页数逐个替换进去。如此看来,不写个脚本是不可能简单实现了。

要写脚本,我们用到的工具都得有命令行版本才行。从azw提取图片可以用calibre的命令行工具ebook-convert.exe,或者用上述提取res文件的脚本作者开发的KindleUnpack(这个其实有图形界面,但是可以直接调用kindleunpack.py)。

但是有个重大问题:这两个工具都不支持有DRM的书(见追记)。所以,我们得先破解。calibre的命令行工具都是无法支持插件的见追记),所以我们得找到那个DeDRM插件作者单独提供的stand-alone版本

好不容易调试好python 2的环境,结果这工具一用就报错(已经装了pyCrypto)。自己debug了半天,发现是他藏得很深的用了一个lzma库,我没安装。按照代码去装backports.lzma,结果……装不上。在issues里看了下,果然有人报过,说白了就是作者的setup.py写的太烂对Windows不兼容。更囧的是作者研究了下,发现他自己搞不定所以这个issue就这么在这摊着。

说到这里不得不吐槽下python开发的库管理简直烦,所谓的platform independent某种意义上是个笑话,有许多库的开发者可能只用Linux/MacOS,这已经不是第一个我遇到的在Win下连安装都有问题的库了,更不要说使用了。

还好有人在另外一个issue里提供了一个wheel(编译好的版本),虽然非常老(0.0.3,现在已经是0.1.0),但是能用(见追记)。

这里其实有个插曲,我一开始装了这wheel之后还是不能用(连直接from backports import lzma都能报错),于是我一怒之下把deDRM工具里ion.py调用lzma的部分给删掉了,结果反而能用了,因为lzma其实只有对付上文提过的Kindle最新KFX格式才用得到。至于lzma不能用的问题?我干掉几个后台卡住的python.exe进程之后就好了。

OK,工具准备齐全,我们接下来要做的就是几步走:

  1. 用上文所述的azw6抽取脚本提取res里的高清图片
  2. 用deDRM tool的Windows application破解azw文件
  3. 用Calibre的命令行工具提取破解后的no_drm azw3里的图片
  4. 对比两组图片,把3里的部分用1里的高清图片替换。
  5. (可选)利用calibre命令行工具获取meta tag来给文件夹改名
  6. (可选)自动打包

其中1、2两步都有source code,理论上可以import进我的脚本里,但是因为这是python 2写的我决定还是直接process.call

虽然调试耗了不少功夫,但是最后结果还是相当满意的。代码比较丑陋这里就不放了,中间遇到的两个坑:

  1. 那个抽取AZW6的脚本里有句话是平淡无奇的print self.title(这是py2,print书籍的title而已)但是如果是飞ASCII的标题经常会在控制台卡住,报错“no such file or directory”(??)(经常是我手动运行不卡,用VS调用就卡),烦得很直接注释掉了。
  2. Python的re.match原来和re.search不一样……不一样。

追记

5月20日追记三点。

1是backports.lzma的第三方wheel版可以在这里下载。另外,DeDRM工具那边也已经修改了代码,让缺少LZMA有正确的提示(也在说明里提到了要有LZMA),并且也增加了对pylzma(另外一个py2有的lzma库,上面那个链接里也有wheel可以下,PyPI好像也有)的支持。

2是解压电子书。如上文所述,我现在用的是Calibre带的ebook-convert。这个的本质是把电子书转成网页/压缩包的形式,然后再解压(命令行就是ebook-extract 你的azw文件.azw temp.zip --extract-to 解压到的dir)。有个小问题就是转换成网页的时间有时候会很长。所以我又去试了下那个KindleUnpack——快是快很多没错,但是这个有个问题就是出来的图片名并不是从1开始,感觉上似乎是直接按照原始包的section来命名,比如第一页经常就是40多编号了。这个在后续处理,尤其是替换HD图片的时候非常不方便,所以还是放弃了。另外提示一下,这个的GUI还是CLI都要用py -2运行:否则虽然GUI能跑起来但是实际转换是要报错的。

3是ebook-convert无法处理deDRM的问题。我上面提过这个工具不支持deDRM——其实我是错的。理论上,Calibre的命令行工具都支持各种插件的。事实上,如果你用命令行工具calibredb:

calibredb.exe add book.azw

来加入一本encrypted的书,会自动调用插件deDRM。

但是问题在于,那个deDRM的插件在设置里把自己的类型设成了“仅添加时调用”。所以,在单纯convert的时候,并不会参与。这个是我去mobileread的论坛问了下才明白(顺便一提,那边的人回复好快!我发帖不到半小时就N个回帖了…)。

我自行修改了那个插件的init.py,添加了on_preprocess=True,立刻就可以在ebook-convert里调用了。而且,似乎没什么副作用,不会影响正常转换noDRM的图书。我在犹豫要不要建议插件作者自行加上。

这么搞了之后,上面的流程的第一步和第二步就可以合并了。但是我暂时还没这么做,因为这样得保证使用环境的calibre必须装了修改版的DeDRM插件才能用,不是很便于部署。

追记2

上面提到的转换速度太慢的问题一直困扰我,后来我又研究了下,发现calibre其实是提供另外一个binary,calibre-debug.exe具有只解包不转换的功能,switch是-x

批量处理方面,唯一需要注意的是爆出来之后最后一张图片会是一张重复的封面的小图,没有必要,我目前观察所有拆的包都有,可以无脑删除。

至于上述3提到的修改calibre来直接支持DeDRM插件,考虑到可移动性的问题依然没有用,还是调用deDRM的py版本。

提取高清图片的脚本部分已经改写成py3并且直接调用了,参见后文