‘Web’ カテゴリーのアーカイブ

swfmill 0.3.1 インストール失敗問題対策

2011 年 8 月 11 日 木曜日

PCではFlashから外部APIを叩いて情報を取得、ということができますが、日本の携帯ではそのあたりが禁じられているので、いわゆる「Flashアプリ」を作る際にはswfmillが結構な割合で使用されています。
これはswf←→xmlの変換を行なうサーバアプリケーションで、つまりテンプレートxmlを用意しておけば、ユーザーごとに異なる中身のFlashを提供することができるわけです。

さて、このswfmillですが、結構長い間、0.2.12にKLabパッチをあてて使用されていました(0.2.12はSJISが扱えない+FlashLiteがSJISだったため)。

ところが0.3.1がリリースされてKLabパッチが取り込まれたため、さてこれを使おうとすると、インストールできません。
正確には、configureは通るのにmakeが通りません。
バグ報告を見ると、0.3.1リリースの数日後(2010/7)には上がっているものの、動きは無いようです。
https://bugs.launchpad.net/swfmill

検索してみると、ソースファイルを書き換える例がいくつか出てきます。特に商用では正直ソースを書き換えるのはあまりやりたくないわけですが、そうも言ってられず。
makeのエラーからソースをたどり、数ヶ所程度の書き換えでmakeが通るようになりました。

しかし、この時私の目の前にはまだまだサーバが何台も残っていたわけです。まさか全部手作業で書き換えるのはありえない、というわけで、パッチを書いてみました。
swfmill.0.3.1.v2.patch
swfmill-0.3.1.tar.gzを展開したswfmill-0.3.1上で

patch -p0 < swfmill.0.3.1.v2.patch

を実行後、

./configure && make && make install

でmakeが通ると思います。
※2011/8/25追記:
makeが通ってできたバイナリで一部commandが正常動作しないようでしたので公開を停止しました。
※2011/8/27追記:
patchを修正・再UPしました。

※このパッチはAmazonEC2上のCentOS5.6でのみ動作確認済みです。他の環境では動作確認していません。
※このパッチを使用した結果について一切の責任を負いません。

CodeIgniterのimage_libでImageMagickを使った場合のバグ的挙動

2011 年 6 月 9 日 木曜日
なんか似たようなエントリが続いていて恐縮ですが、
同じようなことをしばらく続けているとやはりそういうことになるわけでして。
今回はバグと言えばバグだし、「こういう機能だ」と言われればそれまで、というものです。

さて、CodeIgniterにはimage_libというライブラリがありまして、
これがgdやImageMagickのラッパになっているわけです。
デフォルトがgdなわけですが、引数でImageMagickと渡してやるだけで使えることになっています。

で、やろうとしたことは、
・アップロードで受け取ったデジカメなどで撮った画像を定められた大きさの正方形に加工
というものです。
画像処理の流れは
・縦長か横長かを判定
・余分な部分をカット
ということになるはずです。
縦長/横長を判定するのは、まず短辺が指定のサイズになるよう縮小/拡大する必要があるためですね。
判定結果は、
$config['maintain_ratio'] = TRUE; $config['height'] = 50; $config['width'] = 50; $config['master_dim'] = ‘width’; $this->load->library(‘image_lib’, $config); $this->image_lib->resize();
のmaster_dimをheight/widthどっちに指定するかに使います。

次にcrop()を使うわけですが、このメソッドの数値の指定はちょっと癖があります。
この部分のドキュメントは
切り抜きメソッドは、次のように切り取る場所を指定する X および Y 軸 (ピクセル) を設定する必要があるのを除いて、 リサイズメソッドと大体同じように動作します:
と書かれています。
さらっと読むと「0〜X, 0〜Y」が切り抜き範囲なのかなと思ってしまうのですが、
実はこれは逆で、「X〜, Y〜」が切り抜き範囲なのです。
(0,0は画像左上端です)
ある大きさの画像を「左上端から50×50で切り抜きたい」とした場合、
オプションへの数値が渡しようがないように見えます。
$config['x_axis'] = 50; $config['x_axis'] = 50; $this->load->library(‘image_lib’, $config); $this->image_lib->crop();
では、「X=50, Y=50から画像終端まで」の切り抜きになってしまうからです。

で、ソースを見てみることにしました。

PHPでImageMagickを直接に使う場合、PECLのimageickライブラリを入れることが結構あるかと思いますが、CodeIgniterのimage_libの内部ではexecを叩いています。
つまり、
$this->load->library(‘image_lib’, $config);
とする時のオプション$configは、そのままconvertコマンドへ渡されるわけです。

