美剧截图指南


背景

笔者最近看了《致命女人》这部美剧,觉得台词都很贴近生活,对话也经常抖包袱,很有必要再回味一遍。可是一集40分钟,哪有那么多时间呢?而且,怎么样才能最快定位到自己想要找的精彩台词呢?一集240M的大小放在手机上也十分占用空间,而且自己的笔记都是放在iPad上。面对诸多不便,是时候展示一下程序员的倔强了。

过程

最简单的办法就是看剧本(或者字幕文件),但是作为一个学习能力一般的人,最起码的图文并茂还是要有的,于是脑海中出现了一个想法

把字幕对应的截图一一保存下来,整理成一个PDF不就行了。这样既可以通过视觉加强回忆,也可以通过文本快速搜索。wonderful!

首先查阅了网上提取字幕的方法,大多是OCR。因为视频里面的字幕都是"硬字幕"(即通过视频编辑软件写进去的,无法逆向提取出来,当然像mkv这种格式是可以的,但人人影视上下的是mp4,硬字幕实锤了),所以这种想法很自然,但也有几个问题:

  • 速度慢(显然)

  • 容易被噪音干扰(可能一帧有广告标语的画面也被截进来了)

  • 不能保证100%准确

当然最重要的一点是,我觉得一晚上搞不定,所以放弃了这个策略。换个思路,能不能先拿到字幕,然后计算出每一条字幕出现的时间,去视频指定位置截图就OK了。首先找到了字幕

下载之后发现格式较为简单的 srt 文件全部乱码了,只好硬着头皮去处理带了文字格式、较为复杂的 ass 文件,打开之后大概是这样:

......
Dialogue: 0,0:00:08.36,0:00:13.74,*Default,NTP,0,0,0,,{\an8\fscx120\fscy131\move(183.56,27.946,188.94,26.346,15,4000)}{\fs38\fsp8\fn方正兰亭特黑长简体\b1\bord0\blur0\shad6\fad(0,100)\c&H43D4E1&}{\fs50}{\t(15,3600,\fs16)}致命女人
Dialogue: 0,0:00:08.36,0:00:13.74,*Default,NTP,0,0,0,,{\an8\fscx120\fscy131\move(183.56,133.546,185.74,67.413,15,3600)}{\fs28\fsp8\fn方正兰亭特黑长简体\b1\bord0\blur0\shad6\fad(0,100)\c&H43D4E1&}{\fs28}{\t(15,3600,\fs14)}第一季 第一集
Dialogue: 0,0:01:12.55,0:01:15.33,*Default,NTP,0,0,0,,{\an8\pos(194.4,101.333)}{\fs28\fsp2\fn方正小标宋简体\b1\bord0\blur0\shad0\fad(50,0)}丈夫
Dialogue: 0,0:01:15.94,0:01:18.03,*Default,NTP,0,0,0,,我和贝丝·安高中时开始交往\N{\fn微软雅黑}{\b0}{\fs14}{\3c&H202020&}{\shad1}I started dating Beth Ann in high school.
Dialogue: 0,0:01:18.45,0:01:22.35,*Default,NTP,0,0,0,,她以前常给我做三明治还有缝衣服扣子\N{\fn微软雅黑}{\b0}{\fs14}{\3c&H202020&}{\shad1}She used to make me sandwiches and sew buttons on my shirts.
Dialogue: 0,0:01:23.17,0:01:25.00,*Default,NTP,0,0,0,,相信我  爱照顾人的女生\N{\fn微软雅黑}{\b0}{\fs14}{\3c&H202020&}{\shad1}I tell you, there's nothing sexier than a girl
Dialogue: 0,0:01:25.00,0:01:27.01,*Default,NTP,0,0,0,,最性感了\N{\fn微软雅黑}{\b0}{\fs14}{\3c&H202020&}{\shad1}who likes to take care of you.
Dialogue: 0,0:01:27.99,0:01:30.01,*Default,NTP,0,0,0,,我是在一次慈善晚会上认识萨蒙妮的\N{\fn微软雅黑}{\b0}{\fs14}{\3c&H202020&}{\shad1}I was introduced to Simone at a benefit.
Dialogue: 0,0:01:30.01,0:01:32.51,*Default,NTP,0,0,0,,她的出场太惊艳了\N{\fn微软雅黑}{\b0}{\fs14}{\3c&H202020&}{\shad1}Oh, the entrance she made.
......

我们需要的只有3列,开始时间、结束时间和字幕,于是文本处理开始,截取代码如下

    dos2unix ${originSubtitle}
    grep -F "Dialogue"  ${originSubtitle} > ${tag}
    sed -i "s/{.*}/|/g" ${tag}
    sed -i "s/\\\\N|/,/g" ${tag}
    sed -i "s/|//g" ${tag}

    cat ${tag}| cut -d "," -f 2,3,10,11 > tmp.txt
    cat tmp.txt > ${tag}
    rm tmp.txt
    dos2unix ${tag}

然后就拿到了一下文本

