BSHSBE07を購入

バッファローコクヨサプライのBluetoothステレオイヤホン、BSHSBE07を購入しました。

僕は通勤電車で音楽を聞こうとした時に、イヤホンに二つ期待している事があります。

  • 音漏れしない -> カナルが良い
  • 混んでいる時にケーブルを周りに引っ掛けない ->ワイヤレスが良い

ワイヤレスのカナルは、あまり選択肢がありません。ペンダントに普通のカナル式イヤホンを付ける製品もあるのですが、ケーブルがそこそこ長いためか混雑時に引っ掛けてしまう事があって止めました。

スポーツ用のイヤホンに、ネックバンドのカナル式が数機種あるのですが、見た目に異様な物が多いので敬遠しています。スポーツウェアとサングラスでジョギングでもしてそうなデザインだったり。

そんな中、ソニエリからHBH-IS800という、コードの短いカナル式のドライバ部分に電池と受信機を内蔵してしまった製品が出て死ぬ程ストライクだったんですが、例によって国内販売がありません。

BSHSBE07はHBH-IS800に似たコンセプトの製品で、試しに買ってみました。

無音時のホワイトノイズは無し
ワイアレスのイヤホンやヘッドホンに付き物の、ホワイトノイズがありません。今時は、どこの製品でもノイズは乗らないのかもしれません。

イヤーピースのサイズは一種類
厳しいです。耳の穴が広がりそう。

コントローラは扱い難い
手探りでボタンを探す事が難しいです。正円に近いのでボタンの配置面が探し難い上、ボリュームアップと再生を間違い易い配置になっています。

他社からも、同様の製品が出てくると良いなぁと思います。選択肢的に。

カテゴリー: Hardware | コメントをどうぞ

ScanSnapで読み取ったPDFを無劣化で画像ファイルに変換する

僕は技術書をScanSnapで読み取り、PDFとして電子化しています。Mac上で閲覧する時はそのままで問題がありませんが、iPadに転送する場合などは、ページ単位の画像ファイルに変換してから、ePubなどに再構成しています。

この画像ファイルに変換する作業は、Automatorを使ってページ単位でJPEG画像にレンダリングを行っていたのですが、非常に時間がかかる上に再圧縮の過程で劣化します。

元々ScanSnapはビットマップ画像として読み込みを行っているので、PDFの中にはページ単位のビットマップ画像データが存在するはずです。これをそのまま切り出せば、短時間に無劣化で画像ファイルへの変換が可能になるはずです。

PDFからのビットマップ画像の取り出し
調べた所、Xpdfに含まれるpdfimagesコマンドで抽出可能な事が解りました。XpdfはMacPortsでインストールするか、ここで配布しているバイナリパッケージなどをインストールしてください。次のようなコマンドで抽出可能です。

pdfimages -j PDFファイルパス 出力プリフィックス

画像は、「出力プリフィックス-xxx.pbm」か「出力プリフィックス-xxx.jpg」として出力されます。ScanSnapの読み取り時にモノクロになった場合は1ビットのPBM形式に、カラーになった場合はJPEG形式です。つまりモノクロは可逆圧縮で、カラーはDCTとしてPDFに格納されているようです。

PBMからTIFF形式のG4 FAX圧縮に変換
カラーの方のJPEGはそのままePubに変換できますが、モノクロの方は別のファイル形式に変換する必要があります。最初はGIFを試してみましたが、画像の合計サイズが元のPDFの1.5倍程度まで大きくなりました。次にPNGを試した所、GIFよりはファイルサイズが小さくなりましたが、元のPDFと比較すると大きなサイズになりました。一瞬、「ちょ、PDFのモノクロ圧縮はすごすぎだろ」とか思いましたが、冷静に考えると1ビットのモノクロなので、TIFF形式のG4 FAX方式で圧縮してみたら、見事に元のPDFファイルとほぼ同じサイズになりました。PDFの画像部分は、TIFFが手本なのかもしれません。カラーにJPEGをサポートしているのも同様ですし。

変換作業自体は、ImageMagickを使うと簡単です。MacPortsを使うなり、バイナリパッケージをダウンロードするなりしてインストールしてください。次のコマンドでTIFF形式のG4 FAX圧縮に一括変換できます。

mogrify -format tiff -compress Group4 PBMファイルパス...

GIFやPNGは問題無いと思いますが、TIFFをサポートしていないePubリーダーもあるかもしれません、その時はファイルサイズを犠牲にして、PNGに変換する事になると思います。ファイルは大きくなりますが、無劣化には違いありません。

