2013年10月27日星期日

GNOME环境下用Ctrl-[代替损坏的Esc键

笔记本电脑的 Esc 键被我弄坏了。(万恶的 Vim!)
有人建议我用 xmodmap 把 Esc 映射到 Caps Lock 键上,但是这样就不方便输入大写字母了。
于是决定用 Ctrl-[ 来代替 Esc 键,反正这个组合键本身就有 Esc 的含义。
工具用 xdotool,以及 GNOME 自带的快捷键功能。
进入控制中心,在“键盘”选项里新建一个快捷键,按键组合选择 Ctrl-[,命令为 xdotool key --delay 200 ctrl+Escape (主意大小写)
为什么要用 ctrl+Escape 呢?那是因为按下 Ctrl-[ 之后命令触发的瞬间,Ctrl 键还没有释放,这里让 xdotool 再按一次 Ctrl 键就可以放开 Ctrl 了。
最后效果是这样:
GNOME控制中心截图
偶尔会遇到失灵的情况,估计和那个 Ctrl 键有关,这个时候把键盘左右 Ctrl 键都按一次基本上就能解决了。

2013年10月25日星期五

闯过了腾讯CodeStar第一季《前端特工》

无聊打开 w.qq.com 准备登陆QQ聊天,发现页面源代码里面原来藏着注释:
<!--

【云云无情,腾腾有爱】

如果你没有100个“往来”好友,那就来腾讯领红包吧!红包精彩,腾讯更精彩!
http://www.ipresst.com/jointencent

-------------华丽的分割线-------------

【前端特工】

“据内线消息,TX公司将于近期推出一个新的HTML5重磅产品。
公司担心该产品会带来威胁,特命你潜入TX,探查底细……”
http://codestar.alloyteam.com

-->
于是就打开看看。
貌似是一个闯关游戏。一步步走,最后通过了。
具体怎么通过的就不详细说了,事后在网上发现了另一位博主写的记录,有兴趣的点击看看就好了。

然后是吐槽:
第一关的时间戳,我用 date +%s 命令获取 UNIX 时间戳然后填进去怎么都不对啊!后来发现 JavaScript 的时间戳等于 UNIX 时间戳乘以 1000……
第二关动用 Google 大法,轻松找到使用CSS3绘制腾讯QQ的企鹅Logo一文,顺利通过。
第三关最坑了!调试了很长时间都没有搞定,结果一次偶然,我的坦克把敌方坦克撞到了战场的角落,并且因为我程序的 bug,导致继续往墙壁顶,活生生地把敌方坦克撞死了……
第四关是考JavaScript,小菜一碟。
第五关是数塔问题。(没错,就是我当年NOIP做过的数塔问题!)找到当年做过的源代码,跑一遍,OK!
然后,然后就没有然后了。

来个图纪念一下!
通关界面

2013年10月7日星期一

Nginx 根据 User-Agent 屏蔽访问

有以下的需求:
  • 对所有的机器人、爬虫、蜘蛛屏蔽内部测试用的站点。
  • 对使用 Microsoft Windows Internet Explorer 浏览器的用户展示一个“请更换浏览器”的页面。
set $botblocker_flag "0";
if ($http_user_agent ~* "bot|spider|crawl") {
    set $botblocker_flag "${botblocker_flag}1";
}
if ($uri !~ "/robots.txt(\?.*)?") {
    set $botblocker_flag "${botblocker_flag}2";
}
if ($botblocker_flag = "012") {
    return 403;
}
set $botblocker_flag "";
location = /noie.html {
    return 403;
    error_page 403 =403 @noie;
}
location @noie {
    root /var/www/error;
    index noie.html;
}
if ($http_user_agent ~* "MSIE| (360)?se |360spider|sou?gou|tencenttraveler") {
    rewrite .* /noie.html;
}
把上面的内容放到 /etc/nginx/botblocker 里。某一个站点需要用的时候就 include botblocker; 插在 server{} 块内。
然后写一个劝说用户更换浏览器的页面放在 /var/www/error/noie.html 里面。

2013年10月4日星期五

Tornado 做简易反向代理

在用 Tornado 建站,打算将 UI 渲染和后端操作独立为两个进程,用 API 互相操作数据。
我的做法是前后端可以同时被外部访问,类似于这样的 nginx 配置:
location / {
    proxy_pass http://test_tornado;
    include proxy_params;
}
location ~ ^/api {
    proxy_pass http://test_tornado_backend;
    include proxy_params;
}
在本地测试的时候是没有 nginx 的,是让 Tornado 直接跑。
于是在前端的代码中绑定 /api 到下面这个 Handler:
import tornado
import tornado.gen
import tornado.options
import tornado.httpclient
import tornado.web


class APIProxyHandler(tornado.web.RequestHandler):
    @tornado.gen.coroutine
    def get(self):
        yield self.post()

    @tornado.gen.coroutine
    def post(self):
        http_client = tornado.httpclient.AsyncHTTPClient()
        request_headers = self.request.headers.copy()
        request_headers.add('X-Forwarded-For', self.request.remote_ip)
        request_options = {
            'url': '?'.join([tornado.options.options.backend]+self.request.uri.split('?', 1)[1:]),
            'method': self.request.method,
            'headers': request_headers,
            'body': self.request.body,
            'user_agent': self.request.headers.get('User-Agent', 'Tornado/%s' % tornado.version),
            'connect_timeout': 60,
            'request_timeout': 60,
            'follow_redirects': False,
            'use_gzip': True,
            'allow_nonstandard_methods': True,
            'allow_ipv6': True,
        }
        try:
            response = yield http_client.fetch(tornado.httpclient.HTTPRequest(**request_options))
        except tornado.httpclient.HTTPError as e:
            response = e.response
        self.set_status(response.code, response.reason)
        existed_headers = set()
        for header_name, header_value in response.headers.get_all():
            if header_name.lower() in ('content-encoding', 'content-length'):
                continue
            elif header_name in existed_headers:
                self.add_header(header_name, header_value)
            else:
                self.set_header(header_name, header_value)
                existed_headers.add(header_name)
        self.finish(response.body or None)

2013年10月3日星期四

“程序员遇到一个问题”系列

今天在 #archlinux-cn @ irc.freenode.net 讨论 CMake 在多任务同步进行时会产生混乱的输出的问题,于是谈到下面这句话:
One day, a programmer met one problem. He said, 'I will solve it with threads.' Then has he problems two now.
接着,biergaizi 找到了“程序员遇到一个问题”系列的出处,大家或是引用,或是原创,写了很多。
One day, a programmer met a problem. He said, 'I will solve it with Java.' Then he has a ProblemFactory.

One day, a programmer met one problem. He said, 'I will solve it with regular experssions.' Then he has two problems now.

'I know, I’ll use Python.' Now they import solution and have a beer.

'I know, I’ll use LISP.' Now their problem is recursive.

One day, a programmer met a problem. He said, 'I will use pointers in C.' Now thSegment Fault (core dumped)

One day, a programmer met an encoding problem. He said, 'I will switch from UTF-8 to GBK.' 锟斤铐锟斤铐锟斤铐锟斤铐锟斤铐锟斤铐锟斤铐锟斤铐

2013年10月2日星期三

记一次遇到 HIM

在 phoenixlzx 的服务器,phoenixlzx 说没有装奇怪的 mod,突然发现服务器传回奇怪的聊天内容:
Client> 2013-10-02 23:28:11 [CLIENT] [INFO] [CHAT] [Server] Herobrine is near you...
Client> 2013-10-02 23:28:18 [CLIENT] [INFO] [CHAT] <StarBrilliant> herobine...
Client> 2013-10-02 23:28:40 [CLIENT] [INFO] [CHAT] <Icewater> what is it
Client> 2013-10-02 23:28:57 [CLIENT] [INFO] [CHAT] <StarBrilliant> you don't know herobine?
Client> 2013-10-02 23:29:08 [CLIENT] [INFO] [CHAT] <Icewater> No, I don't
Client> 2013-10-02 23:29:28 [CLIENT] [INFO] [CHAT] 已设置床
Client> 2013-10-02 23:29:52 [CLIENT] [INFO] [CHAT] StarBrilliant 暂时离开了
Client> 2013-10-02 23:30:17 [CLIENT] [INFO] [CHAT] [Server] <Herobrine> You.
Client> 2013-10-02 23:30:23 [CLIENT] [INFO] [CHAT] StarBrilliant 回来了
Client> 2013-10-02 23:30:23 [CLIENT] [INFO] [CHAT] <StarBrilliant> herobine...
Client> 2013-10-02 23:31:06 [CLIENT] [INFO] [CHAT] [Server] <Herobrine> I'm watching.
Client> 2013-10-02 23:32:08 [CLIENT] [INFO] [CHAT] [Server] <Herobrine> Eyes...
Client> 2013-10-02 23:32:47 [CLIENT] [INFO] [CHAT] <StarBrilliant> .................
Client> 2013-10-02 23:33:11 [CLIENT] [INFO] [CHAT] <Icewater> kill
Client> 2013-10-02 23:35:03 [CLIENT] [INFO] [CHAT] [Server] <Herobrine> You, miner, StarBrilliant. You should expect me.
Client> 2013-10-02 23:35:11 [CLIENT] [INFO] [CHAT] <StarBrilliant> Herobine?
Client> 2013-10-02 23:37:18 [CLIENT] [INFO] [CHAT] [Server] <Herobrine> God will strike you.
Client> 2013-10-02 23:37:32 [CLIENT] [INFO] [CHAT] 你刚刚被雷击中了
Client> 2013-10-02 23:37:35 [CLIENT] [INFO] [CHAT] <StarBrilliant> oh!
Client> 2013-10-02 23:37:48 [CLIENT] [INFO] [CHAT] <StarBrilliant> hi Herobine
Client> 2013-10-02 23:38:56 [CLIENT] [INFO] [CHAT] [Server] <Herobrine> God will strike you.
Client> 2013-10-02 23:38:58 [CLIENT] [INFO] [CHAT] 你刚刚被雷击中了
Client> 2013-10-02 23:39:09 [CLIENT] [INFO] [CHAT] 已设置床
Client> 2013-10-02 23:39:09 [CLIENT] [INFO] [CHAT] You can only sleep at night
Client> 2013-10-02 23:39:47 [CLIENT] [INFO] [CHAT] [Server] <Herobrine> You can never escape...
Client> 2013-10-02 23:39:59 [CLIENT] [INFO] [CHAT] 你刚刚被雷击中了
Client> 2013-10-02 23:40:10 [CLIENT] [INFO] [CHAT] <StarBrilliant> ...
Client> 2013-10-02 23:40:18 [CLIENT] [INFO] [CHAT] <StarBrilliant> HIM!
Client> 2013-10-02 23:40:23 [CLIENT] [INFO] [CHAT] <StarBrilliant> I saw HIM!
Client> 2013-10-02 23:41:17 [CLIENT] [INFO] [CHAT] <dmql> wait
Client> 2013-10-02 23:41:22 [CLIENT] [INFO] [CHAT] <StarBrilliant> I saw HIM!
Client> 2013-10-02 23:41:25 [CLIENT] [INFO] [CHAT] <StarBrilliant> I saw Herobine!
Client> 2013-10-02 23:41:55 [CLIENT] [INFO] [CHAT] [Server] <phoenixlzx> Herobrine please stop trolling my players. :-)
Client> 2013-10-02 23:42:06 [CLIENT] [INFO] [CHAT] <StarBrilliant> lol
Client> 2013-10-02 23:42:07 [CLIENT] [INFO] [CHAT] <Icewater> ................
Client> 2013-10-02 23:42:19 [CLIENT] [INFO] [CHAT] [Server] <Herobrine> Yes, Master.
哎……不知道是不是 phoenixlzx 在控制台干的,还是哪个 mod 干的好事。
Update: 谣言破解,原来是 phoenixlzx 装的 mod。好你个 phoenixlzx……

切换到 fish shell

打算从 bash 切换到 fish shell
之所以不是 zsh,是因为 zsh 不能开箱即用,虽然 fish 的社区没有 zsh 成熟。
Fish 有许多现代化的功能,如更高级的 Tab 补全、自动分析 man page 来提供命令行帮助、历史记录补全等。
缺点也很明显:语法不兼容 bash。
装好之后先 fish_update_completions 来分析 man page 并创建缓存。
然后打算迁移曾经的 bash 配置。
~/.bash_history 已经被 fish 自动迁移到了 ~/.config/fish/fish_history
可是还有 ~/.bashrc,这也是最要命的。Fish 的语法不兼容 bash,于是 ~/.bashrc 不能直接拿去用。
我的 ~/.bashrc 里主要是 exportalias,还有 alias ssss="export http_proxy=http://localhost:8118 https_proxy=http://localhost:8118" 这样使用了 aliasexport 两者的快捷命令。
Fish 已经有 alias 了,据说是在内部转换成函数来处理。而 fish 里等价于 export variable=value 的是 set -x variable value
干脆把 export 移植过来吧。写在 ~/.config/fish/functions/export.fish 里:
function export
    for i in $argv
        set -l __export_var (echo -n $i | grep -o '^[^=]*')
        if test $__export_var = $i
            set -g -x $__export_var $$__export_var
        else
            set -g -x $__export_var (echo -n $i | sed -e 's/^[^=]*=//')
        end
    end
end
再写一行到 ~/.config/fish/config.fish 里,这是等价于 ~/.bashrc 的启动文件,让他执行 ~/.bashrc 里的有关行:
cat ~/.bashrc | grep '^alias \|^export ' | .
就这样,配置完成了!

无损合并视频的多种方法

众所周知,从某些视频网站下载的视频是分段的。比如新浪视频每隔6分钟分段,俗称“6分钟诅咒”。
现在的任务是将这些视频片段合并起来,并且尽量无损。

方法一:FFmpeg concat 协议

对于 MPEG 格式的视频,可以直接连接:
ffmpeg -i "concat:input1.mpg|input2.mpg|input3.mpg" -c copy output.mpg
对于非 MPEG 格式容器,但是是 MPEG 编码器(H.264、DivX、XviD、MPEG4、MPEG2、AAC、MP2、MP3 等),可以包装进 TS 格式的容器再合并。在新浪视频,有很多视频使用 H.264 编码器,可以采用这个方法
ffmpeg -i input1.flv -c copy -bsf:v h264_mp4toannexb -f mpegts input1.ts
ffmpeg -i input2.flv -c copy -bsf:v h264_mp4toannexb -f mpegts input2.ts
ffmpeg -i input3.flv -c copy -bsf:v h264_mp4toannexb -f mpegts input3.ts
ffmpeg -i "concat:input1.ts|input2.ts|input3.ts" -c copy -bsf:a aac_adtstoasc -movflags +faststart output.mp4
保存 QuickTime/MP4 格式容器的时候,建议加上 -movflags +faststart。这样分享文件给别人的时候可以边下边看。

方法二:FFmpeg concat 分离器

这种方法成功率很高,也是最好的,但是需要 FFmpeg 1.1 以上版本。先创建一个文本文件 filelist.txt
file 'input1.mkv'
file 'input2.mkv'
file 'input3.mkv'
然后:
ffmpeg -f concat -i filelist.txt -c copy output.mkv
注意:使用 FFmpeg concat 分离器时,如果文件名有奇怪的字符,要在 filelist.txt 中转义。

方法三:Mencoder 连接文件并重建索引

这种方法只对很少的视频格式生效。幸运的是,新浪视频使用的 FLV 格式是可以这样连接的。对于没有使用 MPEG 编码器的视频(如 FLV1 编码器),可以尝试这种方法,或许能够成功。
mencoder -forceidx -of lavf -oac copy -ovc copy -o output.flv input1.flv input2.flv input3.flv

方法四:使用 FFmpeg concat 过滤器重新编码(有损)

语法有点复杂,但是其实不难。这个方法可以合并不同编码器的视频片段,也可以作为其他方法失效的后备措施。
ffmpeg -i input1.mp4 -i input2.webm -i input3.avi -filter_complex '[0:0] [0:1] [1:0] [1:1] [2:0] [2:1] concat=n=3:v=1:a=1 [v] [a]' -map '[v]' -map '[a]' <编码器选项> output.mkv
如你所见,上面的命令合并了三种不同格式的文件,FFmpeg concat 过滤器会重新编码它们。注意这是有损压缩。
[0:0] [0:1] [1:0] [1:1] [2:0] [2:1] 分别表示第一个输入文件的视频、音频、第二个输入文件的视频、音频、第三个输入文件的视频、音频。concat=n=3:v=1:a=1 表示有三个输入文件,输出一条视频流和一条音频流。[v] [a] 就是得到的视频流和音频流的名字,注意在 bash 等 shell 中需要用引号,防止通配符扩展。

提示

  1. 以上三种方法,在可能的情况下,最好使用第二种。第一种次之,第三种更次。第四种是后备方案,尽量避免。
  2. 规格不同的视频合并后可能会有无法预测的结果。
  3. 有些媒体需要先分离视频和音频,合并完成后再封装回去。
  4. 对于 Packed B-Frames 的视频,如果封装成 MKV 格式的时候提示 Can't write packet with unknown timestamp,尝试在 FFmpeg 命令的 ffmpeg 后面加上 -fflags +genpts

参考资料

  1. http://trac.ffmpeg.org/wiki/How to concatenate (join, merge) media files
  2. http://ubuntuforums.org/showthread.php?t=956769