前回までのエントリ(第1回, 第2回)では、FlashLite コンテンツのサーバでの動的生成/合成処理について、開発ステップの全体的な流れや、swfmill を使用する際の SWF 構造の見方などについて紹介してきました。
今回は、前回のサンプルを使って、実際に swfmill を使用しながら SWF の動的生成をおこなう手順を紹介していきます。
前回の後半で、swfmill swf2xml で得られた XML をテンプレート化しておき、これを操作する方法として、次の2種類を紹介しました。
- XML 中の置き換えをおこなう部分を場所を一意に特定できる文字列にしておき、プログラムからは文字列置換により書き換える
- 置き換え対象部分を独自に定義した要素などに編集しておき、適当な XML 操作のための API (僕の場合は、PHP であれば SimpleXML、Ruby であれば REXML や Libxml-Ruby などを使ったりしてます) を介して書き換える
ここでは、手軽な前者の方法により、前回紹介したサンプルの背景画像の入れ替えをやってみます。背景画像は、XML 中:
182 <DefineBitsJPEG2 objectID="4"> 183 <data> 184 <data>/9n/2P/Y/+AAEEp..
の data で定義されていますので、これを下のような形に書き換えてみます。
182 <DefineBitsJPEG2 objectID="4"> 183 <data> 184 <data>####BACKGROUND_IMAGE####</data>
この XML を sample.xml として保存します。あとは、プログラムから、この XML の ####BACKGROUND_IMAGE#### の部分を文字列置換により JPEG 画像に差し替え => swfmill xml2swf とやれば、背景画像の差し替え完了となります。
せっかくの XML データですから、各種 XML API を介して操作するのが良いのかもしれませんが、システム内外からの不特定な入力の余地がないのであれば、こういった単純な文字列置換の方が、よりシンプルに実装でき、かつ高速な動作が期待できると思います。
今回は、ruby から swfmill を起動、XML の文字列置換をするためのクラスとして、下のような簡単なクラスを書いてみました。
module SwfmillUtil SWFMILL = "/Users/tmtysk/bin/swfmill" class Swfmill def self.xml2swf(xml, option = "-e cp932") IO.popen("#{SWFMILL} #{option} xml2swf stdin", "r+") do |io| io.write xml io.close_write io.read end end def self.swf2xml(swf, option = "-e cp932") IO.popen("#{SWFMILL} #{option} swf2xml stdin", "r+") do |io| io.write swf io.close_write io.read end end end end
これを使って、以下のように差し替え処理を書いてみます。背景画像は bg.jpg とします。
require '../lib/swfmill_util' xml = File.open('sample.xml').read xml.gsub!(Regexp.new('####BACKGROUND_IMAGE####'), Base64.encode64([0xff, 0xd9, 0xff, 0xd8].pack("C*") + File.open('bg.jpg').read).gsub("\n","")) File.open('foo.swf', 'w') do |f| f.write SwfmillUtil::Swfmill.xml2swf(xml) end
上記では、ファイルからオープンした jpeg データの先頭に [0xff, 0xd9, 0xff, 0xd8].pack(“C*”) の4Byteの文字列を付加していますが、これは SWF File Format Specification にも記載されている接頭子(マーカー)になります。このマーカーを忘れたり、また、Base64エンコーディングした後の改行文字を取り除くのを忘れたりすると、SWF 生成に失敗したり、SWF 生成は一見うまく行くのに再生すると画像部分が真っ赤になっていたりします。この「画像が真っ赤になる現象」は、プレイヤーが画像をレンダリングできない <= 画像データがどこかマズいという事のようで、画像の差し替えをやるとよくハマる箇所ですので、もし発生したら、上の画像エンコード周りを確認してみると良いと思います。
上のコードを実行すると、差し替え後の swf が foo.swf として保存されます。生成した swf をそのままブラウザに返すのであれば、適当な Content-Type を設定した上で SwfmillUtil::Swfmill.xml2swf(xml) の値をそのまま送信してやれば OK です。ただし、swfmill の起動コストもありますので、(程度にもよりますが)アクセスが集中するようなサイトで、同期的に SWF を生成してブラウザへ返す、というような事をやるのは、あまりオススメできません。あらかじめ SWF を作り置きしておくとか、非同期処理やバッチ処理で回避できるよう画面遷移を調整するとか、SWF 再生中のボタンアクションで非同期に loadMovie するように SWF の構成を見直すとか考える必要が出てくる事もあります。swfmill だと限界が見えてくるかもしれませんが、この他、負荷軽減のアイデアや実績があれば、コメントなどで教えていただければ幸いです。
さて、今回は、文字列置換により背景画像のみ差し替える、というやり方を紹介しましたが、同様の方法で、前回紹介したような「ムービークリップシンボルを丸ごと差し替える」というのも可能です。具体的には DefineBitsLossless2, DefineShape, DefineSprite 辺りを丸ごと文字列置換や XML 操作で置き換えてやれば OK です。この際、置き換え前後で objectID の参照関係が変わってしまわないように注意する必要があります。この辺の話も書こうと思ってたのですが、似たような話になってきたので、ここは割愛することにします。
今回使用したコードや、もうちょっと便利に使えるようにしたクラス群を github かどこかに置こうと思っています。ご興味の方、適当に遊んでみていただければと思います。
最後に、この手の事を swfmill 経由でやっていて、よく聞かれる事をまとめておきます。
Q. jpeg の部分に gif, png など他の形式の画像を置く事ってできる?
A. もっとも簡単な方法は、swfmill 外部で GD やら ImageMagick やら使って画像形式を相互変換してしまうことです。が、それだと SWF の容量制限上アレだったり、圧縮がかかって見た目上ナニだったりするので嫌、ということになると思います。そういうときは、swf2xml した後で、DefineBitsJPEG2 と DefineBitsLossless2 を置き換えてやれば OK です。jpeg 画像は DefineBitsJPEG2 で定義されていますし、それ以外の画像(gifやらpngやらbmpやら)は、すべて SWF の独自ビットマップ形式として DefineBitsLossless2 で定義されています。
DefineBitsJPEG2 では、今回の上で紹介した通り、比較的簡単な方法で JPEG データを流し込む事ができますが、DefineBitsLossless2 のデータについては SWF の独自形式ですので、既存の画像形式からの変換処理を書いてやる必要があります。この辺りの処理は、後ほど公開するクラス群に入っていたりするので、ご興味のある方はご覧ください。あまりよく確認してないですが、PHP や Perl でも実装例があったようななかったような気がしますので、そちらをお求めの方は適当に検索してみると良いと思います。
Q. 文字(テキスト)の入れ替えをやりたい?フォントを変える事もできる?
A. FlashLite1.1 の場合、テキストデータは DefineEditText(ダイナミックテキスト)要素 の initialText 属性で定義されているのがほとんどです(DefineText でパブリッシュされたのを見た事無いだけで仕様的には存在しうると思うのですが)。なので、この initialText を書き換えて xml2swf してやればテキストの入れ替えが可能です。テキストに日本語を含む場合もありますので、KLab の方が公開されている swfmill への文字エンコーディング指定パッチを適用しておくと便利です。(紹介を忘れてましたが、このパッチ、FlashLite1.1 を swfmill で扱う場合は、ほぼ必須と思います。)
フォントを変えるというのは、不可能ではありませんが、ちょっと手の込んだ事をやる必要があります。というのも、指定されうるテキストのフォント情報(グリフ)を SWF に埋め込むと、たいていの場合、それだけで FlashLite の容量制限をオーバーしてしまうからです。やるのであれば、「テキストではなく画像にしてしまう」、もしくは、「表示するテキスト情報から使用されている文字を抽出し、使用する文字の分だけグリフを埋め込む」という事をやることになるでしょう。グリフ情報はあらかじめ DB などで保持しておけば OK です。なお、フォント種類や文字にもよりますが、幾つか試してみた感じでは、日本語の漢字一文字を埋め込むと、SWF サイズが大体 300〜500Byte くらい増加するようです。
ちなみに、グリフ情報は DefineFont タグなどで定義されています。適当に1,2文字フォント埋め込みをしたSWFをパブリッシュして、構造を解析してみると良いかもしれません。
Q. ActionScript 部分を差し替えたい
A. これは、わりと僕がよくやる方法です。ActionScript のロジックは DoAction タグで定義されていますので、この子要素を SWF File Format Specification の action model 仕様を見ながら書き換えてやれば OK です。具体的には、SWF の再生フローを ActionScript 内の特定の変数値によって分岐するように書いておき、この変数値をプログラムから書き換える、といった感じです。Flashゲームや、ちょっとしたツールなど、アプリケーションライクな SWF を動的生成する際、動的な部分をある程度 SWF 側に持たせてしまうことで、生成プログラムの開発コスト/個別対応コストを下げる事も期待できます。
単に変数の置き換えをするのであれば、変数値を固定長(長さが変わってしまうと、内部で管理されているタグの長さの不一致が発生し、swf 構造が不正なものとなってしまいます)にしておけば、swf をそのまま(バイナリセーフな)文字列置換処理にかける事で、swfmill すら使わずに動的に swf を書き換える事もできます。swf の動的生成(と呼んでいいのかアレですが)としてはかなり高速な方法ですので、ゲームなど多量なアクセスが発生しうる SWF のセッション管理用にユーザ/セッション固有の ID を埋め込む、というのにも有効です。
Q. swf2xml したものを xml2swf しただけなのに、再生できなくなった
A. これはかなり稀なケースなんですが、swfmill で if / else の ActionScript コードを含む SWF を swf2xml したときの ActionScript のバイトコード変換処理に不都合があるらしく、ブロックジャンプのオフセット値が間違って出力されることがあるようです。swfmill のどの辺りかを追えてないので、根本的な解決ができてないのが申し訳ないのですが、こいつも SWF File Format Specification の action model 周りの仕様を睨みながら、出力された XML 中のオフセット値を適当に調整してやる事で、問題が解決できる場合があります。
以上、3回にわたって書いてきましたが、この手の話で、現時点で文章にできるのはこんなところです。機会があれば、今後は、こういう仕組みを使って個人的に作ったものを紹介していければ良いなあと思ってます。
何か参考になれば幸いです。
関連していそうなエントリ:
6月 1st, 2009 at 18:00
[...] インストールから、swfmillの相互変換までの要点がわかります。 ・ケータイサイトでFlashLiteコンテンツを動的生成する(その3) SWF生成→再生で「画像が真っ赤になる現象」に遭遇したら、ココを読む。 [...]