「Image::MagickとFile::Find::Ruleを用いて簡単一括サムネイル作成」の続き
以前書いたAutoResize.pmですが、サムネイルのサイズ指定やらファイル名等をコールバックで指定出来るようにすると便利なんじゃない?と思ったので息抜きがてらに修正。あとはFile::Find::RuleからFile::Find::Rule::MMagicに切り替えたので、MIMEタイプでファイルを指定出来るようになったりとか。
AutoResize.pm
package AutoResize;
use File::Find::Rule::MMagic;
use File::Basename;
use Image::Magick;
use Data::Dumper::Concise;
sub new{
return bless {};
}
sub in{
my $self = shift;
$self->{'in'} = shift;
return $self;
}
sub out{
my $self = shift;
$self->{'out'} = shift;
return $self;
}
sub name{
my $self = shift;
$self->{'name'} = \@_;
return $self;
}
sub magic{
my $self = shift;
$self->{'magic'} = \@_;
return $self;
}
sub convert{
my $self = shift;
my $cb = shift;
my $in_dir = $self->{'in'};
my $out_dir = $self->{'out'};
(-d $in_dir) || die("$in_dir : No such directory");
(-d $out_dir) || (mkdir $out_dir) || die("$out_dir : Can't make thumbnail directory");
my $rule = new File::Find::Rule::MMagic;
$rule->name( @{$self->{'name'}} ) if defined $self->{'name'};
$rule->magic( @{$self->{'magic'}} ) if defined $self->{'magic'};
@pict_files = sort $rule->in($in_dir);
# ファイル名にtitleが含まれるものは先頭に配置
for(0..$#pict_files){
my $tmp = $pict_files[$_];
(splice(@pict_files,$_,1) && unshift(@pict_files,$tmp)) if (basename($tmp) =~ /^title/i);
}
for(@pict_files){
my $image = { width => '', height => '', name => $_};
my $thumb = { width => '', height => '' , name => basename($_), dir => $out_dir };
my $q = new Image::Magick;
$q->Read($image->{name}) && (warn "ReadError:$image->{name} $!\n") && next ;
($image->{width}, $image->{height}) = $q->Get('width', 'height');
$cb->($image, $thumb);
next if ( ($thumb->{width} == '') and ($thumb->{height} == '') );
$q->Resize('width' => $thumb->{width}, 'height' => $thumb->{height});
$q->Write($thumb->{dir}.$thumb->{name}) && (warn "WriteError:$out_file $!\n");
}
return;
}
1;
シンプルな使用例:simple.pl
#! /usr/bin/perl
use strict;
use warnings;
use AutoResize;
my $pict_dir = './book/000000/';
my $thumb_dir = './thumb/000000/';
# コールバックで画像サイズや出力ファイル名の指定が出来ます
my $cb = sub{
my $orig = shift;
my $thumb = shift;
$thumb->{width} = int($orig->{width} /2);
$thumb->{height} = int($orig->{height}/2);
};
print "in: $pict_dir out:$thumb_dir\n";
my $q = new AutoResize;
$q->in($pict_dir)->out($thumb_dir)->name("*.jpg")->magic("image/jpeg")->convert($cb);
print "complete.\n";
1;
実際はこんな感じで使ってます:autoResize.pl
#! /usr/bin/perl
use strict;
use warnings;
use AutoResize;
my $id = shift || die('Usage: autoResize.pl BookID');
$id = sprintf('%06d',$id);
my $pict_dir = './book/'.$id.'/';
my $thumb_dir = './thumb/'.$id.'/';
print "in: $pict_dir out:$thumb_dir\n";
my $q = new AutoResize;
$q->in($pict_dir)->out($thumb_dir);
$q->name("*.jpg","*.JPG","*.jpeg","*.JPEG","*.png","*.PNG","*.gif","*.GIF","*.bmp","*.BMP");
my $count = 0;
my $cb = sub{
my $orig = shift;
my $thumb = shift;
if($orig->{width} < 150){
$thumb->{width} = int($orig->{width}/2);
$thumb->{height} = int($orig->{height}/2);
}else{
my $ratio = $orig->{height} / $orig->{width};
$thumb->{width} = 150;
$thumb->{height} = int(150 * $ratio);
}
my $name = sub{
$count++ . ".jpg";
};
# 連番で保存
$thumb->{name} = $name->();
};
$q->convert($cb);
print "complete.\n";
1;
コールバックで条件書けるにしてみたけど、convert->("100x100")やらconvert->("50%")みたいに画像サイズを直接渡せるともっと便利なんじゃないかなという気もしますね。
ジョブキューサーバ Gearmanの紹介
先週Gearman::Serverのコードを読んでいたのでその内容でも書こうかと思ったのですが、その前にGearman自体を紹介しなきゃいけないのかなと思って書いてみました。
Gearmanとは何か
Gearmanは(LiveJournalやmemcachedの産みの親である)Brad FitzpatrickによってPerlで書かれたオープンソースの分散型ジョブキューサーバです。(後にBrian AkerとEric DayによってC言語で書き直されました)
Gearmanの特徴
- オープンソース
- 他言語対応(Perl, C, PHP, Ruby, Python, Java etc)
- フレキシブル(特定のデザインパターンに縛られることなく、お手軽に実装可能)
- シンプルなプロトコルとインタフェースを採用している為オーバーヘッドが小さい
- 軽量かつコンパクトなので簡単にアプリケーションに組み込むことが出来る
- 単一故障点がない(フォールトトレラントなシステムを構築することが可能)
Gearmanで出来ること
時間が掛かる処理を非同期で実行させたり、分散して複数のサーバに実行させたりすることが可能です。
非同期処理の例として、画像のアップローダがしばしば挙げられます。アップロードとリサイズを一連の処理として行う場合、画像のリサイズが終了するまで制御が戻らないのですが、非同期に処理を実行させることでリサイズ処理の終了を待たずにレスポンスを返すことが可能です。その他にも、大量のログのアナライズや動画のエンコード等、時間の掛かる処理をリアルタイム性を損なう事なく実行する事が可能です。
Gearmanを使うメリット
非同期処理の実装は幾つかあります。例えば前の項で挙げたアップローダの場合、forkして子プロセスにリサイズ処理を委譲する形で実装することも可能ですが、その場合自分でプロセス管理をしなければなりません。
Gearmanを使えば、キューをJobServerに投げるだけなので非常にシンプルに実装出来ます。また、Worker(実際にキューを受けて実行するプロセス)とApache等のフロントエンドを別サーバで動かすことで負荷を分散させたり、Workerを動かすサーバを増やすことでボトルネックを解消したりすることが可能です。
Gearmanを使う上での注意点
Gearmanは標準ではジョブキューの永続化をサポートしていません。キューはメモリ上で管理されているので、異常終了時にジョブが消える可能性があることに留意する必要があります。RDBMSを利用した代替の選択肢としてはTheSchwartz等が挙げられます。(IBMの資料に永続化の話があったので公式サイトのドキュメントを読み直したらPersistent Queuesという項がありました。斜め読みなのですがgearmandの起動時のオプションでバックエンドとして利用するDBを選択することでジョブキューを永続化出来るようです)
参考文献
- Gearman
- Gearman - Wikipedia, the free encyclopedia
- O'Reilly Webcast: Introduction to Gearman
- Gearman を使って PHP アプリケーションのワークロードを分散する
- web2.0 時代のジョブキューサーバー Gearman と TheSchwartz の関係について - TokuLog 改め だまってコードを書けよハゲ
とりあえずここまで。続きとしてはGearmanの構成やGearmanを使ったコーディングの実例、Gearmanの実装等を予定していたのですが、今はAnyEventと格闘しているので気が向いたら書きたいと思います。
dfの出力結果をスクリプトで見やすくカスタマイズする
LVM使用時に、デバイス名が長くなるためにdfの出力が自動改行されるのが邪魔なので、好きなフォーマットで出力しようという話。ディスク監視のコード書いてる時に「改行うぜー」と思って調べたら--portabilityというオプションがあることを知りました。
df -hの出力結果:

diskstat.plの出力結果:

diskstat.pl:
#! /usr/bin/perl
use strict;
use warnings;
my @disk = `df --portability -h`; shift @disk;
for(@disk){
my($dev, $block, $use, $available, $per, $mount ) = split;
printf "%-20s : %5s(%4s)\n",$mount,$available,$per;
}
1;
参考:[linux-users:106733] Re: LVM使用時の df 出力結果について
File::Find::Rule::MMagicのベンチ
テスト条件
ディレクトリ10個、画像ファイル300弱
#! /usr/bin/perl
use strict;
use File::Find::Rule::MMagic;
use File::Find::Rule;
use Time::HiRes;
my $s_time = Time::HiRes::gettimeofday;
my $find = new File::Find::Rule;
for(0..9){
my $id = sprintf("%06d",$_);
my @file = $find->file->name('*.jpg')->in("./$id");
}
my $e_time = Time::HiRes::gettimeofday;
print "File::Find::Rule\n";
print $e_time - $s_time ."\n\n";
my $s_time = Time::HiRes::gettimeofday;
my $find = new File::Find::Rule::MMagic;
for(0..9){
my $id = sprintf("%06d",$_);
my @file = $find->file->magic('image/*')->in("./$id");
}
my $e_time = Time::HiRes::gettimeofday;
print "File::Find::Rule::MMagic\n";
print $e_time - $s_time ."\n";
1;
結果
File::Find::Rule
0.0299019813537598
File::Find::Rule::MMagic
2.19269204139709
ファイルを毎回開いている分遅いですが、思った以上に遅かったです。
拡張子が信用できる状況であればわざわざ使う必要はないかも。
Image::MagickとFile::Find::Ruleを用いて簡単一括サムネイル作成
所用で20万件ほどサムネイルを作成することになった
→シェルスクリプトからconvertを回してみたけど使いづらい。
→Image::MagickとFile::Find::Ruleを使えば幸せになれるんじゃないか。
→やってみた!
AutoResize.pm (File)
package AutoResize;
use File::Find::Rule;
use File::Basename;
use Image::Magick;
sub new{
return bless {};
}
sub in{
my $self = shift;
$self->{'in'} = shift;
return $self;
}
sub out{
my $self = shift;
$self->{'out'} = shift;
return $self;
}
sub serial{
my $self = shift;
$self->{'serial'} = shift;
return $self;
}
sub _outfile{
my $self = shift;
$self->{'out'}.$self->{'count'}++.'.jpg' if (defined $self->{'serial'});
}
sub _calc_pixel{
my ($orig_width, $orig_height) = (shift, shift);
my ($th_width, $th_height);
# width under 150px, size at 50%
if($orig_width < 150){
$th_width = int($orig_width/2);
$th_height = int($orig_height/2);
}else{
$th_height = int($orig_height/($orig_width/150));
$th_width = 150;
}
return $th_width, $th_height;
}
sub convert{
my $self = shift;
my $in_dir = $self->{'in'};
my $out_dir = $self->{'out'};
(-d $in_dir) || die("$in_dir : No such directory");
(-d $out_dir) || (mkdir $out_dir) || die("$out_dir : Can't make thumbnail directory");
my $rule = new File::Find::Rule;
my @pict_files = sort $rule->file()->name("*.jpg","*.JPG","*.jpeg","*.JPEG",
"*.png","*.PNG","*.gif","*.GIF","*.bmp","*.BMP")->in($in_dir);
# ファイル名にtitleが含まれるものは先頭に配置
for(0..$#pict_files){
my $tmp = $pict_files[$_];
(splice(@pict_files,$_,1) && unshift(@pict_files,$tmp)) if (basename($tmp) =~ /^title/i);
}
for(@pict_files){
my $in_file = $_;
my $out_file = $self->_outfile || $out_dir.basename($in_file);
my $q = new Image::Magick;
$q->Read($in_file) && (warn "ReadError:$in_file $!\n") && next ;
my($th_width, $th_height) = _calc_pixel( $q->Get('width','height') );
$q->Resize('width'=>$th_width, 'height'=>$th_height);
$q->Write($out_file) && (warn "WriteError:$out_file $!\n");
}
return;
}
1;
in(dir)で指定したディレクトリ以下の画像ファイルを探索して、out(dir)にサムネイルファイルを出力します。出力する際にサムネイルのディレクトリが存在しなければ作成します。サムネイルはアスペクト比を維持したまま、元ファイルが横幅150px以上であれば150pxに、150px未満であれば1/2のサイズにしてあります。serial()でサムネイルのファイル名を連番にすることが可能(デフォルトでは元ファイルと同じ名前)
使用例
autoResize.pl:
#! /usr/bin/perl
use strict;
use warnings;
use AutoResize;
my $id = shift || die('Usage: autoResize.pl BookID');
$id = sprintf('%06d',$id);
my $pict_dir = './book/'.$id.'/';
my $thumb_dir = './thumb/'.$id.'/';
print "in: $pict_dir out:$thumb_dir\n";
my $q = new AutoResize;
$q->in($pict_dir)->out($thumb_dir)->serial('on')->convert;
print "complete.\n";
1;
(2011/05/13 追記)
モジュール修正しました。
「Image::MagickとFile::Find::Ruleを用いて簡単一括サムネイル作成」の続き

