为熊猫 TV 直播生成带弹幕的录播视频

2017-07-19 • Web 万象 • #弹幕 #熊猫tv #直播 #nodejs

最近在沉迷谜之声直播。由于有时候下班比较晚,赶不上开播,又偶尔对直播游戏感兴趣的,就会跑去找录播来看。这个主播十分好心,会在贴吧里上传自己的录播视频。然而由于主播自己的录播视频采用 obs 串流的同时录制,对弹幕肯定是无能为力的,于是就想着能不能通过什么办法把弹幕加上去。所幸,办法还是有的。

现在熊猫直播全站 https 后,弹幕数据都是走的 websocket 了,通过 Fiddler 之类的抓包工具可以轻松抓到。不过,知乎上有一篇详细的回答说明老版本弹幕格式,也有一些现成的项目用来解析弹幕,所以这里就不关注弹幕数据格式了。总而言之,我把这些东西封装成了一个 npm 库 pandan,可以通过 node.js 简单的获取一个房间的实时弹幕了。比如:

let room = new (require('pandan').Room)(426346)
room.on('room-danmaku', function ({data}) {
  console.log(`${data.from.nickName}: ${data.content}`)
})
room.join()

有了弹幕数据自然就好办多了。从抓到的数据包来看,有主播开播、下播事件,送礼、送竹子等事件,完全足够我生成弹幕了。通过一段小脚本,可以把弹幕格式转换成下面这个样子的 xml:

...
<d p="1830.6300048828,1,25,16777215,1415241251,0,Da8b0a0d,668464499">再见了我的青春</d>
<d p="1477.6800537109,1,25,52479,1415241254,0,35b92ad0,668464527">虐啊</d>
...

脚本比较丑,就不贴了,相关参数的解析可以看这篇文章。这里有个小问题,由于直播通常有几秒钟的 buffer,所以弹幕整个时效性会稍微延后一些。这个问题在你观看直播的时候不容易发现(因为大家都有延迟),但生成录播之后弹幕会很明显的延后。当然,解决起来也十分简单,将弹幕时间轴往前平移几秒钟就可以了。

然后祭出大杀器:danmaku2ass,一个能将弹幕转换成 ass 字幕的工具。执行类似这样的命令:

python3 danmaku2ass/danmaku2ass.py -o 2017-07-17-1942-09.ass -s 1152x648 -fn "Microsoft YaHei" -fs 30 -a 0.8 -dm 7 2017-07-17-1942-09.xml

就能生成 ass 字幕了。使用任何带字幕功能的播放器,都可以播放啦~看个例子:

example image

当然,并不是所有主播都会自己上传录播视频。如果你刚好也有这种需求,可以尝试在连接弹幕服务器之后,获取开播事件,并在开播之后通过 you-get 来获取直播录像。

2016: 我的浏览器 A - Z

2016-12-21 • Web 万象 • #浏览器 #我的浏览器a-z

A: https://aoishi.[REMOVED] 自己服务器的管理面板
B: http://bbs.i-cassell-you.com/forum.php 还是卡院
C: http://civitas.soobb.com/ 多少年没上了……
D: https://www.dnspod.cn/ DNSPod 有几个域名在上面
E: http://easy-admin.[REMOVED] ???已经是我记不起来的东西了
F: http://fiddle.jshell.net/ 呃……其实我用得不是很多
G: https://gitlab.com/ Ainou 的代码托管在那,所以经常去
H: http://hicivitas.com/ 以前兼职公司的官网
I: http://www.ip138.com/ …………大概是经常看自己 ip
J: http://jd.com/ 已经不怎么上淘宝了,狗东比较舒服
K: http://kuler.adobe.com/ 一个配色工具,可能是因为在收藏夹吧,平时不怎么用
L: http://logcg.com/ 这是谁的博客啊……完全不记得访问过了
M: https://mp.weixin.qq.com/ 微信公众平台……嗯最近可能是上得比较多
N: http://notes.jetienne.com/ 这又是谁的博客了……不记得了
O: http://openshift.redhat.com/ 我感觉今年我应该没上过
P: http://piao-tech.blogspot.com/ 怎么越来越离谱了
Q: http://qzone.qq.com/ ……啥。。
R: http://redis.io/ 嗯……倒是经常去查文档
S: https://suiyu.xyz/ 偶尔会写日记
T: http://tower.im/ 好用的团队协作工具呢……不过现在也不怎么用了
U: http://upyun.com/ 偶尔会上去配一下 CDN
V: https://v2ex.com/ 毫无悬念
W: http://weibo.com/ 其实我最近比较少上了……
X: http://xiumima.com/ 呃……社工库么
Y: http://yeah.net/ 偶尔会上网易邮箱看看
Z: https://zhihu.com/ 知乎倒是常去看的