0:00:08.36,0:00:13.74,第一季 第一集
0:01:12.55,0:01:15.33,丈夫
0:01:15.94,0:01:18.03,我和贝丝·安高中时开始交往,I started dating Beth Ann in high school.
0:01:18.45,0:01:22.35,她以前常给我做三明治还有缝衣服扣子,She used to make me sandwiches and sew buttons on my shirts.
0:01:23.17,0:01:25.00,相信我  爱照顾人的女生,I tell you
0:01:25.00,0:01:27.01,最性感了,who likes to take care of you.
0:01:27.99,0:01:30.01,我是在一次慈善晚会上认识萨蒙妮的,I was introduced to Simone at a benefit.
0:01:30.01,0:01:32.51,她的出场太惊艳了,Oh
0:01:32.95,0:01:35.46,设计师礼服  挂满钻石,Designer gown
....

由于需要计算字幕的中间时间,需要先转化成毫秒,然后再转回来,考虑到 shell 是我用过最垃圾的语言之一,并且 不善计算和字符串处理,我一度考虑要不要专门写个子程序做这件事,但考虑到其他语言也不是很精通,只能又硬着头皮上了

# use lua is a lazy way
lua="lua5.3"

# hh:mm:ss.xxx to millisecond
# return (hh * 3600 + mm * 60 + ss) * 1000 + xxx
function hhmmss2millisecond() {
    originTime=$1
    hh=$(echo ${originTime} | cut -d ":" -f 1)
    mm=$(echo ${originTime} | cut -d ":" -f 2)
    ss=$(echo ${originTime} | cut -d ":" -f 3 | cut -d "." -f 1)
    xxx=$(echo ${originTime} | cut -d ":" -f 3 | cut -d "." -f 2)
    xxx=$(echo "0."${xxx})
    ${lua} -e "print( (${hh} * 3600 + ${mm} * 60 + ${ss}) * 1000 + ${xxx} * 1000 )"
}
function millisecond2hhmmss() {
    originTime=$1
    result=$(${lua} -e "print(math.modf(${originTime}/ 1000))")
    second=$(echo ${result} | cut -d " " -f 1)
    xxx=$(echo ${result} | cut -d " " -f 2 | cut -d "." -f 2 | cut -c 1-3)

    # ss=$(${lua} -e "print(${second}%60)")
    ss=$(${lua} -e "print(math.fmod(${second}, 60))")
    second=$(${lua} -e "print(${second} - ${ss})")
    result=$(${lua} -e "print(math.modf(${second} / 60))")
    minute=$(echo ${result} | cut -d " " -f 1)
    mm=$(${lua} -e "print(math.fmod(${minute} , 60))")
    hh=$(${lua} -e "print(math.modf(${minute} / 60))")
    hh=$(echo ${hh} | cut -d " " -f 1)
    echo ${hh}":"${mm}":"${ss}"."${xxx}
}
...
cnt=0
# cost much time
while read -r line; do
    cnt=$((${cnt} + 1))
    start=$(echo "${line}" | cut -d "," -f 1)
    end=$(echo "${line}" | cut -d "," -f 2)
    start=$(hhmmss2millisecond ${start})
    end=$(hhmmss2millisecond ${end})
    middle=$(${lua} -e "print( (${start} + ${end})/2 + ${offset}*1000 )")
    pos=$(millisecond2hhmmss ${middle})
    arr[${cnt}]=${pos}
    echo -ne "\rprocess ${cnt} / ${lineCount}"
done < "${tag}"

这样我就拿到了每条字幕出现的时间了,这里有两点需要注意下:

  1. offset: 由于前面插入了广告,所以字幕文件和实际字幕出现的位置会有一个偏移

  2. 不能把截图时间写在一个文件里面,然后在 while read 循环里面直接调用 ffmpeg,不然会出现以下错误

Invalid duration specification for ss: :2:6.135

也就是 : 前面的内容被吞了,但是注释掉这行就不会,思考之后发现是 ffmpeg 会影响 read,也就是说 while 循环里面的操作会影响他自己,造成预期外的结果,我当时真的佛了,大约有半个小时,我发现只要用 echo 输出,所有的行都是合法的,但是只要调用了了 ffmpeg 读到的文件内容竟然能变,在没有任何代码显式操作的情况下,一度以为撞鬼了。一个小插曲,明白过来以后,我把它存在一个数组里面,然后再单独处理数组,避免 ffmpeg 和 read 的冲突

cnt=0
for i in ${arr[@]} ; do
    cnt=$((${cnt} + 1))
    echo ${i}
    ffmpeg -ss ${i} -i ${originVideo} -vframes 1 -vf "scale=600:600/a"  -q:v 10 ${cnt}.jpg
done

注意几个细节

  • -ss:放在前面,可以节省运行时间,尤其是大文件

  • -q:v:不要太低,否则会让图片质量变大(设置前生成的pdf有60M,设置后只有10M)

  • -vf "scale=600:600/a": 缩放,图片没必要太大

最终效果

最后把这些图片用通过markdown转成pdf就行了

    out="out.md"
    echo "" > ${out}
    while read -r line; do
        cnt=$((${cnt} + 1))
        echo "![](./${cnt}.jpg)" >> ${out}
        echo "" >> ${out}
        echo ${line} | cut -d "," -f 3- >> ${out}
        echo "" >> ${out}
        echo -ne "\rprocess ${cnt} / ${lineCount}"
    done < "${tag}"
    exit

这里一开始想用 pandoc, 结果各种 latex 依赖需要处理,于是干脆转成 html,再用谷歌浏览器保存成pdf

后面如有时间,打算通过这种方式把权游和《破产姐妹》好好学习下!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值