灵感的来临,没有任何预兆;灵感的消失,也不会有告别仪式;用文字记下她们吧,让灵感永存……

基于目录的网络相册软件:Singapore

随着数码相机等的普及,数码照片日趋泛滥,几乎看都看不过来了。由于照片数量巨大,传统的通过web方式上传照片的方式变得不再有效。这些照片在文件系统中通常是通过数型目录的方式进行管理的,如果能够直接根据数型目录生成相应的相册,将会使把本地资源通过网络相册的方式来分享变得非常容易。

清华大学山野协会经过十几年的发展,已经积累了三十多G的照片(包括以前的扫描部分),而且还在以每月上G的速度增长。曾经想把这些宝贵资料搬到Google PicasaWeb上去,经过几天的尝试,觉得工作量实在是大,最后暂停工作。这些照片都是在协会服务器上按照年份整理好的,非常需要一个基于目录的网络相册软件。

snakesu推荐了miniShowCaseSingapore,经过试用,发现Singapore基本符合我的期望。它能够根据目录自动动态生成相册,其它相关数据存储可以采用MySQL、SQLite或者csv文件的形式。前端页面由模板生成,自带的模板效果很不错。它是用PHP实现的,安装和管理非常简单,代码也采用面向对象的方式写的,很直白,注释丰富,修改很容易,这一点非常重要。

在实际使用过程中,发现一些细节不太理想,于是做了一些Hack:

不知为什么,它对所有请求做了HTML转译处理(singapore.class.php文件的107行),导致所有带有中文名的内容无法访问,注释掉那一行就搞定了。

默认生成的缩略图等信息存储在照片的源目录中,我把它们设置到专门的目录中,这样就不需要原照片目录的写权限,保证安全性,同时能够保持原目录的干净。

优化了生成缩略图的方法,原来会使用ImageMagick或者GD创建100x100,80x80和50x50的三种缩略图,效率非常低。当处理比较大的照片时,会占用大量内存,我的服务器的320M内存根本撑不住,有一次把耗尽了所有内存,服务器瘫痪了几个小时,表现为可以连接但是长时间没有响应。不过最后内核杀掉了PHP-CGI进程,居然活过了来,真是神奇,可见Linux的可靠性不一般呀。现在改成了尽量用JHead来提取照片中已有的缩略图,如果失败则用convert加-sample参数来生成固定尺寸的缩略图。经过这样的优化,即使是第一次访问某个相册也不需要长时间等待了,响应比较快。正如批量处理数码照片相关上文中所说的,这种方案会导致浏览缩略图时画质下降,也还可以接受啦。同时将大量用到的两种尺寸的缩略图缩减为一种。

在原系统中,将文件夹分为带有子目录的Gallery和只包含有照片的Album两类,假定Gallery中不直接包含照片,这样导致既有子目录又有照片的目录中的照片无法看到。最好的处理方式是将他们统一对待,但改起来有点费劲,就做了简单的调整,将Gallery和Album的判别方式改为比较子目录和照片的数量,并将Gallery的照片和Album中的子目录也一并显示出来,通常都比较少。

出于性能方面的考虑,现在的Gallery还不能够选择封面,于是加了一个默认封面,勉强凑或。以后有时间了再改进Gallery和Album的封面处理方式。

虽然做了一些改进,但是还有不足:用户不能做基本的修改操作,比如旋转。导致我用JHead和Picasa结合的方式花了一整天的时间来旋转照片:-) 要实现的话也不难,就是担心简单的实现方式会让机器人乱点链接导致反复旋转,而且还需要对照片的写权限,不安全。另外一个就是匿名评论功能,有这个的话可以给多人分享照片增加很多乐趣。官方可能会做这方面的工作,就先放着吧。

以上所做修改是基于singapore-0.10.1进行的,具体的修改内容可以查看diff文件(不一定能用patch直接应用)。

