アーカイブ

Archive for 2010年5月

OS Xのアドレスブックのアイコンを最新のTwitterのアイコンで自動更新する

タイトル長いですが。

OS Xのアドレスブックのカードには、アイコンというか写真を設定できます。ここを設定しておくと、OS Xと同期しているiPhoneで着信あった時にアイコンが表示されたりするので、判りやすかったりします。

問題は知人各位の写真の調達なんですが、僕はmixiとかTwitterとかSkypeのアイコンを貼付けて使っています。ただみなさんアイコンを時々変更されるので、気が付くと懐かしいアイコンになっていたりします。

そこで、最新のTwitterアイコンで、アドレスブックを自動更新するスクリプトを書いてみました。

データ入力
まずは、知人各位のカードに、「URL」を追加してください。内容はTwitterのユーザページのURLである、「http://twitter.com/user_name」としてください。ラベルは「自宅」とか「勤務先」とか選べますが、何でも構いません。URLの内容がTwitterのユーザページになっている事が全てです。

スクリプトをインストール
今回作ったスクリプトはターミナルで動作するrubyスクリプトなのですが、動作させるために必要なパッケージをインストールする必要があります。ターミナルで下記のコマンドを入力してください。

gem install twitter4r

これはRubyGemsによってユーザ領域にインストールされるので、システムを壊す心配はありません。

次に、以下のスクリプトをAddressIconPaster.rbという名前のテキストファイルとして保存してください。

#!/usr/bin/ruby

require 'osx/cocoa'
OSX.require_framework 'AddressBook'
require 'rubygems'
require 'twitter'
require 'open-uri'

twitter = Twitter::Client.new(:login => ARGV[0], :password => ARGV[1])

address_book = OSX::ABAddressBook.sharedAddressBook
address_book.people.each do |person|
  urls = person.valueForProperty(OSX::KABURLsProperty)
  if urls
    for index in 0...urls.count
      url = urls.valueAtIndex(index)
      if /^http:\/\/twitter.com\/(.+)/ =~ url
         user = twitter.user($1)
         open(user.profile_image_url) do |file|
           person.setImageData(OSX::NSData.dataWithRubyString(file.read))
         end
      end
    end
  end
end
address_book.save

アイコンの自動更新を実行
ご自分のTwitterのユーザ名とパスワードを用意して、ターミナルで下記のコマンドを入力してください。

ruby AddressIconPaster.rb ユーザ名 パスワード

これで、カードにTwitterのURLを設定した全てのユーザのアイコンが最新の物に更新されます。

知人のカードにTwitterのURLを入力する事は、今回のアイコン設定が必要なくても自然な事だと思いますし、特別な設定が必要ないので良い感じではないかと思います。

せっかくだから、RubyCocoaでGUIを付ける所がまでやれよ自分……という気もしましたが、iPad弄りが楽しいのでここまでに致したく存じます。

カテゴリー:デスクトップ, 開発 タグ:

整理済みのZIPアーカイブにZippedImages Etcetera対応タグを設定する

僕は電子化したZIPアーカイブを、iTunes風のフォルダ階層に整理しています。
著者名のフォルダの中に書名のZIPアーカイブを、以下のようなイメージで配置しています。

著者A
  書名a.zip
  書名b.zip
著者B
  書名c.zip
  書名d.zip

先日リリースしたZippedImages Etceteraに必要なタグの設定を省力化するために、上記の状態に整理された書庫ファイルをバッチ処理するスクリプトを書きました。
以下のタグを自動設定します。

  • ZIPアーカイブを含むディレクトリ名を著者名として設定
  • ZIPアーカイブのファイル名から拡張子を除去した文字列を書籍名として設定
  • ZIPアーカイブに含まれる最初のJPEGファイルをサムネイルとして設定

つまり、発売日以外のタグを全部自動設定できます。
ZIPアーカイブの整理方法は人それぞれだと思いますが、スクリプトを修正してやれば色々なケースにも対応できると思います。
例えばファイル名に著者名と書籍名を両方含めてフラットに管理している場合でも、今回のスクリプトを修正すれば対応できると思います。

