« 2007年10月 | メイン | 2007年12月 »

2007年11月29日

User Agent Profileについて
このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

harukiです。

端末に関する詳細な情報が少ないため苦労することが多いケータイサイト開発ですが、 その手がかりとなる情報にUser Agent Profile(UAProf)があります。

国内で対応しているのはSoftBankの3G端末です。

SoftBankの3G端末はリクエストにX-Wap-Profileヘッダがある端末があります。

(例)SoftBank 911SHの場合

X-Wap-Profile:http://www.sharp-mobile.com/UAProf/911SH_SHJ001_3g.xml

http://www.sharp-mobile.com/UAProf/911SH_SHJ001_3g.xml

 

UAProfについては、以下がとてもわかりやすいです。
Forum Nokia - User Agent Profile の基本 - ツール & SDK - テクニカルインフォメーション
http://www.nokia.co.jp/forum/developer/tech_doc/browsing/user_agent_profile/

 

国内のケータイでは標準の以下の6つに加え

  • HardwarePlatform
  • SoftwarePlatform
  • BrowserUA
  • NetworkCharacteristics
  • WapCharacteristics
  • PushCharacteristics

別途、以下の2つも含まれています。

  • MmsCharacteristics (MMSに関するプロパティ)
  • Streaming (ストリーミング関連)

細かい情報などは以下にあります。

 

しかし残念なことに、技術資料のHTTP編によると、

2007年以降発売の3GC端末では、本ヘッダを送出しない端末がある。
と書かれていて、実際にSoftBank 913SHなどでは送出されてきません。

UAProfも間違いがあったりして、100%信用できる情報ではありませんが、情報が少ないケータイサイト作成者には有益な情報ですので、 メーカーの方々は是非とも情報を提供していただきたいです。

海外ケータイのUAProf

海外でのDDR(Device Description Repository)は以下のようなところもあります。(日本のケータイの情報は足りないです)

上記のWURFLのAPIを使ったものとしてWALLがあります。 WALLは共通のマークアップを書くことにより端末に応じてタグを吐き分けてくれるライブラリです。 http://wurfl.sourceforge.net/java/index.php
WALL 4 PHP
http://wall.laacz.lv/

 

ケータイサイト開発で手詰まりになった際には、(もしかしたら)手がかりになることもあるかもしれません。

2007年11月28日

PHPでJSONを扱う
このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

yamaokaです。

Ajaxの普及に伴い、JavaScriptで扱いやすいJSON形式で サーバーからのレスポンスを返すことが増えてきているように思います。 PHPでJSONを扱う方法についていくつか紹介します。

JSON関数

もともとPECLの拡張モジュールとして提供されていましたが、 PHP 5.2.0以降、デフォルトでPHPに組み込まれるようになっています。 そのため、最も利用しやすい形式なのではないかと思います。

利用方法は以下のとおりです。json_decodeの戻り値はオブジェクトになります(第2引数にtrueを指定すると連想配列になります)。

$values = array('company' => 'ウノウ', 'name' => 'yamaoka');

$json = json_encode($values);
// string '{"company":"\u30a6\u30ce\u30a6","name":"yamaoka"}' (length=49)

$values = json_decode($json);
// object(stdClass)[1]
//   public 'company' => string 'ウノウ' (length=9)
//   public 'name' => string 'yamaoka' (length=7)

Jsphon

JsphonHawkさんの開発によるJSONライブラリです。 PHPで記述されており、PEARコマンドを利用して簡単にインストール、利用することができます。 Unicode Escape Sequenceをきちんと扱うことができるのが特徴です。日本語を扱う場合、心強いですね。

利用方法は以下のとおりです。Jsphon::decodeの戻り値は連想配列になっています。

require_once 'Jsphon.php';
$values = array('company' => 'ウノウ', 'name' => 'yamaoka');

$json = Jsphon::encode($values);
// string '{"company":"\u30a6\u30ce\u30a6","name":"yamaoka"}' (length=49)

$valeus = Jsphon::decode($json);
// array
//   'company' => string 'ウノウ' (length=9)
//   'name' => string 'yamaoka' (length=7)

PHP4とPHP5、また、PEAR環境の有無によってもエラーハンドリングが異なるので注意が必要です。 詳しくはJsphonのページのエラー処理に関する記述を参照してください。

Zend_Json

Zend_JsonはZend Frameworkに含まれるJSONを扱うためのコンポーネントです。 詳細はZend Frameworkのドキュメントを参照してください。

利用方法は以下のとおりです。Zend_Json::decodeの戻り値は連想配列になっています。

require_once 'path/to/Zend/Json.php';
$values = array('company' => 'ウノウ', 'name' => 'yamaoka');

$json = Zend_Json::encode($values);
// string '{"company":"\u30a6\u30ce\u30a6","name":"yamaoka"}' (length=49)

$valeus = Zend_Json::decode($json);
// array
//   'company' => string 'ウノウ' (length=9)
//   'name' => string 'yamaoka' (length=7)

ちなみに、Zend_Json::useBuiltinEncoderDecoderの値をtrueにすると、 JSON関数が利用できる場合はそちらを使うように挙動が変更されます。

まとめ

Zend_Jsonのように、フレームワークに JSONを扱う機能が備わっている場合はそれを使うのが一番手軽だと思います。 そうでない場合、JSON関数かJsphonを利用することになるでしょう。 ただ、日本語を扱う場合はUnicode Escape Sequenceをきちんと扱うことができる Jsphonの方が安心かもしれませんね。

2007年11月19日

いまさら、ディレクトリトラバーサルについて語ってみる
このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

Keitaです。
ディレクトリトラバーサルという言葉があります。
今では、常識になっており、開発するときには無意識に対策する(されている)人がほとんどだと思います。
ただ、DBにデータを格納することが当たり前の昨今ファイルの扱いをちゃんとできない人もたまーにお会いするのでので、個人的にPHPでやっていることを書いておきます。

どんなものか

詳しい定義は各自調べてもらうとして一例を一つ。
次のコードをみてください
<?php
    $file = $_GET['file'];
    $dir = '/pngdir/';
    $filepath .= $dir . $file;

    if (! file_exists($filepath)) {
        $filepath = $dir . 'nofile.png';
    }

    header("Content-Type: image/png");
    header("Content-Length: " . filesize($filepath));
    $fp = fopen($filepath,'rb');
    fpassthru($fp);
概要としては、「/pngdir/」には、PNG画像があると想定して、「http://example.com/hoge.php?file=ero.png」といったアクセスがあったら、/pngdir/ero.pngを表示するような簡単なコードです。

このコードを見たらすぐに、脆弱性があることがわかる人がほとんどだと思います。