总之,通过浏览器,可以看出一个趋势:我使用浏览器浏览不同的 web 网站的时间越来越少了——大部分网站我根本想不起来。而更多的时间,可能花在了刷手机上。移动互联网近年来确实是势不可挡,我也不得不沦陷了。

唯一不变的,大概是 Web 的信仰——以及,今年我还在坚持使用 Firefox 浏览大部分网页。

Tiny Tiny RSS 推送到 kindle

2015-05-30 • Web 万象 • #rss #kindle #ttrss #推送

前方高能预警:以下内容本人仅照原样提供,不对其有任何支持!伸手党速速退散,折腾指数五颗星!

前方预警:如果你没有 kindle 或者不打算用 kindle 看 RSS,以下内容对你而言并无作用。

很久以前折腾了一个 Calibre 的自定义脚本,用这个脚本可以将 tiny tiny rss 中的内容推送到 kindle 上实现 kindle 的 rss 阅读。

kindle 上的阅读体验

不过在最新的 ttrss 上,我发现它已经没法实现分类阅读了,估计是 api 有所改动,毕竟写了也有好几个月了,懒得调整了……

先放上脚本链接,包含 recipe 文件和推送脚本,后者需要 mailgun 账户。

如果你用的是 Linux VPS,那么只要按照calibre 官网上所述的安装方法安装好 calibre,最后将 recipe 文件和 push.sh 放到 VPS 上,修改一些变量就能用了。

如果你用的是 Windows (自己的电脑或者 VPS 都好),那打开 calibre,点新闻抓取,高级,自己添加一个 recipe,将我的 recipe 文件粘贴进去,最后设置登录用户名为: username;http://<ttrss-url>;test,密码为 ttrss 的密码,然后设置自动抓取推送即可。

最后说一句,对此脚本不负任何责任,这玩意需要自己慢慢折腾,我之前没写折腾笔记所以现在懒得写 step-by-step 了……

Firefox 35 下可用的 pentadactyl

2014-12-09 • Web 万象 • #firefox #pentadactyl

pentadactyl 是一个以类 vim 方式操作 Firefox 的插件。

由于某些原因,pentadactyl 并未停止开发,但火狐附加组件页面和官方每日构建页面都停止了更新,导致火狐新版无法支持 。

于是我自己构建了一个版本,是文章发布时的最新源码构建的。放在了百度网盘,需要的人自取。

构建其实也很简单,以 CentOS 为例,假设你安装了 epel ,然后:

yum install mercurial zip make # 安装hg,zip,gmake等依赖
hg clone http://dactyl.googlecode.com/hg/ dactyl # 将 dactyl 的源码克隆到本地
cd dactyl/
make -C pentadactyl xpi # 制作 pentadactyl 的 xpi 文件

然后你会在 downloads 文件夹下发现构建好的 xpi 文件。直接用火狐安装即可。

更新:Firefox 35 可用的版本已经传到了百度网盘

gulp.js - stream 驱动的自动构建工具

2014-06-30 • Web 万象 • #gulp #coffee #jade #stylus

最近心情不太好,开始有点自暴自弃。所幸能够写点代码分分心……

开始接触一些前端的项目,就尝试着用一些看起来很 cool 的技术/工具。初步选定的工具包括 jade 、 stylus 和 coffee 。

可是做预编译实在是一个很麻烦的事情,直到我遇见了 gulp.js

gulp.js 是一个简洁明快的自动构建工具,官方文档也异常简洁清晰。主打“stream”式构建,配置文件写起来也是不太复杂。这里针对自己用到的一些插件做简单的介绍。

gulp-concat 是一个将多个文件合并到一起的插件。

gulp-clean 用来清理目标目录中的文件。

gulp-stylusgulp-coffeegulp-jade用来预编译我的代码。

想要达到的效果是:

  1. 将一些静态文件复制到发布目录;
  2. 将框架的 css 与自己的 stylus 编译成的 css 合并;
  3. 将框架的 js 与自己的 coffee 合并;
  4. 将 jade 编译为 html 文件。

目录结构如下:

public/ 存放发布文件
src/ 存放源文件

实现思路:

清理发布目录

var clean = require('gulp-clean');
gulp.task('clean', function () {
    return gulp.src('public').pipe(clean());
});

几乎是最简单的任务。将发布目录整个的删掉。

gulp.src用于读取源文件,这里读取的是public目录;然后将其 pipe 到 gulp-clean 插件,将其清理掉。

处理静态文件