使い方
このスクリプトはZip/Rubyを利用しています。ターミナルからRubyGemsを使って、インストールしてください。

gem install zipruby

あとは、タグを付けたいZIPアーカイブを含むディレクトリをスクリプトに渡してやってください。
再帰処理で、含まれるZIPアーカイブを全部処理します。

ruby ziupdate.rb <ZIPアーカイブを含むディレクトリ>

以下がスクリプトになります。

#!/usr/bin/ruby

require "find"
require "rubygems"
require "zipruby"

Find.find(ARGV[0]) do |file_path|
  file_path = File.expand_path(file_path)
  if /\A[^.].*\.zip\z/ =~ File.basename(file_path)
    author = File.basename(File.dirname(file_path))
    title = File.basename(file_path, ".zip")
    Zip::Archive.open(file_path) do |zip_file|
      thumbnail = zip_file.map {|entry| entry.name}.sort.find {|name| /\A[^.].*\.(jpg|jpeg)\z/ =~ File.basename(name)}
      meta = "Title: \"#{title}\"\nAuthors:\n  - \"#{author}\"\nThumbnail: \"#{thumbnail}\"\n"
      zip_file.add_or_replace_buffer("ZippedImages.yaml", meta)
      print meta
    end
  end
end
カテゴリー:開発 タグ: ,

Objective-CでZIPアーカイブを読み取る

ZippedImages Etceteraでは、ZIPアーカイブを解凍せずに、直接読み込んでいます。
いくつかObjective-C用のZIPライブラリを試したのですが、なかなかピンと来る物が見つからず、C言語用のMinizipライブラリを使いました。

まず、Minizipの配布物から以下のファイルをプロジェクトに追加します。

crypt.h
ioapi.c
ioapi.h
mztools.c
mztools.h
unzip.c
unzip.h
zip.c
zip.h

次に、OS X付属の以下のライブラリを追加をします。iPhone OSでは試していませんが、zlibは存在するので同様の手順で大丈夫だと思います。

libz.dylib

インターフェース部分には、次のようなラッパークラスを書きました。ZippedImages Etceteraに必要な部分しか用意していませんが、ZIPアーカイブの内容を一覧して、任意のファイルを取り出せます。単純化したかったので、ストリームには対応していません。

//
//  ZipFile.h
//  ZippedImagesEtcetera
//
//  Created by Kenji Nishishiro  on 10/05/08.
//  Copyright 2010 Kenji Nishishiro. All rights reserved.
//

#import "unzip.h"

@interface ZipFile : NSObject {
	NSString *path_;
	unzFile unzipFile_;
}

- (id)initWithFileAtPath:(NSString *)path;
- (BOOL)open;
- (void)close;
- (NSData *)readWithFileName:(NSString *)fileName maxLength:(NSUInteger)maxLength;
- (NSArray *)fileNames;
@end

//
//  ZipFile.m
//  ZippedImagesEtcetera
//
//  Created by Kenji Nishishiro  on 10/05/08.
//  Copyright 2010 Kenji Nishishiro. All rights reserved.
//

#import "ZipFile.h"

@implementation ZipFile

static const int CASE_SENSITIVITY = 0;
static const unsigned int BUFFER_SIZE = 8192;

- (id)initWithFileAtPath:(NSString *)path {
	NSAssert(path, @"path");

	if (self = [super init]) {
		path_ = [path retain];
		unzipFile_ = NULL;
	}
	return self;
}

- (void)dealloc {
	NSAssert(!unzipFile_, @"!unzipFile_");

	[path_ release];
	[super dealloc];
}

- (BOOL)open {
	NSAssert(!unzipFile_, @"!unzipFile_");

	unzipFile_ = unzOpen64([path_ UTF8String]);
	return unzipFile_ != NULL;
}

- (void)close {
	NSAssert(unzipFile_, @"unzipFile_");

	unzClose(unzipFile_);
	unzipFile_ = NULL;
}