ScanSnapの型が新しい場合は、モノクロがグレイスケールになると聞いています。その場合、pdfimagesがどのようなファイルを吐くか判りません。恐らくJPEGだと思うのですが……

カテゴリー: Desktop | コメントをどうぞ

ZIPアーカイブに固めた画像ファイルをePubに変換する

僕の所にもiPadが届いたので、ScanSnapで読み込んでおいた技術書(PDF)と、漫画(ZIPアーカイブに固めた画像ファイル)を転送して閲覧してみました。

重いです。特にPDFが。

最初から電子化されている書籍であればベクトルデータのPDFは軽いと思いますが、スキャンした書籍はビットマップ画像の固まりなので、ファイルサイズからして巨大になり、閲覧が重いです。
ただし同じ書籍で比較すると、PDFよりもZIPアーカイブの方が軽いようです。

閲覧にはCloudReadersを使っているのですが、出来ればiBooksを使いたいです。
なによりも、書籍をiTunesで管理したいのです。

そこでZIPアーカイブを、ePubに変換してみる事にしました。

まずPDFの場合はZIPアーカイブに変換する所からになりますが、こちらにすばらしい解説があります。

Desire for wealth
ScanSnap で作成した pdf を Mac OS X の標準機能で軽くする & zip に固める

ここで行うサイズ調整はかなり効果があります。

次にZIPアーカイブからePubへの変換ですが、こちらの情報によるとePubはそれほど複雑な物ではないようです。

横浜工文社
日本語Epubブックサンプル

ここの情報を参考に、変換スクリプトを書いてみました。
githubで公開したので、ZippedImagesToEpub.rbをダウンロードしてください。

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

gem install zipruby
gem install uuid

後は、スクリプトを実行します。

ruby ZippedImagesToEpub.rb ZIPアーカイブ

現状iPadを横画面にすると、画面の左側か右側のどちらかだけに画像が表示されてしまう書籍もあります
また著者やカバー画像などのメタデータもサポートしていません。
このあたりは、コツコツ改善していこうかと思っています。

GUIを付けても良いんですが、Automatorから使う方が幸せかもしれません。
RubyCocoaの本買うかな…

カテゴリー: Develop | コメントをどうぞ

Objective-CでYAMLをパースする

ZippedImages Etceteraはメタデータの書式にYAMLを使っています。
XMLは人間による読み書きには適さないとかいった理由はどうでも良いんですが、実装にあたってCocoaから使えるパーサが必要になりました。

残念ながら良さそうな好みにあったObjective-C用のライブラリが無かったので、C言語用のlibYAMLを使いました。
libYAMLはXMLで言う所のSAXのような逐次処理型なのですが、空間も時間も切迫しない用途なので、DOM的に使えるラッパークラスを書きました。

githubに、ビルド方法や使い方も含めて公開しました。

ZippedImages Etceteraに必要な分しか実装していないので、論理型とか、明示型指定なんかは未サポートです。サポートするプリミティブ型の追加は簡単だと思いますけど。
配列とか辞書とか構造体の方は出来ています。
あと、エミッタ側は作ってません。

libYAMLはパースまではやってくれるんですが、型判定なんかは使う側に任されています。
まあ、バインドする言語で型に差異があるので仕方無いような、もっとやりようがあるような。

YAMLのライブラリというとsyckが有名ですが、ソースをみたらyaccを使っていたので敬遠してみました。ビルドで力尽きそうだったので……

カテゴリー: Develop | コメントをどうぞ

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という名前のテキストファイルとして保存してください。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#!/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弄りが楽しいのでここまでに致したく存じます。

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

カテゴリー: Desktop, Develop | コメントをどうぞ

整理済みの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アーカイブを含むディレクトリ>

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/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
カテゴリー: Develop | コメントをどうぞ

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アーカイブの内容を一覧して、任意のファイルを取り出せます。単純化したかったので、ストリームには対応していません。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
//
//  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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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で公開しました。

カテゴリー: Develop | コメントをどうぞ

ZippedImages Etcetera 1.0をリリース

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

そこで蔵書を全部電子化して、ノートパソコンで持ち歩く方向で行く事にしました。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

カテゴリー: Develop | 3件のコメント

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

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

カテゴリー: Develop | コメントをどうぞ

Cocoa勉強会で発表してきました

なんか、前回ポストと同じタイトルですね。
iPhoneのカメラベースアプリについて発表してきました。
デモが一発で動かないとか涙目でしたが、なんとかなりました。

カテゴリー: Develop | コメントをどうぞ