ということは、先ほどのx_axis, y_axisの指定方法は、
CodeIgniterの問題ではなくImageMagickのオプションの指定方法の問題です。
ImageMagickをコマンドラインで叩く場合には、
convert -resize 50×50+25+25 file_org file_dist
というようにオプションを指定します。上記の例の場合は、
・50×50にリサイズした上でX=25, Y=25から画像の終端までを切り抜く
ということになります。

となると、「じゃあ0〜X, 0〜Yに切り取るのはどーするんだ?」ということになりますね。
covertコマンドで叩く場合、上記の例を0〜X, 0〜Yで切り抜くには
convert -resize 50×50-10-10 file_org file_dist
とします。この場合は
・50×50にリサイズした上で、画像左上端から10×10を切り抜く
ということになります。
反転させる場合にはマイナス指定、というのは、こういうものではよくある指定ですよね。

さて、ではCodeIgniterで
$config['x_axis'] = -50; $config['x_axis'] = -50; $this->load->library(‘image_lib’, $config); $this->image_lib->crop();
のように指定してみるとどうなるか。
…これは動作しません。image_libライブラリの580行目に、その答えがあります。
$cmd .= ” -crop “.$this->width.”x”.$this->height.”+”.$this->x_axis.”+”.$this->y_axis.” \”$this->full_src_path\” \”$this->full_dst_path\” 2>&1″;
まさかのプラス決め打ち!!右下側切り抜くことしか想定されていないとは!