http://example.com/hoge.php?file=../etc/passwd
とすれば、/pngpath/../etc/passwd = /etc/passwdのファイルをそのまま表示しようとします。

システム上の任意のコードが見られてしまうと、たとえば、ソースコードや、パスワードファイルなど秘密にしておくべきファイルが見られますし、何より、個人のサーバなどで性的なファイルをおいた場合の被害は計り知れません。
というわけで、こういう要件に対して安全にコードを書く方法を説明したいと思います。

入力値をチェックする


想定してない文字列のチェックはあってしかるべきですが実際の処理直前で
PHPの関数であるrealpathがそれです。
使い方は、マニュアルを見てもらうとして、これは、渡されたパス文字列の正規化を行います。
シンボリックリンクの場合は、実際のファイルのパスが渡されます。

ただし、../etc/passwdを指定されたときも、上記は/etc/passwdと帰ってくるのでこれだけではたりません。
そこで、そのディレクトリが公開していいファイルのディレクトリかをチェックするするようにします。

そこらへんの処理をまとめるとこんな感じになります。
function is_public_file($public_dir, $path)
{
    if (! is_string($path)) {
        return false;
    }

    $realpath = realpath($path);

    if ($realpath === false) {
        return false;
    }

    if (file_exists($realpath) === false) {
        return false;
    }

    if (strncmp($public_dir, $realpath, strlen($public_dir)) === false) {
        return false;
    }

    return true;
}
realpathが、 BSD システム以外ではファイルが存在しない場合には、falseを返す仕様なので、ファイルの有無も一緒にチェックするようにしてあります。

というわけで、下記のようなコードになりました。
<?php
    $file = $_GET['file'];
    $dir = '/pngdir/';
    $filepath .= $dir . $file;
    $filepath = realpath($filepath);

    if (is_public_file($dir, $filepath) === false) {
        //本来は不正なURLを書いた場合は、適切なロギングやエラー処理をするべき
        $filepath = $dir . 'nofile.png';
    }

    header("Content-Type: image/png");
    header("Content-Length: " . filesize($filepath);
    $fp = fopen($filepath,'rb');
    fpassthru($fp);
もう少しきれいにかけそうな気がひしひしとしますが、こんな感じで概念はつかんでいただけたのではないかと思います。

2007年11月16日

Apache で特定のコンテンツへのアクセスに対して帯域制限しよう
このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

こんばんは、最近自転車のライトを交換した naoya です。

今日は、Apache で特定のコンテンツへのアクセスに対して帯域制限する方法を紹介したいと思います。まず、特定のコンテンツの帯域を制限しようと思った背景から説明したいと思います。フォト蔵では、写真と動画をサポートしています。そのため、動画へのアクセスが増えると他の写真などへのコンテンツに対するレスポンスが悪くなってしまうことがあります。動画は、FLV 形式ですが比較的ファイルサイズが大きいものが多いため、FLV のみ帯域制限を行ってみることにしました。

まず、Apache で帯域制限できるモジュールについて調査しました。調査には、Software Design 2007年9月号をおもに参考にさせていただきました。

 

Software Design (ソフトウエア デザイン) 2007年 09月号 [雑誌]

 

Software Design (ソフトウエア デザイン) 2007年 09月号 [雑誌]

posted with amazlet on 07.11.15

技術評論社 (2007/08/18)

Software Design 2007年9月号では、いくつかの Apache の帯域制限できるモジュールが紹介されていたのですが、今回選択したモジュールは mod_bw です。

mod_bw を選択した理由は、次のとおりです。

  • バーチャルホストに対応している
  • 特定のコンテンツ (MIME) 別に設定できる
  • 帯域設定が柔軟にできる
  • Apache 2 系で動作する

それでは、mod_bw のインストール方法について説明します。

  1. mod_bw を公式サイトからソースコードをダウンロードします(現時点での最新版は 0.8 です)
  2. 展開してコンパイルします apxs -i -a -c mod_bw.c
  3. コンパイル後、mod_bw.so ファイルを /usr/lib/httpd/modules へコピーします

次に設定方法ですが、Apache の設定ファイル httpd.conf に、mod_bw を読み込むように、次の設定を追加します。

LoadModule bw_module          /usr/lib/httpd/modules/mod_bw.so

次に、FLV に対して帯域制限する前に、通常の Apache の設定では FLV に対する MIME タイプは設定されてないので、次の設定を追加します。

AddType video/x-flv .flv

次に実際の帯域制限ですが、例えば次のように追加します。この設定は、すべてクライアントに対して最大 1024 bytes/s の制限を行うという意味になります。この設定は、VirutalHost の中に追加します。

       BandWidthModule On
       BandWidth all 102400
       MinBandWidth all -1
       AddOutputFilterByType MOD_BW video/x-flv

最後に動作確認は、ab (Apache Bench) か、実際にファイルをダウンロードしながら正しく帯域制限が行われているかチェックします。

今回は、Apache で特定のコンテンツのみ帯域制限する方法について紹介しました。帯域には限りがあるので、サイトを訪れてくれたユーザに対して快適なサービスを提供していきたいですね。

2007年11月14日

jQueryのパフォーマンス最適化に関するTips
このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

こんにちは、山下です。
今回は、jQueryのパフォーマンス最適化について説明したいと思います。

軽量と言われているjQueryですが、いろいろな機能を実現しようとして複数のプラグインを導入すると、だんだんと動作が重くなってきます。サーバ側をいくらチューニングしたところで、ブラウザ側での処理に時間がかかっていたら、せっかく訪問してくれたユーザに重いサイトとして認識されてしまいます。以下に、ウノウで運営している「映画生活」で実際に行っている方法を紹介します。

1. Packed版ではなくMinified版を使う

jQuery1.1まではPacked版のみだったのですが、jQuery1.2からMinified版もダウンロードできるようになりました。Packed版よりもMinified版を使うことをお勧めします。どう違うのかというと、Packed版はファイルサイズを極限まで削減するために静的辞書+可読文字符号化という方法を使っているのですが、ページが読み込まれる度に展開処理が必要になり、無駄な処理が発生します。

これに対してMinified版は、コメントや無駄な空白などを取り除くだけですので圧縮率は低いのですが、無駄な展開処理が必要ありません。最新のPCを使っている場合は気にならないかもしれませんが、古いPCを使っている場合にこの差は顕著になります。静的辞書法については、JavaScriptの圧縮 - daily dayflowerに詳しく書かれていますので参考にしてください。

以上のような理由からjQuery1.2からMinified版が提供されるようになったのですが、まだ1.1.4を使っている場合には、自分でMinified版を作成しましょう。私はJSMinのPython版を使っています。jsmin.pyをダウンロードして次のように実行します。

$ python jsmin.py <some.src.js >some.js

2. 代わりにmod_deflateなどを使う

上で説明したように、Minified版はPacked版より圧縮率の点で劣るのですが、Webサーバの機能を使って通信時にgzip圧縮することで圧縮率を高めることができます。WebサーバにApache2を使っている場合は、mod_deflateが利用可能です。

設定例:

LoadModule deflate_module modules/mod_deflate.so
<FilesMatch "\.(html|css|js)$">
SetOutputFilter DEFLATE
</FilesMatch>

3. プラグインを一つのファイルにまとめる

すべてのページで読み込まれるプラグインなどは、jquery.jsと一緒にまとめてしまいましょう。ブラウザがWebサーバと同時に接続できるコネクション数は限られています。また、通信のオーバーヘッドをできるだけ減らすことで、レスポンスの向上が期待できます。
プラグインのjsファイルは圧縮されていないことが多いので、前述のJSMinなどを使って忘れずにMinified処理をしておきます。

4. 自動で実行される処理を止める

ThickBoxはよく使われるjQueryプラグインで、aタグにclass="thickbox"を追加するだけで画像のエフェクト表示を行うことができます。これは、JavaScriptが分からない人でも手軽に使えて便利なのですが、jQuery(document).ready()関数で毎回イベントの割り当が行われるので非常に効率が悪いです。

$(document).ready(function(){   
  tb_init('a.thickbox, area.thickbox, input.thickbox'); ← 削除
  imgLoader = new Image();// preload image
  imgLoader.src = tb_pathToImage;
});

<a class="thickbox">の代わりに、次のようにHTML内の記述を変更します。

<a href="javascript:tb_show('タイトル',
                             '/hoge/?height=300&width=300')">