- (NSData *)readWithFileName:(NSString *)fileName maxLength:(NSUInteger)maxLength {
	NSAssert(unzipFile_, @"unzipFile_");
	NSAssert(fileName, @"fileName");

	if (unzLocateFile(unzipFile_, [fileName UTF8String], CASE_SENSITIVITY) != UNZ_OK) {
		return nil;
	}

	if (unzOpenCurrentFile(unzipFile_) != UNZ_OK) {
		return nil;
	}

	NSMutableData *data = [NSMutableData data];
	NSUInteger length = 0;
	void *buffer = (void *)malloc(BUFFER_SIZE);
	while (YES) {
		unsigned size = length + BUFFER_SIZE <= maxLength ? BUFFER_SIZE : maxLength - length;
		int readLength = unzReadCurrentFile(unzipFile_, buffer, size);
		if (readLength  0) {
			[data appendBytes:buffer length:readLength];
			length += readLength;
		}
		if (readLength == 0) {
			break;
		}
	};
	free(buffer);

	unzCloseCurrentFile(unzipFile_);

	return data;
}

- (NSArray *)fileNames {
	NSAssert(unzipFile_, @"unzipFile_");

	NSMutableArray *results = [NSMutableArray array];
	if (unzGoToFirstFile(unzipFile_) != UNZ_OK) {
		return nil;
	}
	while (YES) {
		unz_file_info64 fileInfo;
		char fileName[PATH_MAX];
		if (unzGetCurrentFileInfo64(unzipFile_, &fileInfo, fileName, PATH_MAX, NULL, 0, NULL, 0) != UNZ_OK) {
			return nil;
		}
		[results addObject:[NSString stringWithUTF8String:fileName]];

		int error = unzGoToNextFile(unzipFile_);
		if (error == UNZ_END_OF_LIST_OF_FILE) {
			break;
		}
		if (error != UNZ_OK) {
			return nil;
		}
	}
	return results;
}

@end

使い方は次のような感じです。

ZipFile *zipFile = [[[ZipFile alloc] initWithFileAtPath:@"ZIPアーカイブ名"] autorelease];

// ファイルを開く。
if (![zipFile open]) {
	// エラー処理
}

// ファイルリストを取り出す。パーシャルバスで全ファイルが取得されるので、階層的に扱う必要は無い。
NSArray *fileNames = [zipFile fileNames];
if (!fileNames) {
	// エラー処理
}

// ファイルの内容を取り出す。一度にメモリに読み込むので、ストリーム処理が必要な大きいファイルには向かない。安全のために最大サイズを指定できる。
NSData *data = [zipFile readWithFileName:@"ファイル名" maxLength:読み込む最大サイズ];
if (!data) {
	// エラー処理
}

// ファイルを閉じる。
[zipFile close];

ZIPアーカイブ内の文字コードは、UTF-8を想定しています。
ZIPアーカイブ内のファイル名がShift_JISになっている場合などは、もう少し改良が必要だと思います。

2010/05/23 追記
githubで公開しました。

カテゴリー:開発 タグ: , ,

ZippedImages Etcetera 1.0をリリース

2010年5月9日 3件のコメント

僕は普段、技術書を全部職場の書架に置いているのですが、金曜日になると数冊持ち帰ります。土日に書きそうなプログラムに関係する書籍を選ぶ訳ですが、残念な事に大抵外しています。「ああこの本じゃなくて、あの本にあのあたりにサンプルプログラムが載っていたのに……」的な失敗です。

そこで蔵書を全部電子化して、ノートパソコンで持ち歩く方向で行く事にしました。PK-513Lという断裁機で書籍を裁断して、両面スキャナのScnaSnap S510に読み込ませて電子化を進めています。毎日がんばっても、数年かかりそうな分量でアレですが……

さてその電子化ですが、書籍の内容によってフォーマットを選んでいます。

技術書
画像読み取り後に、PFDに変換します。字面はOCRで文字認識させて、画像の文字位置に透明なテキストとして重ねます。書籍名や著者名は、メタ情報として付加します。
OCRの精度には限界があるのですが、Spotlightで本文を検索できるので死ぬ程便利です。メタ情報やサムネイルは、Finderでも表示可能です。