gulp.task('static', function () {
    gulp.src('src/img/**')
        .pipe(gulp.dest('public/img/'));
    gulp.src('src/fonts/**')
        .pipe(gulp.dest('public/fonts/'));
});

和上面的范例类似,只不过用的是gulp.dest,用于将 pipe 过来的文件发布到 public 目录。

注意这里的路径是 src/img/** ,表示对 img 目录下的文件进行递归处理。

构建 jade

var jade = require('gulp-jade');
gulp.task('html', function () {
    return gulp.src(['src/jade/**/*.jade', '!src/jade/partial/**'])
        .pipe(jade())
        .pipe(gulp.dest('public/'));
});

这里只是换用了gulp-jade插件而已。

需要注意的是,这次的路径传入进来的是个数组;数组会如同你想象的那样,将数组中的元素挨个匹配;!开头的表示去除src/jade/partial目录下的所有 *.jade 文件;**/*.jade则表示该目录下递归中所有的 jade 文件。

这里将 gulp 链 return 回去,是为了在处理依赖的时候,能够正确确定函数处理进度,以保证运行顺序。

合并 js 和 css

var concat = require('gulp-concat');
//合并 css
gulp.task('css', function () {
    return gulp.src('src/css/*.css')
        .pipe(concat('0.css'))
        .pipe(gulp.dest('.tmp/css/'));
});
//处理 js
gulp.task('js', function () {
    //加载jquery、modernizr、foundation、vue
    return gulp.src([
        'src/js/vendor/jquery.js',
        'src/js/vendor/modernizr.js',
        'src/js/foundation.min.js',
        'src/js/vendor/vue.min.js'
    ])
        .pipe(concat('0.js'))
        .pipe(gulp.dest('.tmp/js/'));
});

这里用到的是gulp-concat插件。它能把多个文件合并成为同一个。

这里将输出送到了 .tmp 目录下,是为了后续能够处理它。

编译 stylus 和 coffee

var stylus = require('gulp-stylus');
var coffee = require('gulp-coffee');
//stylus 编译
gulp.task('stylus', function () {
    return gulp.src('src/styl/**/*.styl')
        .pipe(stylus())
        .pipe(concat('1.css'))
        .pipe(gulp.dest('.tmp/css/'));
})
//处理 coffee
gulp.task('coffee', function () {
    return gulp.src('src/coffee/**/*.coffee')
        .pipe(coffee({
            bare: true
        }).on('error', gutil.log))
        .pipe(concat('1.js'))
        .pipe(gulp.dest('.tmp/js'));
});

这里依然将输出送到了 .tmp 下。接下来要将 .tmp 下的文件合并为真正能够使用的 css 和 js 文件。

合并临时 css 与 js 文件

gulp.task('concat-css', ['css', 'stylus'], function () {
    return gulp.src('.tmp/css/*')
        .pipe(concat('common.css'))
        .pipe(gulp.dest('public/css'));
});
gulp.task('concat-js', ['js', 'coffee'], function () {
    return gulp.src('.tmp/js/*')
        .pipe(concat('common.js'))
        .pipe(gulp.dest('public/js'));
});

这里用到的是 deps 也就是“依赖”语法。task 的第二个参数是一个数组。在执行这个任务时,会自动执行数组中的任务;并且,当且仅当数组中所有任务执行完毕后,才会执行后面的 function 中的任务。

这里的应用就是,在先合并好了框架的 css 和自己写的 stylus 编译完成之后,再将临时文件中的两组文件合并起来。

清理临时文件

gulp.task('concat-and-clean', ['concat-css', 'concat-js'], function () {
    return gulp.src('.tmp').pipe(clean());
});

这里则是用到了刚刚提到的 gulp=clean 插件和 deps 语法。

默认动作

gulp.task('default', ['concat-and-clean', 'html', 'static']);

在命令行下,执行 gulp 时,则会自动执行 default 任务。这里定义的 default 任务没有事实上的动作;它只是同时执行上面所述的几个任务,然后自己退出。

监视改动

gulp.task('watch', ['default'], function () {
    //js
    gulp.watch('src/coffee/**', ['concat-js']);
    //css
    gulp.watch('src/styl/**', ['concat-css']);
    //html
    gulp.watch('src/jade/**', ['html']);
    //静态
    gulp.watch('src/img/**', ['static']);
})

gulp.watch 能够监视指定文件的改动,并启动任务;也就是说,当src/coffee/中的文件改动时,gulp 会自动执行 concat-js 任务。

然后我们只需要在命令行中输入 gulp watch ,gulp 将会自动跑一轮 default 任务,然后监视我们的源文件;当发现文件变动时,将自动执行编译。

至此,我的 gulpfile.js 就编写完毕了。Enjoy it!

友情链接