已经使用Hack过的Singapore把山野的相册做好了,欢迎访问。

davies 发表于 2007 年 04 月 22 日 | 0 条留言

批量处理数码照片相关

两周前写了关于批量更新数码照片时间的文章,使用的是 Perl 实现的 ExifTool,非常强大,除了速度有点慢以外。前两天发现了一个更针对常用的处理数码照片操作的更好的工具JHead,它是用C实现的,速度更快。作者是根据处理的照片的实际需求而作,非常贴合实际,目前的最新版已经到2.7,比较成熟和稳定,在Gentoo中可以直接安装:emerge jhead。

具体的使用方法请看官方文档,下面简单列举几个我用到的功能:

自动旋转照片:jhead -autorot photo.jpg
它会根据某些数码相机记录下的拍摄信息自动旋转照片,并适当处理相关EXIF信息,包括缩略图。在要批量自动旋转大量照片是用它的通配符展开还是觉得不太爽,可以结合find来处理,比如把当前目录下的所有照片都自动旋转可以这样:
find . -type f -exec jhead -autorot '{}' \;
Windows自带的图片和传真查看器居然不会处理EXIF,令人发指,建议不要使用它来旋转照片。对于那些不包含旋转信息的照片,用 Picasa 来处理还是很不错的,会将缩略图跟照片同步起来。只是它总是要备份照片到名为"Orinigals"的子目录中,没得选择。可以用下面的命令一次性删除:
find .-type d -name Originals -exec rm -rf '{}' \;

导出照片中的缩略图:jhead -st thumbnail.jpg photo.jpg
网站上为了浏览图片时更迅速,需要生成缩略图。使用GD或者ImageMagick等工具来处理时,需要解压缩图片再进行放缩,还是比较慢的。而从数码照片直接提取已经生成好的缩略图时,速度要快上一个数量级,所消耗内存也非常小,在需要实时生成缩略图的应用非常有用,比如第一次浏览基于目录的相册Singapore时。但是这种方式也是有代价的,因为照片中的缩略图是事先生成的,尺寸未知,很可能跟实际用来显示的大小不一致,这样浏览器再重新进行大小调整时会时画质下降很多,影响浏览时的视觉效果。缩略图的作用也就是让用户大概知道是什么内容,这方面的要求还是可以基本满足的。

另外,再用ImageMagick的convert来生成缩略图时,建议采用-sample参数而不是-resize或者-geometry,前者会比后者快三倍。比如:
# time convert P4175994.JPG -resize 100x100 1.jpg
real    0m2.268s
user    0m2.114s
sys     0m0.122s
# time convert P4175994.JPG -sample 100x100 1.jpg
real    0m0.881s
user    0m0.772s
sys     0m0.108s

处理数码照片的拍摄时间方面,更是方便,可以直接相对调整照片的牌是时间,根据拍摄时间调整文件的修改时间(便于做排序),可以根据拍摄时间来格式化文件名等,非常贴心。

此外,还能操作JPG文件中的评论文字,根据文件比例选择文件,比如选择所有竖型照片。看看它的参数说明文档就非常清除了。

davies 发表于 2007 年 04 月 22 日 | 0 条留言

关于构建文档翻译平台的想法

开源社区现在有大量非常有用的文档,虽然对部分英语较好的人不存在阅读障碍,也鼓励大家尽量提高自身的英语能力,但是有一份翻译得不错的中文文档,会更加有用,毕竟使用自己最熟悉的语言是最轻松的。同时也能造福广大英语不好的人,即使翻译文档不能完全准确表达原文的意思,能让人快速了解个大概也是非常非常有用的,通常费了很多时间阅读英文资料后也只能留下一个大概的印象。总的来说,如果能够有一个方便地支持协作翻译的平台,让更多的人更容易地参与翻译和改进,将是非常好的一件事。