漫画
画像読み取り後に、ZIPアーカイブにまとめます。PDFよりも再生負荷が小さく、多様な機器で表示できます。ZIPアーカイブにはメタ情報が無いのでSpotlightによる検索はファイル名頼りです。Finderでサムネイル表示もできません。

ZIPアーカイブに固めた画像ファイルは手軽なのですが、今一Finderでの扱いが寂しいです。そこで、ZippedImages Etceteraを開発しました。

ZippedImagesEtcetera 1.0

ZippedImages Etceteraは、ZIPアーカイブに固めた画像ファイルのメタ情報をSpotlightで検索できるようにしたり、Finderでサムネイル表示できるようにするプログラムです。

インストール
ZippedImagesMetadataImporterを~/Library/Spotlightに、ZippedImagesPreviewGeneratorを~/Library/Quicklookにコピーしてください。逆にアンインストールする時は、これらをゴミ箱に捨てればOKです。

メタデータの付け方
下記のような内容のファイルを、ZIPアーカイブにZippedImages.yamlというファイル名で追加してください。現状、この作業をGUI操作で行うツールは用意できていません。

Title: "書籍名"
Authors:
  - "著者名1"
  - "著者名2"
PublishDate: 2010-05-09
Thumbnail: "0001.jpg"

Titleは書籍名、Authorsは著者名、PublishDateは発売日、Thumbnailはサムネイルに使う画像のZIPアーカイブ内でのファイル名です。著者名の所は行数可変なので、複数の値を入力できます。また全ての値は省略可能です。
文字エンコーディングはUTF-8、改行コードはLF(ラインフィード)を想定しています。エディタで保存する時に注意してください。書式はYAMLと呼ばれる物ですが、使う分には良く判らなくても無問題です。

メタ情報の表示
Finderで、ZIPアーカイブの情報パネルを表示してみてください。
詳細情報欄に、タイトル(Title)、作成者(Authors)、Published(PublishDate)が表示されはずです。また、プレビュー欄にはThumbnailで指定した画像が表示されます。

サムネイルの表示
Finderをアイコン表示かカバーフロー表示に切り替えてください。Thumbnailで指定した画像で表示されます。

プレビューの表示
Finderでクイックルックを実行してください。Thumbnailで指定した画像で表示されます。

Spotlightで検索
TitleとAuthorsで指定した値は、Spotlightで検索できます。詳細検索の場合、Titleはタイトル、Authorsは作成者として検索できます。また独自の検索属性として、以下の二つを追加しています。

  • Zipped Images – ZIPアーカイブにZippedImages.yamlが含まれている事を示す真偽値
  • Published – PublishDateで指定した発売日の日付値

詳細検索条件にZipped Imagesを指定すると、ハードディスク内のメタ情報を付けたZIPアーカイブが全部抽出されるので、スマートフォルダとして保存しておくと一覧表示に便利だと思います。Publishedの方は、発売順にソートする時に使えるでしょう。

今後の予定
とりあえず、メタ情報の編集エディタを用意したいと思っています。一回解凍して、ZippedImages.yamlを作成して、もう一度圧縮するのは面倒すぎるので。
内部的にSpotlightを使って、ハードディスク内のメタ情報を付けたZIPアーカイブをiTunesのように表示させるブラウザとか素敵な気もしますが、作業ボリューム的に実現できるか謎です。
あと、クイックルックはページングに対応したいかも。

技術書の話から入ったのに、作ったソフトは漫画用ですかw

カテゴリー:開発 タグ:

続・C#で動的なハイポートで待ち受けるWebサーバを実装する

以前C# WebServerに投げたパッチが、1.1にマージされました。これでHttpListenerにハイポートが指定できるようになったのですが、高水準APIのHttpServerはハイポートをサポートしません。
追加のパッチを投げたのですが、そちらは2.0で不要になるので却下となりました。
今の所2.0はリリースされていないので、1.1にパッチを当てて使っています。

カテゴリー:開発 タグ: