Mac Dictionary Kit

开发了将近两周的一个项目终于可以称作_正式发布_了: [Mac Dictionary Kit](http://code.google.com/p/mac-dictionary-kit/) 的目标是成为一套在 Mac OS X 系统下常用的词典处理与转换工具。虽然在目前它只支持 [stardict 格式](https://stardict.svn.sourceforge.net/svnroot/stardict/trunk/doc/StarDictFileFormat)的转换,但相信随着以后代码的抽象和新格式需求的增加,我们会支持更多的格式,以及更强大、更复杂的转换功能。也尽可能提供更方便的图形用户界面。

DictUnifier

目前 MDK 以两个子项目的形式发布: sdconv 是独立的命令行方式转换工具,专门用于 stardict 词典格式到 Dictionary 2.0 词典格式的转换;DictUnifier 是一个图形界面转换工具,它的设计是为了自动探测并支持多种来源格式,虽然目前它也只支持 stardict 格式。

发布在 Google Code 上,主要是为了提供更好的下载服务、更详细的文档和 bug 跟踪。因为这个项目原来参考了 stardict 的代码,所以沿用 GNU GPLv2 发布。

目前,这两套工具提供的都是 Universal Binary,支持 Intel 和 PowerPC 的 Mac 机器。也都是绿色软件,只需要解压/挂载就可以使用。

在这两套工具正式发布之前,得到了水木社区 [Apple 版](http://www.newsmth.org/bbsdoc.php?board=Apple)许多版友的测试与 bug 报告,在此表示诚挚的谢意。

Nally

这两天做了[一些](http://jjgod.org/code/GBK-support.patch.gz) [Nally][] ([MacBlueTelnet][]) 的[修改](http://jjgod.org/code/font-smooth.patch),主要是让它支持 GBK 编码的 BBS。如果你经常在 Mac 下上 BBS,又对现有的客户端 (Terminal, iTerm, AlienBBS) 的显示正确性/速度/文本渲染效果不满意,可以试一试 [yllan][] 开发的这个 Nally。最大的优点就是用 Core Text 渲染文本,速度特别快,而且字体选择方面专为中文 BBS 优化。我自己已经用它替代使用了很长时间的 iTerm 了。

另外,这是一个开放源码的项目,你也可以用 subversion checkout 下代码做出贡献哦。

Nally, 连线水木

[Nally]: http://yllan.org/mac/Nally
[MacBlueTelnet]: http://sourceforge.net/projects/macbluetelnet/
[yllan]: http://yllan.org

用 Python 维护 iTunes Library

Mac OS X 10.5 中,由于 [Scripting Bridge][] 的引入,用 Ruby 或 Python 程序完成原来 AppleScript 才能完成的任务变得非常简单,而因为这两门语言自身的强大,无形中,可以完成的工作也多了不少。比如我们原来可能要用 ID3Mod 这样的软件进行 iTunes Music Library 的歌曲乱码转换,现在写一段不到十行的 Python 脚本就能完成 (当然,界面没有那么方便)。

[Scripting Bridge]: http://www.apple.com/applescript/features/scriptingbridge.html

### 一个小例子

这里先用 Python 简单的展示一点可以完成的操作:

# 导入必要的模块
from Foundation import *
from ScriptingBridge import *

# 找到 iTunes 这个应用程序
iTunes = SBApplication.applicationWithBundleIdentifier_(“com.apple.iTunes”)

# 打印出当前正在播放的音乐名称
print iTunes.currentTrack().name()

这段代码在 Leopard 下,既可以保存为 .py 文件,用系统自带的 python 解释器 (`/usr/bin/python`) 执行,也可以直接在命令行下调用 Python 解释器,查看它的输出,比如我这里是:

$ python
Python 2.5.1 (r251:54863, Oct 5 2007, 21:08:09)
[GCC 4.0.1 (Apple Inc. build 5465)] on darwin
Type “help”, “copyright”, “credits” or “license” for more information.
>>> from Foundation import *
>>> from ScriptingBridge import *
>>> iTunes = SBApplication.applicationWithBundleIdentifier_(“com.apple.iTunes”)
>>> print iTunes.currentTrack().name()
Le Festin
>>>

注意,执行之前最好先在 iTunes 里开始播放一首曲目。

### 了解更多

见识了 Scripting Bridge 的威力之后,你很自然的想知道这段代码为何可以工作,应该按什么方式来调用它,除了查询当前播放的歌曲名称以外,还有什么其他的接口可以使用。这时候,我们就需要对我们希望用脚本操纵的程序所提供的 Scripting 接口有个清晰的了解。所以可以用 `sdef` 这个命令获得一个程序的脚本接口定义:

$ sdef /Applications/iTunes.app
xml version=”1.0″ encoding=”UTF-8″?>


出现了一长串的怪异信息,太混乱了,看不懂,怎么办呢?这时应该用 `sdp` 命令过滤一下,生成一个比较可读的格式,Objective-C 头文件:

$ sdef /Applications/iTunes.app | sdp -fh –basename iTunes
$ ls
iTunes.h
$ more iTunes.h
/*
* iTunes.h
*/

#import
#import

这里就是一个对 iTunes 提供的脚本编程接口的详尽描述了。从这里我们其实很容易看出,原来 `iTunes = SBApplication.applicationWithBundleIdentifier_(“com.apple.iTunes”)
` 获得的,是一个 iTunesApplication 对象,以这个对象为出发点,我们可以做到任何 API 允许的事情。下面就是一个例子。

#!/usr/bin/python

from Foundation import *
from ScriptingBridge import *

iTunes = SBApplication.applicationWithBundleIdentifier_(“com.apple.iTunes”)

for track in iTunes.sources()[0].playlists()[0].tracks():
print track.name(), track.artist()

简单的观察一下,iTunes 的脚本编程 API 组织是这样的:在 `iTunesApplication` 下,可以找到多个来源 (`iTunesSource`),来源有许多种,比如 music library,比如 CD,比如 iPod 等等,而每个来源里,又按照播放列表 (`iTunesPlaylist`) 来组织,而每个播放列表中,显而易见地有多个曲目 (`iTunesTrack`),在上面这个例子里,我们要找的第一个来源 (`sources()[0]`) 的第一个播放列表 (`playlists()[0]`),正是你的 iTunes Music Library。

值得注意的一点是,在 `iTunes.h` 中用 `SBElementArray *` 表示的数据类型,一眼可以看出它是数组,在 Python 中处理起来也很简单,直接当成 list 类型遍历即可。

### 修改数据

那么,查询我们知道了,该怎么把信息写回去,也就是说,修改原来 Music Library 的内容呢?

先来看看 `iTunes.h` 中的出现的一段:

@interface iTunesTrack : iTunesItem

– (SBElementArray *) artworks;

@property (copy) NSString *album;
@property (copy) NSString *albumArtist;
@property NSInteger albumRating;
@property (readonly) iTunesERtK albumRatingKind;

这里描述的是一个曲目 (它继承了 `iTunesItem` 类型,所以也继承了它的 `name` 等属性),`@property` 这种写法,是 Objective-C 2.0 中[新出现的][objc-property],也很好理解,`(copy)` 的意思是你的赋值会被复制一份保存,`(readonly)` 当然是只读的,而 `NSInteger` 这种简单的变量也是可以写的。

[objc-property]: http://theocacao.com/document.page/510

那么,查询我们知道了,比如这里有个 `album` 属性,我们就去调用 track 的 `album()` 方法,但究竟如何赋值呢?还是按照 Cocoa 的 Key-value coding 的老规矩,改成 `setAlbum(<参数>)`,比如:

>>> iTunes.sources()[0].playlists()[0].tracks()[0].name()
u’Le Festin – Performed by Camille’
>>> iTunes.sources()[0].playlists()[0].tracks()[0].setName_(u’Le Festin’)

这里把一首原来叫做 Le Festin – Performed by Camille 的歌曲名称修正为 [Le Festin](),回到 iTunes 里一看,果然改了。

[Le Festin]: http://blog.pixnet.net/cherrybear/post/8735331

### 系统自带与 macports

在 Mac OS X 10.4 中,由于系统自带的 Python 是老旧的 2.3 版本,所以对 Python 爱好者来说,都往往不得不自己编译一份或者使用 fink/macports 中提供的 Python 2.4/2.5 版本。而在 Mac OS X 10.5 中,终于把自带的 Python 升级到了最新的 2.5.1 版本,对爱好者来说无疑是省事了,可惜这个自带的版本一个令中文用户郁闷的 bug 是:在提示符模式下无法用输入法输入任何中文字符 (相信其他 CJK 字符也是如此),比如我用输入法打一段:你好 hello 世界 world,等我一确认,Python 解释器只收到了 hello world,中文字符被自动滤掉了!然而,通过 macports 安装的 Python 此功能却是正常的。

这使得我们这里如果要修改中文的歌曲、作家、专辑名等等都变得很不方便,怎么办呢?

系统自带的 Python 的特别之处,其实在于它自带了许多 Apple 添加的模块,比如我们在代码最开始导入的 `Foundation` 和 `ScriptingBridge` 模块,这些模块有些不是开源的,有些则还没来得及把代码公开,所以我们机器上只安装有编译好的 Python 模块,然而幸好这些模块是 2.5 版本的,只要我们用 macports 也安装一个 2.5 版本,把系统自带模块的路径给加进去,就能找到并使用它们了。让我们还是用开头那个例子做个简单的示范:

#!/opt/local/bin/python2.5

import sys

sys.path.append(‘/System/Library/Frameworks/Python.framework/Versions/2.5/Extras/lib/python’)
sys.path.append(‘/System/Library/Frameworks/Python.framework/Versions/2.5/Extras/lib/python/PyObjC’)

from Foundation import *
from ScriptingBridge import *

iTunes = SBApplication.applicationWithBundleIdentifier_(“com.apple.iTunes”)

print iTunes.currentTrack().name()

有了上面这些介绍,相信要把这几个工具组合在一起成为一套 iTunes Music Library 的管理利器也不是难事,各位不妨发挥想象力,我就不多说了。

用 git 维护 vim 代码

众所周知的是,vim 的代码是 Bram 用 patch 的方式维护的,一种典型的集约式管理,虽然 edyfox 在 https://vim.svn.sourceforge.net/svnroot/vim 维护了 svn 版本,但这也只是导入 CVS 的内容而已,别人无法往里面加入代码,问题是,当你想开发一系列试验性功能时,没法直接在 vim 的 svn 仓库上工作 (比如创建分支),而只能用自己的版本管理仓库。

这便造成了一个显然的维护问题,以我自己为例,vim-cocoa 的代码原本使用 code.google.com 提供的 svn 服务进行维护,但 Subversion 是以目录为单位跟踪修改的,所以,同一个代码目录,要么来自 vim 的代码仓库,要么来自 google code 的代码仓库,二者不可兼得。

所以我原来使用了一套非常麻烦的维护方式:

$ svn co https://vim.svn.sourceforge.net/svnroot/vim/vim7
$ cp -r vim7/ vim-cocoa/
$ cd vim-cocoa; find . -name “.svn/*” -exec rm -rf {};
$ svn import src/gui_mac.m https://vim-cocoa.googlecode.com/gui_mac.m

也就是说,抓下一份 vim 的代码来,复制一份,去掉其 svn 的数据,然后将我修改过的部分导入 google code 的版本仓库。在我完成一部分修改,代码达到稳定之后,再返回 vim7 目录,更新原来用 svn 下载的代码,然后将我在 vim-cocoa/ 下做出的修改 back port 回最新的 vim7 代码里,生成针对最新版本的 patch,并编译,发布。

显然这里要花大量的时间在没有必要的手工操作上,而且很容易出错。这便是 git 该出现的时候了。再简单复述一下我的需求:

1. 能够通过 svn 经常更新到最新的 vim 代码
2. 自己正在工作的 vim-cocoa 分支代码不受影响
3. 能把主干代码和分枝代码按需合并
4. vim 的 svn 库里有些自动生成的文件,应该删除,因为 Bram 不愿意,所以我应该可以在自己的分支里删除,这样可以避免每次 commit 之前需要恢复这些自动生成的文件

下面是用 git 完成的步骤,主要就是利用了 git-svn 这套方便的工具:

$ mkdir vim; cd vim
$ git-svn init https://vim.svn.sourceforge.net/svnroot/vim/vim7
$ git-svn fetch -r 625 # 这里为了减少消耗,不复制整个 svn 版本仓库,只抓最新的 revision

这时 git-svn 会产生一个叫做 git-svn 的 remote branch,并让本地的 master 指向这个 branch。所以,我们可以从 master 分支出一个自己的版本来:

$ git checkout -b cocoa
# 下面是把所有原来 vim-cocoa 做的修改在这个最新版的 vim 代码中打上
$ git commit -m “import vim-cocoa changes into git repo”

此后,如果上游的 vim svn 库更新了,我们可以用 git fetch git-svn 把更新下载下来,然后,用 git merge git-svn 将这些更新 merge 到当前的 branch 里,当然,也可以用 git pull git-svn 把两步合在一起完成,只要你确定 merge 不会出现冲突。

那么,现在就有很好的一个的平台来做本地修改了,但 vim 的一个问题是,src/auto/ 中自动生成的文件也被放在 svn repo 里,这是一个很麻烦的问题,因为 vim 也不支持 off-directory build,所以在我们测试、调试过后,如果要生成 patch,就不得不先把这些 (configure/make 过程中) 必然会生成的文件先恢复到初始状态,无谓的增加了操作,如果在 git 里 commit,也会提示你这些文件更新了,但你显然不愿意把这些改动都算进你的 commit 里,那怎么让这些文件不烦人呢?

简单,我们不管上游的 vim svn 库怎么维护,可以在本地 git 仓库中把这些文件删除,也就是不跟踪的改动了:

# –cached 表示只删除 git 缓存,不删除实际文件
$ git rm –cached src/auto/config.h
$ git rm –cached src/auto/config.mk
$ git rm –cached src/auto/configure

$ git commit -m “stop tracking auto generated files”

因为就算这些文件不在跟踪中,一旦它们修改过了,git status 还是会提示你它们更新了,而且这样用 git commit -a 把所有修改过的文件加入下一次 commit 也不方便,怎么办呢?用 .gitignore 文件:

$ vi .gitignore

加入以下内容:

.*.swp
.DS_Store
src/TAGS
src/tags
src/Vim
src/auto/*
src/auto/configure
src/auto/config.h
src/auto/config.mk
src/auto/if_perl.c
src/auto/link.log
src/auto/link.sed
src/auto/osdef.h
src/auto/pathdef.c
src/config.log
src/config.status
src/objects
src/xxd/*
src/Vim.app/*

这样不管这些文件怎么变,git 都不会提示了。

现在我们需要一个用来作为参考的分支,以定期生成 patch,这个分支必须随着上游 svn 的更新而更新,但我们这里删除这些自动生成文件的记录又不应该包含在内,怎么办呢?可以用 git-svn 自动生成的 master branch 来做这件事情:

$ git checkout master
$ git rm –cached src/auto/config.h

$ git commit -m “blahblahblah”

这样,以后每次要生成一个独立的 vim-cocoa 分支相对 vim 主干的 patch 时,我都可以先在 master 分支上:

$ git-svn rebase
$ git pull git-svn

然后切换回 cocoa 分支:

$ git checkout cocoa
$ git diff master > vim-cocoa.patch

这样便完整了 patch 的生成。

最后,如果我们需要把现在这个 vim-cocoa 像原来 google code 的代码仓库一样,随时公布到网上该怎么办呢?http://repo.or.cz 提供了公开的代码仓库,申请以后可以获得一个 push 地址:

git+ssh://repo.or.cz/srv/git/vim-cocoa.git

push 需要添加一个用户,然后上传它的 ssh 公钥 (在本地用 ssh-keygen 生成)。然后,如果我要把本地的 cocoa 分支发布到网上,就可以执行:

$ git push git+ssh://repo.or.cz/srv/git/vim-cocoa.git cocoa:master

其中 cocoa 指的是来源 (本地) 分支,master 指的是目的 (远程) 分支,为什么要 push 到另一个名字的分支去呢?因为 master 是 git 默认 clone 下来的分支,为了方便其他用户的抓取,以及 gitweb 的信息显示,还是保持公开仓库里的 master 分支是你最常更新的那个分支为好。

用 mac + shntool + lame 转换 APE 到 MP3

这里介绍在 Mac OS X 中如何用 mac, shntool 和 lame 来完成 ape – mp3 的工作,只需要一个命令就可以完成。

mac 用我以前贴的那个。

shntool 可以 `sudo port install shntool`,但它的来源网站似乎被 GFW 了,你可以自行下载编译:


$ wget http://www.vi.kernel.org/pub/mirrors/gentoo/source/distfiles/shntool-3.0.2.tar.gz
$ tar zxf shntool-3.0.2.tar.gz
$ cd shntool-3.0.2; ./configure; make; sudo make install

lame 可以用 macports 上的:

`$ sudo port install lame`

然后这样处理 APE:

`$ shntool split -f CDImage.cue -t ‘%p – %t’ -o ‘cust ext=mp3 lame –quiet – %f’ CDImage.ape`

其中 -t ‘%p – %t’ 是指定生成文件的标题,%p 表示 Performer,%t 表示 Title,都是从 cue 中读出的。’cust ext=mp3 lame –quiet – %f’ 表示输出文件用 lame 处理,默认参数,你当然可以在这里调整 lame 的参数。

举例如下:

$ shntool split -f CDImage.cue -t "%p - %t" -o 'cust ext=mp3 lame --quiet -  %f' CDImage.ape
shntool [split]: warning: discarding initial zero-valued split point
Splitting [CDImage.ape] (40:14.15) --> [Joe Hisaishi - Summer.mp3] (6:25.32) : 100% OK
Splitting [CDImage.ape] (40:14.15) --> [Joe Hisaishi - Going Out.mp3] (1:17.35) : 100% OK
Splitting [CDImage.ape] (40:14.15) --> [Joe Hisaishi - Mad Summer.mp3] (2:55.13) : 100% OK
Splitting [CDImage.ape] (40:14.15) --> [Joe Hisaishi - Night Mare.mp3] (1:50.52) : 100% OK
Splitting [CDImage.ape] (40:14.15) --> [Joe Hisaishi - Kindness.mp3] (1:58.23) : 100% OK
Splitting [CDImage.ape] (40:14.15) --> [Joe Hisaishi - The Rain.mp3] (5:38.70) : 100% OK
Splitting [CDImage.ape] (40:14.15) --> [Joe Hisaishi - Real Eyes.mp3] (3:16.52) : 100% OK
Splitting [CDImage.ape] (40:14.15) --> [Joe Hisaishi - Angel Bell.mp3] (3:13.20) : 100% OK
Splitting [CDImage.ape] (40:14.15) --> [Joe Hisaishi - Two Hearts.mp3] (2:01.33) : 100% OK
Splitting [CDImage.ape] (40:14.15) --> [Joe Hisaishi - Mother.mp3] (2:13.57) : 100% OK
Splitting [CDImage.ape] (40:14.15) --> [Joe Hisaishi - River Side.mp3] (6:14.40) : 100% OK
Splitting [CDImage.ape] (40:14.15) --> [Joe Hisaishi - Summer Road.mp3] (3:08.38) : 100% OK

-f 参数指定读取切割点的文件,如果不指定则从标准输入读取,所以,如果 cue 文件是 GBK 编码的,我们可以:

`$ iconv -f gbk -t utf-8 CDImage.cue | shntool split -t “%p – %t” -o ‘cust ext=mp3 lame –quiet – %f’ CDImage.ape`

就完成了 GBK 到 UTF-8 的转码。速度还是挺快的,估计它用 pipe 来把 mac 的输出送给 lame,因此这两个进程可以并行跑,CPU 能跑到 120% 的样子。

这个办法的好处是非常简单,不需要另外写程序,唯一的缺点是只能依靠 cue 的信息,也没有自动加上 MP3 的 ID3 Tag,不太方便直接导入 iTunes 中,不过我们可以写个后期处理的程序 (比如利用 cue 给出的一些信息,或者用 shntool 输出的 WAVE 信息,调用 freedb/cddb 的服务,或者根据 mp3 来调用 MusicBrainz 的服务) 给它加上 tag。