ThickBoxに限らず、利用するプラグインのソースには目を通して、無駄な処理が実行されていないか確認しましょう。

5. Expiresをできるだけ長く設定する

ブラウザにキャッシュを使ってもらうようにExprireの期限をできるだけ未来に設定します。Apacheの場合は次のように設定します。

Expires On
ExpiresByType text/javascript "access plus 30 day"
ExpiresByType application/x-javascript "access plus 30 day"

6. class指定ではなくid指定にする

以前のエントリで紹介したように、jQueryでは $('div.hoge') などとclass指定でノードを選択することが簡単にできるのですが、class指定での選択はid指定よりも遅いので可能な限り、$('#hoge')のようにidを指定するようにします。HTMLの構造上できない場合もあるかもしれませんが、速度が変わってくることは覚えておいたほうが良いと思います。

 

2007/11/14 17:20追記

コメント欄で質問されたので、下記のようなコードで軽くベンチマークを取ってみました(MacBook+Firefox)。#hogeだと速いのですが、div#hogeだと逆に遅くなってしまうようです。ですので、初掲時のdiv#hogeを#hogeに修正させていただきました。

var txt = '';

var date = new Dae;
for (var i = 0; i < 10000; i++) txt = $('#hoge').html();
alert(new Date - date); // 582

date = new Date;
for (var i = 0; i < 10000; i++) txt = $('div#hoge').html();
alert(new Date - date); // 4771

date = new Date;
for (var i = 0; i < 10000; i++) txt = $('div.hoge').html();
alert(new Date - date);  // 4595

 

以上、jQueryのパフォーマンス最適化について説明してきましたが、これによって向上できる時間は数十〜数百ミリ秒かもしれません。しかし、新しい機能を追加するよりもパフォーマンス・チューニングを行ったほうがアクセス向上に役立ったというデータもあり、サイトを訪れてくれたユーザに少しでも快適に使ってもらうために有効だと思います。

2007年11月11日

ブラウザから使用できる仕様書を書くツールまとめ
このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

こんにちはsatoです。Windows以外の環境で仕様書を書こうと思うと、なかなか良いアプリがなかったりまします。今回はブラウザから使用できる仕様書作成ツールをまとめてみました。

1) オンラインで配置図などを描く

Gliffy

配置図などをオンラインで描くことができます。また 画像への出力なども対応しています。いわゆるVisioの代わりになるツールです。

2) pdfファイルをtextに変換する

PDFTextOnline

pdfをtextに変換することができます。pdfの内容をスクリプトなどで切り出して、データ化するときなどに役に立ちます。他にもいろいろな変換するサイトやツールがあるのですが、ここは日本語が正常に通って見た目通りに変換できます。

3) データベースのテーブル定義などを書く

WWW SQL Designer

オンラインでER図などが書けます。以前miyakeさんが紹介していますが、かなり便利です。

4) Word, Excel, PowerPoint の代わり

Google Docsで一通りのことができます。また、pdfへの出力も簡単にできて、重宝します。

他にも便利なツールがあったら教えてください!

2007年11月 9日

Webアプリテスト2008年問題について考える会のお知らせ
このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

こんにちは! やまもと@テスト番長です。

早いものでもう年末が近づいて参りましたね。
家の近所にディスプレー(ショーウィンドウやデパートなどの飾り)の工房があるのですが、今急ピッチでクリスマスツリーを量産しています。

さてさて、唐突ですが皆さんはWebアプリテスト2008年問題ってご存知ですか?
え?聞いたことないですか?それもそのはず、今自分が発案しました。

実は、常々ウノウラボを見てくださっているQAエンジニアの方々と、
皆さんの職場はどんなテストをしているのかとか、使っているツールは?など
直接お話してみたいなーと思っておりまして。
近々、忘年会シーズンに先駆けて一席用意してみようかなと決心した次第です。
ただのオフ会だとなんなので、勉強会風にしようかとお題を考えてみたのが2008年問題の真相です。
勉強するふりをして飲みに出るもっともらしい口実とも言います。

そんなわけで、QAエンジニアを生業になさっている方同士で情報交換など致しましょう。
参加を希望される方は、このエントリにコメントをお願いします。
後日ご連絡差上げますので、メールアドレスをお忘れなく。
もしくは、「やまもと」@unoh.netまでメールください。

日時ですが、2007/11/27(火) 19:30~ を予定しています。
場所はウノウのある渋谷とさせてください。
人数がどのくらいになるのか等全く未知数なので、
詳細については参加希望を募ってから考えさせてください。

それでは、ご参加お待ちしています!


11/19追記:
詳細が決まりました。
1時間ほど軽く勉強会を行ったのち、懇親会を行います。
まだ多少席が残っていますので、ご興味のある方はご連絡ください。

■「Webアプリテスト2008年問題について考える会」
開催日時 2007/11/27(火) 19:30~
場所 ウノウ株式会社 会議室

勉強会の内容
・自己紹介など(10分)
・ウノウ社内見学ツアー(5分)
・2007年のWEBアプリテスト事情を振り返り(やまもと:15分)
・2008年問題についてブレスト大会(大喜利)
#Ustreamで実況または録画してブログで公開する予定です。

懇親会 20:45分頃から
#お店は未定ですが、近所の「テスト済み」で安心できる所を押さえます。

それでは、宜しくお願い致します!


11/27追記:
勉強会の様子はUstreamで実況させていただく予定です。
アドレスはこちらです。
http://www.ustream.tv/channel/qa-meeting

どうぞ宜しくお願い致します。

2007年11月 6日

JavaでMP3を再生する
このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

こんにちは。NAKAMURAです。最近ではFlashでMMLやDTMの話題など、音楽好きには嬉しい情報が飛び交っています。 ここは音楽ネタで便乗してみたいところですので、JavaでMP3を再生する方法を紹介してみたいと思います。

Java Media Framework APIを利用する方法もありますが、今回はLGPLライセンスで公開されている JLayerを利用してMP3を再生してみます。

再生してみる

早速、JLayerを使ってMP3を再生するサンプルクラスを作ってみました。ダウンロードページよりjarを取得してクラスパスに追加しています。

package net.unoh.mp3;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

import javazoom.jl.decoder.JavaLayerException;
import javazoom.jl.player.Player;

public class MyPlayer {

    private Player player;

    private BufferedInputStream stream;

    public void play(String file)
    throws JavaLayerException, FileNotFoundException {
        stream = new BufferedInputStream((new FileInputStream(file)));
        player = new Player(stream);
        player.play();
    }

    public void close() throws IOException {
        if (player != null) {
            player.close();
        }
        if (stream != null) {
            stream.close();
        }
    }

    public static void main(String[] args) {
        MyPlayer player = new MyPlayer();
        try {
            player.play("Pull Me Under.mp3");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (JavaLayerException e) {
            e.printStackTrace();
        } finally {
            if (player != null) {
                try {
                    player.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}

実行すると、お気に入りのPull Me Underを再生することが出来ました。

少しだけ深追いしてみる

これだけでは面白くありませんので、JLayerのソースコードを少し追ってみることにします。

javazoom.jl.player.Playerクラスのplayメソッドをみてみると、

while (frames-- > 0 && ret)
{
    ret = decodeFrame();
}

というwhileループがあります。ここが再生のループのようです。

次にdecodeFrameメソッドを見てみると、audioという変数がwriteしているのがわかります。

out = audio;
if (out!=null)
{
    out.write(output.getBuffer(), 0, output.getBufferLength());
}

続いて、audio変数の実体のJavaSoundAudioDeviceFactoryを見てみるとwriteImplメソッドでSourceDataLineのwriteをしていることがわかります。

protected void writeImpl(short[] samples, int offs, int len)
    throws JavaLayerException
{
    if (source==null)
        createSource();

    byte[] b = toByteArray(samples, offs, len);
    source.write(b, 0, len*2);
}

これで音を出力することができるのがわかりますが、MP3のフォーマットの制御はどうなっているのでしょうか。

もう一度PlayerクラスのdecodeFrameメソッドを見てみると、decoder変数がアウトプットに必要な情報を取得しているようです。

SampleBuffer output = (SampleBuffer)decoder.decodeFrame(h, bitstream);

javazoom.jl.decoder.DecoderクラスのdecodeFrameメソッドから呼ばれているretrieveDecoderメソッドでMP3のメタ情報の読み込みが行われているのが解ります。

まとめ

JavaでMP3を再生してみました。javax.sound.sampled.AudioFormatクラスやjavax.sound.sampled.SourceDataLineクラスを使ったことのある人であれば、大体想像通りのことをやっているなと思われたのではないでしょうか。

Javaで音を再生してみよう思われた方の参考になれば幸いです。

2007年11月 5日

Linuxをネットワーク経由で自動インストールする方法(前編)
このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

miyakeです。今回は近頃流行りの、サーバの自動インストール方法をご紹介します。

1エントリにまとめるには内容が多かったので、2回に渡って書きたいと思います。ひとまず今日のところは「ネットワークブートから手動インストール」までです。

自動化の部分を期待された方は申し訳ありませんが、次回のエントリをお待ちください(普段より早めに書きたいとは思います)。

ウノウラボでは「 ベンチャー流サーバ構築のススメ(ソフトウェア編)」と題して、OSをインストールではなくコピーする方法をご紹介しています。両者を簡単に比較してみると、それぞれ以下のような特徴があります。

■OSコピーのメリット

新サーバの環境構築の手間がほとんどない
最低限、IPアドレスの設定だけ変更すればすぐに実践投入できます。
完全に同じ構成のサーバを用意出来る
当然ながら、インストールされたパッケージだけでなく、サーバ内のファイルも同じになります。

既存のサービスで負荷が大きくなった時に即増設、といった用途には非常に便利です。

■自動インストールのメリット

物理的にディスクを移動する必要がない
コピーする場合はHDDを物理的につなぎ換えますが、その必要がありません。
サーバの構成を完全に把握できる
インストールするパッケージを設定ファイルで指定するため、何が入っているかが把握しやすくなります。
違う構成のサーバを立てやすい
設定ファイルを変更すれば、用途に応じたサーバが用意出来ます。

最初の項は、ネットワーク越しにディスクコピーする環境を作ればまた話は違ってきますが、今のところウノウではそうなっていません。

こちらは初期導入で大量のサーバをセットアップする場合や、用途毎にハード構成が異なる場合に有効です。

また、VMwareでテスト環境を作ったり、別のプロジェクトで新しいサーバを立ち上げるのにも便利です。

それぞれに異なる利点がありますので、用途に応じて使い分けが出来ればいいと思います。

■概要

サーバの自動インストールを行うには、以下のような行程をとります。

  1. PXEブート環境の構築
  2. kickstartファイルの作成
  3. サーバをPXEブートする

2番のkickstartファイル(自動インストール用の設定ファイル)は次回に触れるとして、今日は1番のPXEブート環境の構築方法をご紹介します。

なお、OSはCentOS5(x86_64)を使用した場合を例としますが、他のディストリビューションでも基本は同じのはずです。

■tftp-serverの構築

まず、PXEによるネットワークブート環境を構築します。PXEブートを行うには、新規にセットアップするサーバ以外に起動用のサーバが必要になります。

インストール用の環境は、既存のサーバ上に作ってもいいですし、VMwareで専用のサーバを作ってしまうのも手です。ノートPCに VMwareでPXEブート用のサーバを作っておくと、別の場所でサーバを新しく立ち上げるのに便利なので気に入っています。

PXEブートを行うには、tftpサーバとdhcpサーバ、それにインストールするOSの起動イメージが最低限必要となります。まずは以下のようにして、パッケージをインストールします。

sudo yum install tftp-server dhcp

tftp-serverはインストールしただけでは有効にならないので、/etc/xinetd.d/tftpを編集します。「disable = yes」となっている行を「disable = no」と変更します。

次に、PXEブート用のブートローダをコピーします。以下の作業は/tftpboot(tftp-server導入時に作られるディレクトリ)に書き込み権限を持った状態で行ってください。

cd /tftpboot
cp /usr/lib/syslinux/pxelinux.0 .

pxelinux.0がどこにも見当たらない場合はsyslinuxがインストールされていない可能性がありますので、その場合はインストールしてください。CentOSでは最初から入っていました。

そして、インストールするOSの起動イメージを適当なミラーサーバからダウンロードします。

mkdir centos5_64
cd centos5_64
wget http://ftp.iij.ad.jp/pub/linux/centos/5.0/os/x86_64/images/pxeboot/initrd.img
wget http://ftp.iij.ad.jp/pub/linux/centos/5.0/os/x86_64/images/pxeboot/vmlinuz

他のOSをインストールする場合は、それぞれのinitrd.imgとvmlinuzを用意してください。

後は、起動用の設定ファイルが必要になります。以下の内容で、/tftpboot/pxelinux.cfg/defaultを作成します。

default centos5_64
prompt 1
timeout 15

label centos5_64
kernel centos5_64/vmlinuz
append load initrd=centos5_64/initrd.img noipv6 devfs=nomount

以上の設定が完了したら、tftp-serverを起動します。tftp-serverはxinetd上で動作するので、以下のようにします。

sudo /etc/init.d/xinetd start

xinetdはtftp-serverをインストールする時に一緒に入るので、個別に導入する必要はありません(CentOSの場合)。

■dhcpdの構築

インストールはtftp-serverと一緒に済ませたので、こんな感じで設定ファイルを自分の環境に合わせて書き換えます。/etc/dhcpd.confをこんな感じで書きます。

ddns-update-style interim;
ignore client-updates;

subnet 192.168.10.0 netmask 255.255.255.0 {
        option routers                  192.168.10.1;
        option subnet-mask              255.255.255.0;

        option domain-name              "dev.local";
        option domain-name-servers      192.168.10.1;

        option time-offset              -18000; # Eastern Standard Time

        range dynamic-bootp 192.168.10.80 192.168.10.99;
        default-lease-time 21600;
        max-lease-time 43200;

        filename "pxelinux.0";
}

IPの設定などは環境に合わせて適宜変更してください。PXEブート用の特別な設定は、最後の「filename」の指定だけです。ここでPXEのブートローダを指定します。

設定が終わったらdhcpdを起動します。

sudp /etc/init.d/dhcpd start

これでPXEブートの環境が整いました。

なお、手元の環境では問題ありませんでしたが、ルータ側のDHCP機能を有効にしている場合、もしかしたらそちらが優先されてしまうことがあるかも知れません。その場合はルータのDHCPをoffにしてください。

■インストール対象のサーバを起動

あとは、新しいサーバに電源を入れて、ネットワークから起動するだけです。BIOSの設定や起動時の操作で、ネットワークから起動してやってください。

そうすれば、PXEブート用のサーバからIPや起動イメージを取得して、Linuxのインストーラが起動するはずです。

PXEブート時のイメージ
PXEブート時のイメージ posted by (C)フォト蔵

インストールに使用するメディアは、公開ミラーのftpやhttpから取得してもいいですし、LAN内で用意してやればより高速です。もちろんローカルの光学ドライブからインストールすることも出来ますが、それだとせっかくPXEブートにした意味がありません。

■まとめと予告

今回の内容で、ネットワークブートからインストーラを起動するところまでは完了です。この時点で、Linuxのインストールに光学ドライブが不要になりました。これだけでもそれなりに便利だとは思いますが、やはりインストールまで自動でやってしまいたいですね。

ということで、次回はこのPXEブート環境を発展させて、OSのインストールを自動化します。

なお、PXEブートの設定は今回最も基本的と思われる内容だけをご紹介しましたが、pxelinux.cfg/以下の設定によって複数の起動イメージを使い分けるなど、様々なことが出来ます。そのあたりも調べてみると面白いかも知れません。

■参考サイト

本エントリを書くに当たって、主に以下のサイトを参考にさせて頂きました。この場を借りてお礼申し上げます。

PHPで暗号化・復号あれこれ
このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

shimookaです。 皆さんはPHPでデータの暗号化・復号をする必要に迫られた場合、どのようにしているでしょうか?今回は、PHPで利用可能なモジュールやパッケージとそれらのサンプルを3つほど挙げてみました。

mcrypt拡張モジュールを使った暗号化

libmcryptを利用したPHP拡張モジュールです。DES、3DES、Blowfish、RIJNDAEL(ラインダール:AES暗号とも呼ばれる)、Blowfishなどのブロック暗号をサポートしています。利用可能な暗号モードはCBC、OFB、CFB、ECBです。 PHPで利用するには、libmcryptをインストールし、configureオプションに「--with-mcrypt」を付ける必要があります。また、PHP5以降、libmcrypt 2.5.6以降が必要です。 以下は、SSHやファイル暗号化ソフトウェアなどに広く利用されているBlowfishを使った暗号化・復号のサンプルコードです。「encrypted data」の値は、毎回変わります。

※以下のサンプルは、推奨されていない関数mcrypt_cbcを使っています。代替関数のmcrypt_generic() および mdecrypt_generic()を使ったサンプルは、追記2を参照してください。

<?php
// 暗号化するデータ
$data = '小池さんはラーメン大好き';
$base64_data = base64_encode($data);
echo "data : " . $data . "\n";

// 暗号化キー
$key = 'the key value for crypting';

/**
 * 初期化ベクトルを用意する
 * Windowsの場合、MCRYPT_DEV_URANDOMの代わりにMCRYPT_RANDを使用する
 */
$iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_CBC), MCRYPT_DEV_URANDOM);

// 暗号化処理
$encrypted_data = mcrypt_cbc(MCRYPT_BLOWFISH, $key, $base64_data, MCRYPT_ENCRYPT, $iv);
echo "encrypted data : " . base64_encode($encrypted_data)."\n";

// 復号処理
$base64_decrypted_data = mcrypt_cbc(MCRYPT_BLOWFISH, $key, $encrypted_data, MCRYPT_DECRYPT, $iv);
$decrypted_data = base64_decode($base64_decrypted_data);
echo "decrypted data : " . $decrypted_data . "\n";

echo 'validate : ' . ($data == $decrypted_data ? 'true' : 'false') . "\n";
ここでは、CBCモードを使用するため、mcrypt_cbc関数を使用していますが、その他としてmcrypt_encrypt関数/mcrypt_decrypt関数、mcrypt_generic関数が利用可能です。これらの詳細はPHPマニュアルを参照してください。 サンプルの実行結果は以下の通りです。
$ php ./mcrypt_cbc.php
data : 小池さんはラーメン大好き
encrypted data : bOiwSixcIcwn58/rP5u1RPzmy0wH3T+C7cm4HDtmNDXYiG+NYEuWokFcJg1SLmcq
decrypted data : 小池さんはラーメン大好き
validate : true
$

PEAR::Crypt_Blowfishを使った暗号化

PEARパッケージにも暗号化のパッケージが複数用意されています。先のサンプルで使用した暗号化方式BlowfishのパッケージPEAR::Crypt_Blowfishも用意されています。2007/11/05時点の最新版は1.1.0RC1です。インストールはpearコマンドを使ったいつもの手順でOKです。今回は、Crypt_Blowfish-1.1.0RC1をインストールしました。
$ sudo pear install -a Crypt_Blowfish
PEAR::Crypt_Blowfishはパッケージ内部で、後述するmcrypt拡張モジュールが利用できる場合、そちらを利用するようになっています。この場合、mcrypt_generic関数を使用することになります。 暗号化・復号のサンプルコードは以下の通りです。なお、以下のサンプルではCBCモードを指定しています(Crypt_Blowfishクラスのfactoryメソッドの第1引数)が、デフォルトのECBモードは現在では推奨されていないモードです。
<?php
require_once 'Crypt/Blowfish.php';

echo "mcrypt module is " . (extension_loaded("mcrypt") ? "" : "not ") . "loaded\n";

// 暗号化するデータ
$data = '小池さんはラーメン大好き';
echo "data : " . $data . "\n";

// 暗号化キー
$key = 'the key value for crypting';

// CBCモードで暗号化するため、初期化ベクトルを用意する
$iv = substr(md5(uniqid(rand(), 1)), 0, 8);

// 暗号化処理
$blowfish = Crypt_Blowfish::factory('cbc', $key, $iv);
$encrypted_data = $blowfish->encrypt(base64_encode($data));
echo "encrypted data : " . base64_encode($encrypted_data) . "\n";


/**
 * [BK]mcrypt拡張モジュールがロードされている場合、1つのインスタンスを
 * 使い回すことができない(mcrypt_generic_deinitしていないため)ので、
 * 再度インスタンスを取得する必要がある。イケてない。。。
 */
if (extension_loaded("mcrypt")) {
	$blowfish = Crypt_Blowfish::factory('cbc', $key, $iv);
}


// 復号処理
$decrypted = $blowfish->decrypt($encrypted_data);
if (PEAR::isError($decrypted)) {
	die($decrypted->getMessage() . "\n");
}
echo $decrypted . "\n";
$decrypted_data = base64_decode($decrypted);

echo "decrypted data : " . $decrypted_data . "\n";
echo 'validate : ' . ($data == $decrypted_data ? 'true' : 'false') . "\n";
実行結果は以下のような感じになります。「encrypted data」の値は、毎回変わります。
$ php ./crypt_blowfish.php
mcrypt module is not loaded
data : 小池さんはラーメン大好き
encrypted data : AM7KWxcRFalD8+GNy996PEXlkcGbpo2naceGi9FlIzWpKEy3DBf7ozh1TcAgVTsH
5bCP5rGg44GV44KT44Gv44Op44O844Oh44Oz5aSn5aW944GN
decrypted data : 小池さんはラーメン大好き
validate : true
$ 
また、mcrypt拡張モジュールがロードされている場合は、以下のようになります。
$ php ./crypt_blowfish.php
mcrypt module is loaded
data : 小池さんはラーメン大好き
encrypted data : 4S39zUGp3B7GVSCkyaqQP5m9oTAzLbkMIY2yHp7cUdsrNcAhZ9Gv6XIu7XFaep7R
5bCP5rGg44GV44KT44Gv44Op44O844Oh44Oz5aSn5aW944GN
decrypted data : 小池さんはラーメン大好き
validate : true
$

openssl拡張モジュールを使った暗号化

openssl拡張モジュールを使って、RSA鍵(公開鍵/秘密鍵)を使った暗号化も可能です。先のBlowfishは共通鍵(暗号化・復号に同じ鍵)を使っています。 PHPで利用するには、OpenSSLパッケージをインストールし、configureオプションに「--with-openssl」を付ける必要があります。なお、OpenSSLパッケージは最新のものを使用するようにしましょう。 さて、暗号化・復号を行う前に、以下のような手順で秘密鍵と公開鍵を作成しておきます。
$ mkdir certificates
$ cd certificates/
$ openssl genrsa -out privkey_rsa.pem
$ openssl rsa -pubout -in privkey_rsa.pem -out pubkey_rsa.pem
$ chmod 400 privkey_rsa.pem
$ 
暗号化・復号のサンプルは、local.ch official blogに掲載されているものを参考にさせていただきました。
<?php
/**
 * $ openssl genrsa -out privkey_rsa.pem
 * $ openssl rsa -pubout -in privkey_rsa.pem -out pubkey_rsa.pem
 *
 * @see http://blog.local.ch/archive/2007/10/29/openssl-php-to-java.html
 */
// 公開鍵・秘密鍵を保存したディレクトリ
define('CERT_DIR', './certificates');

// 暗号化するデータ
$data = "小池さんはラーメン大好き";
echo "data : " . $data . "\n";

// 公開鍵を読み込む
$public_key = openssl_pkey_get_public(file_get_contents(CERT_DIR . "/pubkey_rsa.pem"));

// 暗号化処理
openssl_seal($data, $encrypted_data, $env_key, array($public_key));
echo "encrypted data : " . base64_encode($encrypted_data) ."\n";
echo "env_key : " . base64_encode($env_key[0]) ."\n";

// 鍵リソースの解放
openssl_free_key($public_key);


// 秘密鍵を読み込む
$private_key = openssl_pkey_get_private(file_get_contents(CERT_DIR . "/privkey_rsa.pem"));

// 復号処理
if (!openssl_open($encrypted_data, $decrypted_data, $env_key[0], $private_key)) {
	die(openssl_error_string() . "\n");
}
// 鍵リソースの解放
openssl_free_key($private_key);

echo "decrypted data : " . $decrypted_data . "\n";

echo 'validate : ' . ($data == $decrypted_data ? 'true' : 'false') . "\n";
このサンプルの実行結果は以下の通りです。
$ php ./openssl.php
data : 小池さんはラーメン大好き
encrypted data : zvGd1yvHmkptNOlnwg66NSHjwgjSTfNkuH8q3cesaLku115J
env_key : hXVAaC6TmyGld+nAp4F2PJgTmItLZZ1geymEbcBboSyP3PGC/29HDsFCKU833LMNpdPggA3iziR7pp3Voaz9Hg==
decrypted data : 小池さんはラーメン大好き
validate : true
$

まとめ

BlowfishとRSA鍵を使った暗号化・復号のサンプルをそれぞれ挙げてみましたが、いかがでしたでしょうか?本エントリが少しでもお役に立てると幸いです。

参考URL・書籍

本エントリは以下のサイト・書籍を参考に書かせていただきました。情報を公開していただいたことに改めて感謝いたします。

追記(2007/11/06 14:00)

はてなブックマークのコメントで「復号化ではなくstrong>復号く/strong>が正しい」旨の指摘をいただきました。仰る通りです。タイトル・本文とも修正しました。 ありがとうございました。

追記2(2007/11/06 14:30)

くけーさんからコメントで「mcrypt_cbcはオンラインマニュアルに「使用すべきではありません」とある関数なので, サンプルとして挙げるのに適切ではありません.」との指摘をいただきました。大変失礼しました。仰る通りでした。。。ありがとうございました。 PHPマニュアルには、「mcrypt_generic() および mdecrypt_generic()を参照」とありますので、この2関数を使ったサンプルを以下に挙げておきます。
<?php
// 暗号化するデータ
$data = '小池さんはラーメン大好き';
$base64_data = base64_encode($data);
echo "data : " . $data . "\n";

// 暗号化キー
$key = 'the key value for crypting';

/**
 * 初期化ベクトルを用意する
 * Windowsの場合、MCRYPT_DEV_URANDOMの代わりにMCRYPT_RANDを使用する
 */
$iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_CBC), MCRYPT_DEV_URANDOM);

// 事前処理
$resource = mcrypt_module_open(MCRYPT_BLOWFISH, '',  MCRYPT_MODE_CBC, '');;

// 暗号化処理
mcrypt_generic_init($resource, $key, $iv);
$encrypted_data = mcrypt_generic($resource, $base64_data);
mcrypt_generic_deinit($resource);

echo "encrypted data : " . base64_encode($encrypted_data)."\n";


// 復号処理
mcrypt_generic_init($resource, $key, $iv);
$base64_decrypted_data = mdecrypt_generic($resource, $encrypted_data);
mcrypt_generic_deinit($resource);

$decrypted_data = base64_decode($base64_decrypted_data);
echo "decrypted data : " . $decrypted_data . "\n";

echo 'validate : ' . ($data == $decrypted_data ? 'true' : 'false') . "\n";

// モジュールを閉じる
mcrypt_module_close($resource);
出力結果は以下の通りです。
$ php crypt.php
data : 小池さんはラーメン大好き
encrypted data : ICO08auHD0ouWlCYnGXU1YsHzd4r2NEGLH+WmtijEPtyjvJlibeahccyQKs1dz95
decrypted data : 小池さんはラーメン大好き
validate : true
$

2007年11月 2日

PEAR::Pagerで生成されるリンクを並び替える
このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

bokkoです。

Webアプリケーションではページングの処理を行うことがよくありますが、実際にこの仕組みを作るには少々手間がかかるので、この処理を肩代わりしてくれるライブラリがほしいところです。


この手のライブラリは、PHPではPEAR::Pagerが有名かと思います。


簡単な例

とりあえず、使ってみましょう。(動作確認したVersionは2.4.4です)

require_once('Pager/Pager.php');
$params = array(
                'mode' => 'sliding',
                'delta' => 5,
                'perPage' => 5,
                'prevImg' => '<<',
                'nextImg' => '>>',
                'totalItems' => '1000'
                ); 
$pager =& Pager::factory($params);
echo $pager->links;

上記のphpスクリプトをブラウザで表示すると、以下のように表示されます。
pager_first.PNG

8ページ目に移動してみます。

pager_first8.PNG

表示するページ番号などもちゃんと変更されていて、前のページへのリンク(<<)もついています。とても便利です。しかし、ちょっと生成されるリンクテキストをよく見てみましょう。


[最初のページ] 前のページ n~m番目のページ 次のページ [最後のページ]


という順にならんでいます。もしかしたら、人によっては↓のように並んでいる方がわかりやすいかもしれません。

pager_next.PNG

PEAR::Pagerでは最初にインスタンスを生成する際にいろんなオプションを指定できるのですが、この並び順を入れ替えるオプションはありません。(多分)

でも、どうにかして順番を入れ替えたいので、うまい方法を探してみましょう。


PEAR::Pagerのソースを読んでみる


PEAR::Pagerのソースを読んでみると、Parger.phpのほかにCommon.php、HtmlWidgets.php、Jumping.php、Sliding.phpなどがあります。Common.phpはファイル名が抽象的なのに対して、ほかのファイル名はより具体的です。また、PEAR::Pagerはページングのためのライブラリです。よって、コアとなる処理はCommon.phpにあると考えられます。そして、そのままCommon.phpを眺めていくと、buildという関数があります。

function build()
{
    //reset
    $this->_pageData = array();
    $this->links = '';
    
    $this->_generatePageData();
    $this->_setFirstLastText();
    
    if ($this->_totalPages > (2 * $this->_delta + 1)) {
        $this->links .= $this->_printFirstPage();
    }
    
    $this->links .= $this->_getBackLink();
    $this->links .= $this->_getPageLinks();
    $this->links .= $this->_getNextLink();
    
    $this->linkTags .= $this->_getFirstLinkTag();
    $this->linkTags .= $this->_getPrevLinkTag();
    $this->linkTags .= $this->_getNextLinkTag();
    $this->linkTags .= $this->_getLastLinkTag();
    
    if ($this->_totalPages > (2 * $this->_delta + 1)) {
        $this->links .= $this->_printLastPage();
    }
}

linksという変数にgetにょろにょろLinksという関数の値をどんどん連結していってるところを見ると、これがリンクテキストを生成している部分だと想像できます。そして、_printFirstPage関数が呼ばれた後に_getBackLink関数が呼ばれていて、また、_getNextLink関数の後に_printLastPage関数が呼ばれているのが確認できます。ということはこの順序を入れ替えてやればうまくいきそうです。

書き換え後のbuild関数

function build()
{
    //reset
    $this->_pageData = array();
    $this->links = '';
    
    $this->_generatePageData();
    $this->_setFirstLastText();
    
    $this->links .= $this->_getBackLink();
    if ($this->_totalPages > (2 * $this->_delta + 1)) {
        $this->links .= $this->_printFirstPage();
    }
    
    $this->links .= $this->_getPageLinks();
    
    $this->linkTags .= $this->_getFirstLinkTag();
    $this->linkTags .= $this->_getPrevLinkTag();
    $this->linkTags .= $this->_getNextLinkTag();
    $this->linkTags .= $this->_getLastLinkTag();
    
    if ($this->_totalPages > (2 * $this->_delta + 1)) {
        $this->links .= $this->_printLastPage();
    }
    $this->links .= $this->_getNextLink();
}

これでリンクを並び替えることができます。

2007年11月 1日

FFmpegで変換した3GPP動画をNTTドコモiモーションのストリーミング再生に対応させる (for Linux)
このエントリーをブックマークに追加 このエントリーをlivedoorクリップに追加

isogawaです。

さまざまな形式の動画を携帯電話向けに変換して配信するサービスでは、変換処理をFFmpegで行っている例が多いと思われます。しかしFFmpegが出力する3GPPファイルは、NTTドコモiモーションの、ストリーミング(プログレッシブダウンロード)方式での再生はできません。QuickTime Proや携帯動画変換君で作成した動画はストリーミング再生も可能ですが、変換処理をLinux上で行っている場合には、これらを利用するわけにもいきません(できないことはないですが)。ではどうしたらいいでしょう?という小ネタ。

なにが問題なのか

+--ftyp
+--free
+--mdat
+--moov
  +--mvhd
  +--trak
  | +--tkhd
  | +--mdia
  +--trak
  | +--tkhd
  | +--mdia
  +--udta

3GPPのファイルフォーマットについては、以前の MP4/3GPP/3GPP2ファイルフォーマットの基礎知識をご覧いただくとして、FFmpegが出力する3GPPファイルのAtom/ボックス構造はこんな感じになってます(例によって入れ子は端折ってます)。

このうち、mdatボックスにはメディアデータ(動画や音声の実際のデータ)、moovボックスにはメタデータが格納されているわけですが、この構造だとメディアデータの読み込み完了後にメタデータを読み込むことになるわけで。このままでは、データを逐次読み込んで再生するプログレッシブダウンロードは無理なので、mdatとmoovの順番を入れ替えてやる必要があります。

もうひとつ。ftypボックスにはそのファイルの互換性を示す「ブランド」値が記録されてます。iモード端末は通常のダウンロード再生ではここの値を気にしないようですが、一方ストリーミング再生の場合は、ここにiモーション対応のファイルであることを示す値(mmp4)がないとダメみたいです。

てなことで、FFmpegで変換した3GPP動画をNTTドコモiモーションのストリーミング再生に対応させるには、以下のふたつの処理を行う必要があるようです。

  1. mdatをmoovの後に配置する。
  2. ftypに「mmp4」を追加する。

ではどうするか

FFmpegのGUIフロントエンドである携帯動画変換君で作成した動画がどうして携帯電話できちんと再生できるかというと、携帯動画変換君はFFmpegで(コーデックを)変換した出力を、独自開発のツールで整形しているためです。変換処理をWindows上で行っている場合は、これらのツールが利用できますが、Linuxではそういうわけにもいきません(できないことはないですが)。

ではLinuxではどうしようかということで。バイナリをゴリゴリ削ってファイルフォーマットを書き換えるプログラムを書き起こすのも一興ですが、上に挙げた処理については、オープンソースのマルチメディアフレームワークGPAC付属のコマンドラインツール、MP4Boxを使うと簡単に行えます。はい、ようやく本題です(笑)。

FFmpegの出力をMP4Boxで整形する

以下のようなコマンドで、FFmpegで「input.avi」ファイルからMPEG4/AMRな「output.3gp」ファイルを作成したとして。

ffmpeg -i input.avi -vcodec mpeg4 -b 64k -s qcif -r 15 -acodec libamr_nb -ab 12200 -ar 8000 -ac 1 -flags bitexact output.3gp

MP4Boxで「output.3gp」ファイルを読み込み「output_mod.3gp」ファイルを作成すると。

mp4box -add output.3gp -brand mmp4:1 -new output_mod.3gp
+--ftyp
+--moov
| +--mvhd
| +--trak
| | +--tkhd
| | +--mdia
| +--trak
|   +--tkhd
|   +--mdia
+--mdat
+--free

「output_mod.3gp」ファイルのAtom/ボックス構造を確認するとこんな感じで。ご覧のようにmdatがmoovの後に配置し直されています。

そしてMP4Boxの「-brand」パラメータは、ftypのメジャーブランドの値をパラメータの引数で指定した文字列に変更しています(この例では「mmp4」)。

こうして出来上がった「output_mod.3gp」ファイルは、iモード端末でストリーミング方式での再生ができるようになってると思います。