现在有不少翻译社区,比如译言或者其它的一些翻译型blog,他们适合翻译一些简短的新闻类文章,不便于多人共同维护一份技术文档的翻译。也有基于Wiki的协作翻译形式,但是wiki的使用有一些门槛,在格式上也很难跟原文保持一致,针对原文档的更新也不容易。基于已有的这些并不太理想的方案,针对技术文档的特点,构建一个更适合文档翻译的平台。

先来看看文档翻译的特点:

  1. 技术文档相对新闻和blog来说,有效性时间相对要长,一篇翻译得好的文档可以让很多人受益;
  2. 技术文档是有版本概念的,会随着软件或者系统的更新而更新;
  3. 内容比较多,一个人单独完成翻译比较困难;
  4. 语句比较客观、直白和严谨,容易得到一个大多数人都能接受的翻译结果,多人协调翻页导致;
  5. 有不少相对稳定的专业词汇和概念;
  6. 文档有一定的格式,通常为HTML页面,内部会嵌入代码等。

根据这些特点,希望文档翻译平台能达到下面这些目标:

  1. 个人的翻译的过程是方便和快捷的,能够中英文对照,批量替换等;
  2. 方便地进行多人协同翻译,共同完成大量文档的翻译,个人即使只翻译一句话或者一个段落也是可参与的;
  3. 方便地进行相互校对和修订,提高翻译质量;
  4. 保持跟原文档一致的格式;
  5. 能够自动跟踪原文档的更新,并自动合并;

拟采用的方案:

  1. 将文档的翻译细化到句子甚至短语的翻译,建立文档中句子甚至短语跟翻译结果的一一对应关系,用它们替换原文档得到翻译后的文档。这样当原文更新后,翻译文档也会自动更新,新出现的语句会明显地暴露出来,便于增量翻译。同时可以保持与原文档同样的格式。
  2. 可选择以机器翻译作为初始状态进行改进,提高翻译质量,同时改进后的翻译作为用来进行机器学习。对一批文档采用项目的形式进行管理,同一个项目共享一个词库,用于进行批量替换或机器翻译时的特化词典。机器翻译最大的问题在于对专业词汇的翻译,解决了这部分后通常能得到一个基本能看明白的文档。
  3. 一个句子或者短语可以有多个翻译结果,用户可以选择最合适的翻译,投票决定最优翻译,以提高翻译质量。
  4. 交互界面上采用Google翻译的效果,页面中显示翻译后的结果,鼠标停留时可以看到原文,点击后可以选择其他翻译或者采用原文或者提交新的翻译。
  5. 可以做一个浏览器扩展或者插件,用户可以根据网页的URL,自动跳转到该平台下的相应翻译页面;

以上是我对协作进行文档翻译的一些想法,欢迎大家多提意见,最后能得到一个比较好的方案并付诸实施。

davies 发表于 2007 年 04 月 6 日 | 5 条留言

批量更新数码照片的拍摄时间(EXIF)

现在的数码照片都有EXIF信息,其中包括拍摄时间等,为整理照片提供了方便。有时会碰到一些拍摄时间不对的照片,通常是因为相机的时间设置不对导致,表现为照片的拍摄时间普遍错位,有一个相同的巨大偏移量。

今天找到了一个处理照片等元信息(包括EXIF等)的强大工具 ExifTool,它使用Perl实现平台独立的自由软件,提供了一个命令行工具用于处理各种文件的元信息,具体的用法请参考它的手册

下面就用它来批量跟新照片的拍摄时间。有一组照片是在2007年3月17日15:25拍摄的,但照片中记录的时间为2003年1月29日10:10。ExifTool支持增量修改字段,参数格式形如 -Tag[+-]=VALUE。照片的拍摄时间对应的Tag为EXIF:DateTimeOriginal,符合格式要求的时间偏移量为"04:1:16 5:25",即4年1个月16天5小时25分钟,完整命令参数为
exiftool -EXIF:DateTimeOriginal+="04:1:16 5:25" -fast -overwrite_original *.JPG
它会更新当前目录的所有以JPG为扩展名的照片。