…で、あまりキレイではありませんができるだけ元を残してこんな感じに修正。
— system/libraries/Image_lib.php 2011-03-28 16:25:12.000000000 +0900 +++ application/libraries/Image_lib.php 2011-06-08 23:52:47.000000000 +0900 @@ -577,7 +577,17 @@ if ($action == ‘crop’) { - $cmd .= ” -crop “.$this->width.”x”.$this->height.”+”.$this->x_axis.”+”.$this->y_axis.” \”$this->full_src_path\” \”$this->full_dst_path\” 2>&1″; + if($this->x_axis<0){ + $x_axis = $this->x_axis; + }else{ + $x_axis = “+”.$this->x_axis; + } + if($this->y_axis<0){ + $y_axis = $this->y_axis; + }else{ + $y_axis = “+”.$this->y_axis; + } + $cmd .= ” -crop “.$this->width.”x”.$this->height.$x_axis.$y_axis.” \”$this->full_src_path\” \”$this->full_dst_path\” 2>&1″; } elseif ($action == ‘rotate’) {
で、ちゃんと意図した通りに切り抜いてくれました。
system/libraries/Image_lib.php をapplication/libraries/Image_lib.php としてコピーして修正すれば上書き実行されるのでその点は便利。

このライブラリ、ExpressionEngineのコードだと冒頭コメントで宣言されているんですが、 大丈夫なのかExpressionEngine…。
バグ報告くらい上がってそうだと思ったんですが、
CodeIgniterの方にも、
https://bitbucket.org/ellislab/codeigniter/issues?q=image_lib
CI Reactorの方にも無いんですよねえ…。
https://bitbucket.org/ellislab/codeigniter-reactor/issues?q=image_lib

もうみんなそういう機能だと思って使ってるんでしょうかね?

requireとrequire_onceの違い + CodeIgniter 2.0.1のバグ

2011 年 5 月 20 日 金曜日
CodeIgniter2のファイルヘルパにget_mime_by_extension()というものがあります。ファイル名を投げるとmimeを返す関数です。

しかし、get_mime_by_extension(‘hoge.jpg’)とするとPHP Warningが出たので、system/helper/file_helper.phpにトレースを入れてテストしてみたところ、 application/config/mimes.phpをロードできていない様子。

そこでこのようなチケットを発見。
https://bitbucket.org/ellislab/codeigniter/issue/354/get_mime_by_extension-triggers-php-error
Some dirty trick is changing the source Code: require_once -> require
require_onceをrequireにすれば動くよ!とのこと。

ちなみにこの関数のソースは次のようになっています。
if ( ! function_exists(‘get_mime_by_extension’)){ function get_mime_by_extension($file){ $extension = strtolower(substr(strrchr($file, ‘.’), 1)); global $mimes; if ( ! is_array($mimes)){ if ( ! require_once(APPPATH.’config/mimes.php’)){ return FALSE; } } if (array_key_exists($extension, $mimes)){ if (is_array($mimes[$extension])){ // Multiple mime types, just give the first one return current($mimes[$extension]); }else{ return $mimes[$extension]; } }else{ return FALSE; } } }

requireとrequire_onceの違いは、ドキュメントをさらっと読むだけでは、それぞれ「毎回読み込む」「1回しか読み込まない」としか読み取れません。なによりドキュメントがひどい。
require_once()
http://php.net/manual/ja/function.require-once.php
_once の振る舞い、およびそれが _once なし版とどのように異なるのかについての情報は、 include_once() のドキュメントを参照ください。

include_once()
http://www.php.net/manual/ja/function.include-once.php
この関数の動作についての情報は include() のドキュメントを参照ください。

include()
http://www.php.net/manual/ja/function.include.php
読み込まれるファイルで定義された関数がある場合、 これらは、return()の前後によらず メインファイルで使用できます。 このファイルが二度読み込まれた場合、PHP 5は関数が定義済みであるため 致命的なエラーを発生します。 一方、PHP 4は return()の後に定義された関数については、 エラーを発生しません。 ファイルが読み込み済みであるかどうかを調べ、 読み込まれるファイルの内容を条件分岐で返すかわりに include_once()を使用することを推奨します。
えーと…「_once の振る舞い、およびそれが _once なし版とどのように異なるのかについての情報」はどこへ行ってしまったんでしょうか?
_onceの話が出てくるのはこの数行だけなんです。

これでは、「何回かinclude/requireする可能性のある個所では_onceにしとけばいいんだな」としか読み取れないでしょう。

しかし両者の違いは、こんなサンプルコードでも分かるはずです。
hoge.php <?php return “hoge”;

test.php <?php var_dump(require_once((dirname(__FILE__).”/hoge.php”))); var_dump(require_once((dirname(__FILE__).”/hoge.php”))); var_dump(require((dirname(__FILE__).”/hoge.php”)));

test.phpの実行結果は
string(4) “hoge” bool(true) string(4) “hoge”
となります。
つまり、2回目以降のrequire_onceは、指定したファイルの中身ではなく、boolean値を返します。
つまりこれは、いくつかあるincludeの挙動のうち、「return記述の無いPHP実行ファイルをincludeした場合の返り値」と同じ挙動になるわけです。

このことについてドキュメントには明記されておらず、しいて言えば、
include()
http://www.php.net/manual/ja/function.include.php
内の「例5 include()とreturn()文」のコード例に示唆されているだけです。
と同時に、
include() は、ファイルを見つけられない場合に warning を発行します。 一方 require() の場合は、同じ場合に fatal error を発行する点が異なります。
とあります。さらに、
require()
http://www.php.net/manual/ja/function.require.php
require() は include() とほぼ同じですが、失敗した場合に E_COMPILE_ERROR レベルの致命的なエラーも発生するという点が異なります。 つまり、スクリプトの処理がそこで止まってしまうということです。 一方 include() の場合は、警告 (E_WARNING) を発するもののスクリプトの処理は続行します。
ということは、require_onceを単純にIF文に突っ込むだけでは(そもそもこの記述はIDEに怒られると思いますが)判定は成り立たない、ということになります。

CI2.0.2では、この関数は次のように記述されています。
if ( ! function_exists(‘get_mime_by_extension’)){ function get_mime_by_extension($file){ $extension = strtolower(substr(strrchr($file, ‘.’), 1)); global $mimes; if ( ! is_array($mimes)){ if (defined(‘ENVIRONMENT’) AND is_file(APPPATH.’config/’.ENVIRONMENT.’/mimes’.EXT)){ include(APPPATH.’config/’.ENVIRONMENT.’/mimes’.EXT); }elseif (is_file(APPPATH.’config/mimes’.EXT)){ include(APPPATH.’config/mimes’.EXT); } if ( ! is_array($mimes)){ return FALSE; } } if (array_key_exists($extension, $mimes)){ if (is_array($mimes[$extension])){ // Multiple mime types, just give the first one return current($mimes[$extension]); }else{ return $mimes[$extension]; } }else{ return FALSE; } } }
多少PATHなどが変わった影響はありますが、ファイル存在判定と読み込み後のデータ存在判定を分離したこと、requireをincludeに変えたことで、処理が停止することが無くなった、という具合に修正されています。

じゃあ2.0.2を使えばいいのね、というわけにもいかないのが悩ましいところで、2.0.2は若干バギーだったため、2.0.3のリリースが急がれているようです。日本語版も2.0.2を飛ばして2.0.3に向かうとのこと。

とりあえずsystem/helper/file_helper.phpを直すか、get_mime_by_extension()を使わないか、という選択肢になりますかね…。