  • 手持ちの限られた機種でしか再生を確認できていませんので、再生できねーよという場合は、機種名などを添えてコメントをいただければ幸いです。
  • こんな文章をここまで読んでるようなかたには既知の筈ですが、念のため。ストリーミング方式で配信する際に記述するHTMLタグは、通常のダウンロード方式のものとは異なりますのでご注意ください。

MP4Boxの使い途あれこれ

MP4Boxは3GPPやMP4ファイルを操作するさまざまな機能が用意されており、携帯電話向けの動画を扱う際にはなにかと便利です。

ファイルを指定サイズで分割したり

例えば携帯電話向けの動画は、端末が受信可能なデータサイズの制限に応じてファイルを分割する必要が生じる場合がありますが、以下のようなコマンドで、指定のデータサイズ(以下の例では300キロバイト)以内で、動画を複数のファイルに分割してくれます。

mp4box -splits 300 large-file.3gp

ちなみにFFmpegでもファイルを分割できますが、指定のデータサイズ以内にきちんと収まらなかったり、あれこれバッドなノウハウを凝らす必要があったりするので、単純な分割であれば、MP4Boxを利用したほうがお手軽かと。

フラグメントを結合したり

あるいはFFmpegで、auの携帯電話で作成した3GPP2ファイルを変換すると、(本来の再生時間はもっと長い筈なのに)最初の15秒しか変換されない場合があったりしますが、この現象もMP4Boxを使って解消できます。

この現象は3GPP2の特徴のひとつであるムービーフラグメントに因るものですが(ムービーフラグメントについては MP4/3GPP/3GPP2ファイルフォーマットの基礎知識の、moofボックスの説明をご覧ください)。フラグメントなファイルをMP4Boxで-addとかした出力は(明示的に指定しない限り)フラグメントが結合されるので、FFmpegでもきちんと変換できるようになります。

MP4Boxについて

MP4Boxは、GNU LGPLでライセンスされるオープンソースのマルチメディアフレームワークGPACの付属ツールです。MP4Boxはコマンドラインツールですが、WindowsではYAMBなどのGUIフロントエンドも利用できます。

MP4Boxのドキュメントは以下にあります。

謝辞

この記事の元ネタ作成はharukiさんに協力いただきました。Thanksでーす。