修正好照片的拍摄时间后,可以把分布在多个目录的由多个人拍摄的统一次活动的照片加到同一个相册中,然后按照时间排序,再上传到 Picasa 网络相册中,就可以同大家一起按时间顺序来回味整个活动的精彩了。

davies 发表于 2007 年 04 月 4 日 | 0 条留言

在 MySQL 中实现队列

队列是常用的数据结构,基本特点就是先入先出,在事务处理等方面都要用到它,有的时候是带有优先级的队列。当队列存在并发访问的时候,比如多线程情况下,就需要锁机制来保证队列中的同一个元素不被多次获取。

一个 MySQL 表可以看作是一个队列,每一行为一个元素。每次查询得到满足某个条件的最前面的一行,并将它从表中删除或者改变它的状态,使得下次查询不会得到它。在没有并发访问的情况下,简单地用 SELECT 得到一行,再用UPDATE(或者DELETE)语句修改之,就可以实现。

SELECT * FROM targets WHERE status='C' LIMIT 1;
UPDATE targets SET status='D' WHERE id='id';

如果有并发访问,在SELECT和UPDATE语句之间可能会存在其他地SELECT查询,导致同一行被取出多次。为了保证在并发情况下仍然能正常工作,一种思路是使用数据库地锁来防止,就像在多线程环境下所做地一样。总之,要是的查询和修改为一个原子操作,不被其它的访问干扰。MySQL 5 支持存储过程,可以用它来实现。

单条 UPDATE 语句应该原子操作的,可以利用这个特性来保证并发访问情况下队列的正常工作。每次取元素时,先用 UPDATE 修改符合条件的第一行,然后再得到该行。可惜 UPDATE 语句没有返回值,重新用普通的SELECT的话又很难找到刚被改过的那条记录。

这里用到一个小技巧:在 UPDATE 时加上 id=LAST_INSERT_ID(id),再用  SELECT LAST_INSERT_ID() 即可得到刚修改的那条记录的id。还有一个问题,当表中不存在符合条件的记录,导致 UPDATE 失败时,LAST_INSERT_ID() 会保留原来地值不变,因而不能区分队列中是否还有元素。

ROW_COUNT() 返回上一个语句影响的行数,把它作为 SELECT 的一个条件,可以帮助解决这个问题。

最后,支持并发访问的完整解决方案为:

UPDATE  targets SET status='D', id=LAST_INSERT_ID(id) WHERE status='C' LIMIT 1;
SELECT * FROM targets WHERE ROW_COUNT()>0 and id=LAST_INSERT_ID();

更新:在实现带优先级的队列时这种方法有问题,带有 ORDER BY ... 条件的 UPDATE 语句非常慢,例如:

UPDATE  targets SET status='D' WHERE status='C' ORDER BY schedule ASC LIMIT 1;

而单独查询和更新则是很快的:

SELECT id FROM targets WHERE status='C' ORDER BY schedule ASC LIMIT 1;
UPDATE targets SET status='D' WHERE id='id';

原来这是MySQL的Bug-12915,一年多以前提出来的,虽然关闭了,却只解决了部分问题,尚不支持WHERE,见MySQL 5.0.15 的 Changlog。无奈,上面这种巧妙的方法也没有实用价值了。

最后采用了一种折衷方案,如下:

UPDATE  targets, (SELECT id FROM targets WHERE status='C' AND schedule<CURRENT_TIMESTAMP ORDER BY schedule ASC LIMIT 1) tmp SET status='D' WHERE targets.id=LAST_INSERT_ID(tmp.id);
SELECT * FROM targets WHERE ROW_COUNT()>0 and id=LAST_INSERT_ID();

davies 发表于 2007 年 01 月 25 日 | 2 条留言

上一页 | 第 2 / 19 页 | 下一页