preload
5月 28


2009/06/04追記: こちらのサンプルコードは、その後のバージョンアップにより動作しないものが含まれています。大きな変更点としては「テンプレート」と言葉の用法を変更し、これまで「テンプレート」と呼んでいたものを「パーツ」と呼ぶようにしています。詳細は配布ファイルに含まれるテストコードもしくは別エントリ(そのうち書くかも)をご参照ください。

先日こちらで紹介した SwfmillRuby をバージョンアップして、いろいろ便利機能を追加してみました。機能追加以前に、SWF 内のタグの対応数が絶対的に少ないのですが、基本的に SwfmillRuby が対象にしているのは FlashLite 1.1 でパブリッシュされた SWF なので、この用途に限れば、そこそこ使えるものになってきたと思います。ライセンスに変更はありません(GPL2)。無保証です。

今回追加された機能は、大きく次の3点です。

  • ムービークリップの検出と入れ替えに対応
  • 入れ替えのための事前処理として、ムービークリップをテンプレート化しておけるようにした
  • XML Parser を rexml から libxml2 に変更

上記のほか、SwfmillUtil::Swf 初期化の方法が変更されていたり、SwfmillUtil::DefineSprite というクラスが追加されていたりといった、こまごまとした修正が入っています。が、できるだけ外側からは、xml 使って云々とか、SWF のタグがほげほげということは意識しなくても良いように作っているつもりです(といっても、まだまだうまい書き方できそうなところは多いですけど)。

では、使い方は、アーカイブ中の sample を見ていただくとして、以下、それぞれ、日本語で解説していきます。

ムービークリップの検出と入れ替えに対応

Swf#movieclips により、SWF 内のムービークリップのリストが取得できるようになりました。SWF 内部で採番されているID => SwfmillUtil::DefineSprite のハッシュの配列が返ります。

これを使って、以下のような形で片方の SWF のムービークリップを、もう片方の SWF のムービークリップと差し替える、というようなことができます。

require '../lib/swfmill_util'
require 'pp'

################################################################################
# test to replace movieclip

# initialize
swf = SwfmillUtil::Swf.parseSwf(File.open("data/sample_original.swf").read)
swf2 = SwfmillUtil::Swf.parseSwf(File.open("data/sample_original2.swf").read)

# check included movieclips (object_id => SwfmillUtil::Swf::DefineSprite)
pp swf.movieclips.keys #=> ["8", "5"]
pp swf2.movieclips.keys #=> ["6", "3"]

# check included movieclip_ids by instance_name
pp swf.movieclip_ids_named("animation") #=> ["5"]
pp swf2.movieclip_ids_named("animation") #=> ["6"]

# replace movieclip
swf.movieclips["5"] = swf2.movieclips["6"]

# write swf replaced movieclip
swf.write("data/replaced_mc.swf")

上の例では、まず、Swf#movieclips により、ふたつの SWF に含まれるムービークリップの ID 体系を確認しています。ムービークリップの ID を確認する際は、実用上は、そのうしろの行にあるような、Swf#movieclip_ids_named が便利かもしれません。これにより、ステージ上に置かれる際に付与されたインスタンス名を指定し、対象のムービークリップの ID が確認できます。

実際の入れ替えは、上のように、ハッシュを操作するような形式でおこなえます。ムービークリップは、内部で各種形状(シェイプ)や画像(ビットマップ)、テキスト情報などを参照していますが、入れ替え自体は、上のようなハッシュへの代入操作一発で、参照関係にあるリソースを丸ごと入れ替える事ができます。また、このとき、単純に上書きしてしまうと、内部で使用している ID が重複してしまうなど、ID 体系が狂ってしまうことになるので、Swf#write (のなかで呼ばれる Swf#regenerate) したタイミングで、よしなに ID の体系を調整(adjust)する処理が走るようになっています。

入れ替えのための事前処理として、ムービークリップをテンプレートパーツ化しておけるようにした

これは、FlashLite コンテンツの動的生成サイトを作ろうとした際、実用上必要になる事が多いので、ちょっと強引に作ってみた機能です。実際に、前節のようにムービークリップの入れ替えをおこなうと、内部的には SWF を XML に戻し、XML を書き換えて、XML を SWF に戻す、ということをやることになるため、Swfmill プロセスの起動コストがかさんでしまいます。また、ムービークリップの入れ替えでは、関連するリソースも一気に処理対象になるため、前節最後で書いたような、入れ替え後におこなう ID 体系の調整コストも無視できなくなってきてしまいます。

そこで、あらかじめ、入れ替え対象となる(元の) SWF と、そこに差し込むムービークリップをあらかじめ調査しておき、差し込みをおこなうムービークリップをテンプレートパーツ化しておくことを考えてみました。

以下は、対象の SWF を調査し、片方の SWF に含まれるムービークリップをテンプレートパーツとして保存しておく際のコードサンプルです。

require '../lib/swfmill_util'
require 'pp'

################################################################################
# test to templatize movieclip                                                                                                                              

# initialize
swf = SwfmillUtil::Swf.parseSwf(File.open("data/sample_original.swf").read)
swf2 = SwfmillUtil::Swf.parseSwf(File.open("data/sample_original2.swf").read)

# check included movieclips (object_id => SwfmillUtil::Swf::DefineSprite)
pp swf.movieclips.keys #=> ["8", "5"]
pp swf2.movieclips.keys #=> ["6", "3"]

# check included movieclip_ids by instance_name
pp swf.movieclip_ids_named("animation") #=> ["5"]
pp swf2.movieclip_ids_named("animation") #=> ["6"]

# templatize movieclip specifying the mapping of object_ids
#  and available, unused object_id (if you want to adjust object_ids)
File.open("data/animation_template.xml", "w") do |f|
  f.puts swf2.movieclips["6"].templatize(true, 6, 5, 1000)
end

上の通り、DefineSprite#templatize は 4 つの引数をとります。これらは手前から:

  • あらかじめ ID の調整をおこなうか(true/false)
  • 対象のムービークリップに付与されている ID
  • 対象のムービークリップを、SWF に入れ込む際の ID
  • テンプレート化の際におこなう ID の再編成時に使用できる、未使用の ID の最小値

を表しています。真ん中のふたつは、上記の前半で得られるような、SWF の調査結果をもとに指定します。最後の引数は、明らかに重複の無い大きめの値をバッファを持って指定しておくと良いと思います。なお、引数を何も指定しなければ、テンプレート化の際に ID の調整はおこないませんので、regenerate の際に調整する必要が出てきます。

なお、ここでは、テンプレート化されたムービークリップは XML 化してファイルで永続化していますが、DB に突っ込んだり、容量が許すなら memcache などのキャッシュに載せておいたりという手を使っても良いと思います。

こんなかんじで作成したテンプレートを、以下のようなコードで差し込みます。ここでは、あらかじめ Swfmill#swf2xml した xml から初期化することで、元の SWF を読み込む際に Swfmill 起動が必要ないようにしています。

require '../lib/swfmill_util'
require 'pp'

#################################################################################
# test to regenerate swf using template movieclip

# initialize original swf by preserved xml generated by Swfmill::swf2xml.
# avoid analysing swf's structure using "template_mode".
swf = SwfmillUtil::Swf.parseXml(File.open("data/sample_template.xml").read, true)

# initialize template movieclip by preserved xml generated by Swf#templatize
# avoid analysing swf's structure using "template_mode".
animation = SwfmillUtil::DefineSprite.parseXml(File.open("data/animation_template.xml").read, true)

# check a target object_id
pp swf.movieclip_ids_named("animation") #=> ["5"]

# change movieclip
swf.movieclips["5"] = animation

# write swf changed movieclip.
# avoid adjusting object_id using "adjustment=false"
swf.write("data/regenerated.swf", false)

初期化時の最後の引数は、テンプレートモード (template_mode) を表しています。テンプレートモードで Swf を初期化すると、初期化時に SWF の構成チェック処理がおこなわれません(つまり、Swf#images や Swf#movieclips で、現在の構成をダンプすることができません)。これにより、初期化の際の処理ステップを、ある程度スキップする事ができます。テンプレートモードは、templatize により、テンプレートがあらかじめ作ってある&元の SWF が調査済みで、入れ替えをおこなう相手もわかっているというときに限り、使用される事を想定しています。

また、Swf#write の最後の引数で指定している論理値は、入れ替え時の ID 調整(adjustment)要否を表しています。templatize の際に adjustment=true とし、入れ替え後の ID 調整を先におこなってあれば、ここで adjustment=false とすることで、さらに Swf 生成前のコストを削減する事ができます。

このあたり、冒頭にも書きましたが、ちょっと強引な仕上がりになってますので、ぱっと見た感じ、何が起こるのかわかりにくいのが難点です。もっと使いやすいインタフェースを考えてますが、今のところはこんな感じでご容赦ください。

XML Parser を rexml から libxml2 に変更

オモテからはほとんど意識する必要のない変更点なのですが、これにより今回追加された機能の処理部分のパフォーマンスが劇的(概ね1桁以上、場合によっては2桁)に上がりました。libxml-ruby は Ruby の標準添付ではないので、別途インストールが必要になってしまうのが懸念点だったのですが、実用上こちらの方が望ましいと思いましたので、思い切って全体を書き換えることにしたのでした。

この乗り換えのきっかけは、このライブラリは、内部では XML 処理がごりごりおこなわれているのですが、rexml を使っていたときは、とくにムービークリップ周りの解析や、ムービークリップ入れ替えの際におこなわれる XPath 検索にかなりのコストがかかっていたことがわかった、ということでした。たとえば、50KB のムービークリップが 30 件くらい入った SWF を解析するのに、デスクトップ環境で5-6分かかり、テンプレートを使用したムービークリップの入れ替えに3-4秒かかるといった具合でした。(そもそもの書き方がアレだった部分は重々ありますが。)

解析処理をおこなう際の変数スコープを調整したり、メソッド化してあったところをブロック付きの Hash に書き換えたりすることでソコソコ改善はしたのですが、結局は rexml の XPath 検索が一番のボトルネックになっていたようで、libxml2 利用に置き換えたのが一番効果的だったようです。

libxml2 の利用により、これまで2-3分かかっていた解析処理は3-4秒、入れ替え処理も0.2-0.3秒程度まで短縮できました。同時大量アクセスのあるサイトには厳しいかもしれませんが、他の面でもいろいろ工夫をいれる余地のある環境/状況でしたら、そこそこ実用に耐え得るのかな、と思います。

以上、今回の大きな変更点を紹介しました。まだまだ不具合等内在しているかもですが、ご利用いただけましたら感想など聞かせていただけるとうれしいです。

Tagged with:
5月 08

前回までの「ケータイサイトでFlashLiteコンテンツを動的生成する」エントリで紹介してきた swfmill を使った FlashLite コンテンツの動的処理に関連して、SWF に含まれる画像やテキストを操作するための簡単なクラス集を作ってみました。

swfmill_ruby – github

swfmill と同じ GPL2(the GNU GENERAL PUBLIC LICENSE Version 2) にてライセンスいたします。

swfmill_ruby は、Swfmill を ruby から起動するための簡単なクラス(SwfmillUtil::Swfmill)と、これを使って Swf を操作するためのクラス(SwfmillUtil::Swf)から構成されます。使用には、ruby の標準的な開発環境に加えて、以下のものを用意する必要があります。

その他、使用手順など、詳細は公開ファイル中の README を参照してください。

Swf の操作機能は、現時点では、もっともよく利用する:

  • 画像の入れ替え
  • テキストの入れ替え

に絞って実装しています。これを使用することで、以下のサンプルコードのように、Swf#images で Swf 中の画像データを objectID => Magick::Image のハッシュ、Swf#texts でテキストデータを objectID => String のハッシュにてアクセスする事ができます。

require '../lib/swfmill_util'
require 'pp'

# initialize
swf = SwfmillUtil::Swf.new(File.open("sample.swf").read)

# check included images (object_id => Magick::Image)
pp swf.images #=> {"6"=> JPEG 176x208 176x208+0+0 DirectClass 8-bit 10kb, "3"=>  30x30 DirectClass 16-bit}
pp swf.texts #=> {"2"=>"343201202343201204343201206343201210343201212ABCr"}

# write included images
#swf.images.each do |i,image|
#  image.write("#{i}.#{image.format ? "jpg" : "gif"}")
#end

# replace included images
swf.images['3'] = Magick::Image.from_blob(File.open("flymelongirl.gif").read).first
swf.images['6'] = Magick::Image.from_blob(File.open("bg.jpg").read).first
swf.texts['2'] = "かきくけこXYZ"

# write swf replaced images
swf.write("foo.swf")

後半で書いている通り、画像やテキストの書き換えは、ハッシュの値を置き換えてやる事で実現できます。Swf#write によりファイル出力できますし、Swf#regenerate で再生成後の swf をそのまま得る事もできます。

内部的には、Magick::Image <=> DefineBitsJPEG2/DefineBitsLossless2 の変換をおこなっています。とくに DefineBitsLossless2 の変換処理は、ちょっと面倒ですし、あまり ruby での実装を見かけないので、何らか使いどころがあればお使いください。ImageMagick / RMagick を併用するので多少重たいかもですけど。

なお、FlashLite 1.1 を対象に、よく使うあたりを中心に実装していますので、DefineBitsJPEG3 や DefineBitsLossless2 での format=5, DefineBitsLossless, DefineText などはひとまず除外しています。必要に応じて、適当に修正してみてくださいませ。

今更ながら、大胆な名付けをしてしまった気がするので、いろいろいじってみてもらえると嬉しいです!

Tagged with:
5月 08

前回までのエントリ(第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回にわたって書いてきましたが、この手の話で、現時点で文章にできるのはこんなところです。機会があれば、今後は、こういう仕組みを使って個人的に作ったものを紹介していければ良いなあと思ってます。

何か参考になれば幸いです。

Tagged with:
5月 04

前回のエントリでは、サーバで生成した FlashLite コンテンツを配信するケータイサイトの開発/制作フローとして、以下のような流れを紹介しました。

  1. SWF への入出力(input/output)に見通しを立てる (全員で)
  2. Flash IDE でプロトタイプの fla ファイルを制作し、SWF でパブリッシュ (デザイナ)
  3. SWF 中の何(What)を、どのように動的にいじりたいかを整理 (全員で) (ここまでが前の項)
  4. 動的にいじる手段を選択 (主に開発者で)
  5. 決定した手段に応じて、SWF への入出力を決定 (主に開発者とデザイナで)
  6. fla の制作ルールを決める (主に開発者とデザイナで)
  7. 上の制作ルールに基づいて Flash IDE で fla ファイルを制作し、本番用の SWF をパブリッシュ (デザイナ)
  8. 制作した SWF を必要に応じてテンプレート化する (開発者もしくはデザイナで)
  9. テンプレート化した SWF をいじるアプリケーションを実装する (開発者)
  10. できあがり。運用時の更新は、7〜8〜9の繰り返し

前回は、このフローの4まで、つまり SWF をサーバ上でどのようにいじりたいかを検討し、そのための手段(ツール)として、swfmill を選択する、というところまで紹介しました。

今回は、具体的なコンテンツを想定しながら、このフローの5以降の流れについてまとめていきたいと思います。

サンプルコンテンツの概要

動的にいじるサンプルコンテンツとして、以下のような背景画像+キャラクタのアニメーションを再生するシンプルなFlashLiteコンテンツの動的生成要件を考えてみます。(上記のフローの 3 までの検討結果イメージについても併記してみます。)

  • ステージサイズは 176×208
  • 背景画像とキャラクタのアニメーションをステージ上に持つ
  • SWF を動的に生成するときの入力(input)
    • 背景画像
    • キャラクタ画像
  • SWF 再生後の出力(output)
    • なし。ただ再生するだけ
  • SWF 中の何(what)をいじるのか
    • 背景画像とキャラクタ画像

文字だけだとどんなコンテンツなのかイメージがわきにくいので、サンプルも貼っておきます。(DeviceCentral で再生しているものをキャプチャしています。)

今回は、このコンテンツをベースに、

  • 背景の jpeg 画像を指定のものに差し替え、
  • 飛んでいるキャラクタの画像とアニメーションを指定のムービークリップシンボルに差し替える

ということをやってみます。具体的には、背景画像を以下のもの:

bg2

に差し替え、さらに、キャラクタのアニメーションを、別途作成した以下のようなムービークリップシンボル:

に差し替えたものを、新しい FlashLite コンテンツとして生成することとします。(余談ですが、FlashLite の動的生成が「FlashLite の(動的)合成」と表現されている事がありますが、その意味するところは、上記のように「ムービー中の画像素材などを指定して、これらを組み合わせて(合成して)FlashLite の生成をおこなう」ということと理解しています。)

それでは、以下、先に紹介した制作フローの続きを紹介していきます。

SWF 生成(合成) 時の入出力を決める

先に検討した入出力を精査し、何を入力パラメタとして SWF をつくり、ムービー再生後にどのような出力パラメタをもってサイト遷移に戻るか、を決定します。今回は、以下のように決定したとします。

  • 入力
    • 背景画像: ステージサイズと同じ大きさの jpeg データ
    • キャラクタ画像: アニメーションを伴ったムービークリップシンボルとして指定される(元のキャラクタ画像は GIF89a)
  • 出力
    • なし

なお、実際の要件や制作/開発の現場では、上のようにまとめられるとは限りません。「背景画像には jpeg と GIF の形式のどちらかが指定されうる」とか「キャラクタ画像は画像のビットマップのみ入力として与えるだけとし、アニメーションは固定で良い」などの要望が出る場合があります。今回は、この後のステップを紹介するのに都合が良いように要件を決めていますので、まずは以下をお読みいただいた上で、上記のような要件変更/要望の取り込みがどの辺りの開発ステップ/難易度/コストに影響を与えてきそうかを捉えていただければと思います。正直なところ、ここ以降の制作フローの詰めが、FlashLite コンテンツの動的生成をおこなう際の一番の難所(業務でやる場合は、リスク要因となりやすいフェーズ)と、個人的には感じています。

.flaの制作ルールを決める

SWF 中の差し替えをおこなう要素と、そのときに与えられる入力が決まったところで、デザイナと開発者との間でベースとなる SWF の制作ルールを決めていきます。なぜ、制作ルールを決める必要があるかと言うと、一番の理由は、プログラムから差し替えをおこなう要素が特定できるようになっていないことには、画一的な処理で要素の差し替え(SWF の動的生成)をすることができず、ベースとなる SWF ごとに処理をつくってやらなければならなくなるからです。

これは逆を言えば、ベースとなる SWF が少なく、運用時の SWF 追加や更新も無い、などの理由で、個別の動的生成処理を実装していくのにコストがかからないのであれば、この時点で厳密な制作ルールの握りをつくる必要は無い、ということにもなります。

今回は、この後の説明を簡単にする都合で、以下のように制作ルールを決める事ができた、と仮定します。

  • 背景画像は、jpeg を貼付けただけのムービークリップシンボルを別途作成しておき、これをステージに配置する際に “bg” というインスタンス名を付与しておく事にする
  • キャラクタ画像は、アニメーションを含める形でムービークリップシンボルを別途作成しておき、これをステージに配置する際に “animation” というインスタンス名を付与しておく事にする

上記のように、今回は、差し替えをおこなう要素をふたつともムービークリップシンボル化しておくことを仮定しています。

ただ設置するだけの背景画像を、わざわざシンボル化して、インスタンス名まで付与するのは、制作側からすれば一見非効率かも知れません。ただし、これらのルールは、先にも述べた通り、プログラム側から差し替えをおこなう要素(ムービークリップ)を特定/識別するためのルールですので、ケースバイケースと考えていただければよろしいかと思います。たとえば、「SWF 中には、背景画像として唯一 jpeg 要素を使用する」という制作ルールがあれば、「背景画像として入力した jpeg を SWF 中の jpeg データと置き換える」という処理を作ってやれば、わざわざインスタンス名で特定する必要がないということにもなります。

本番用 SWF をパブリッシュし、必要に応じてテンプレート化し、差し替え処理を実装する

上記のルールに基づいて .fla を制作し、SWF をパブリッシュしたら、いよいよ、具体的な FlashLite 動的生成処理の実装に入っていきます。

まずは swfmill swf2xml を使って、できあがったベースの SWF の構造を確認してみます。(swfmill の基本的な使い方は、他のサイトでもわかりやすい説明が多いので、ここでは割愛します。)今回のサンプル SWF は以下のようになっていたとします。

  1 <?xml version="1.0" encoding="UTF-8"?>
  2 <swf version="4" compressed="0">
:
 13       <DefineBitsLossless2 objectID="1" format="3" width="30" height="30" n_colormap="7">
 14         <data>
 15           <data>eNq9kEsO...
:
 17       </DefineBitsLossless2>
 18       <DefineShape objectID="2">
:
 30               <ClippedBitmap objectID="1">
 31                 <matrix>
 32                   <Transform scaleX="20.00000000000000" scaleY="20.00000000000000" transX="-300" transY="-300"/>
 33                 </matrix>
 34               </ClippedBitmap>
:
 52       <DefineSprite objectID="3" frames="20">
 53         <tags>
 54           <PlaceObject2 replace="0" depth="1" objectID="2">
 55             <transform>
 56               <Transform transX="299" transY="299"/>
 57             </transform>
 58           </PlaceObject2>
 59           <ShowFrame/>
:
176       </DefineSprite>
177       <PlaceObject2 replace="0" depth="3" objectID="3" name="animation">
178         <transform>
179           <Transform transX="3541" transY="1031"/>
180         </transform>
181       </PlaceObject2>
:
182       <DefineBitsJPEG2 objectID="4">
183         <data>
184           <data>/9n/2P/Y/+AAEEp..
:
186       </DefineBitsJPEG2>
187       <DefineShape objectID="5">
188         <bounds>
189           <Rectangle left="0" right="3520" top="0" bottom="4160"/>
190         </bounds>
191         <styles>
192           <StyleList>
193             <fillStyles>
194               <ClippedBitmap objectID="4">
195                 <matrix>
196                   <Transform scaleX="20.00000000000000" scaleY="20.00000000000000" transX="0" transY="0"/>
197                 </matrix>
198               </ClippedBitmap>
:
216       <DefineSprite objectID="6" frames="1">
217         <tags>
218           <PlaceObject2 replace="0" depth="1" objectID="5">
219             <transform>
220               <Transform transX="0" transY="0"/>
221             </transform>
222           </PlaceObject2>
223           <ShowFrame/>
224           <End/>
225         </tags>
226       </DefineSprite>
227       <PlaceObject2 replace="0" depth="1" objectID="6" name="bg">

swfmill を使った FlashLite の動的生成処理を作る際は、(場合にもよりますが、)

  • ベースの SWF をそのまま保持しておき、動的に swf2xml => 差し替え処理 => xml2swf するよりは、
  • 上記のように XML 化したものを要素を差し替え可能な状態に適宜編集して保持しておき、動的に 差し替え処理 => xml2swf  したほうが、

応答速度的にも効率が良くなると思います。ですので、ここでベースの SWF の構造を XML 化しておき、差し替えをおこなう要素の構造を理解しておくとともに、この XML を差し替え可能な状態に編集し、テンプレートとして保存しておく事にします。

今回の要件では、ステージにムービークリップを配置した際のインスタンス名を “bg” または “animation” で指定してもらっているはずですので、まずはこれらがどこに位置しているかを見ていきます。FlashLite 1.1 でパブリッシュされた SWF では、ムービークリップは PlaceObject2 要素(Tag)で name 属性をともなって配置されているはずです。(このあたりの SWF の構造に関しては、Adobe 社が公開している SWF の仕様 – SWF Technology Center (必要に応じて Open Screen Project )も参考にしてください。)上記の場合では、

177       <PlaceObject2 replace="0" depth="3" objectID="3" name="animation">

の行と、

227       <PlaceObject2 replace="0" depth="1" objectID="6" name="bg">

が該当する事になります。

ここから、さらに PlaceObject2 が参照している object を辿っていきます。objectID 属性を確認すると、たとえば animation インスタンスは objectID=”3″ ですので、objectID=”3″ となっているムービークリップ要素 = DefineSprite 要素を探します。今回の例ですと:

 52       <DefineSprite objectID="3" frames="20">
 53         <tags>
 54           <PlaceObject2 replace="0" depth="1" objectID="2">
 55             <transform>

ここが該当します。このなかで、さらに PlaceObject2 により objectID=”2″ が参照されていますが、ここではムービークリップ内に設置されたキャラクタ画像が参照されているはずです。FlashLite 1.1 でムービークリップ内に画像を定義する方法は何種類かありますが、基本的には、

  • DefineBitsLossless2 もしくは DefineBitsJPEG2 で定義した画像データを、
  • //DefineShape/styles/fillStyles/ClippedBitmap で参照し、このシェイプを PlaceObject2 で配置する

というのが一般的な構造になるようです。今回は、上で指定されていた objectID=”2″ な DefineShape を辿って、さらにそのなかの ClippedBitmap というように、objectID をたどっていきます。最終的には、

 13       <DefineBitsLossless2 objectID="1" format="3" width="30" height="30" n_colormap="7">
 14         <data>
 15           <data>eNq9kEsO...

に行き着きます。ここの data 要素の中身が、アニメーションをおこなっているキャラクタ画像のビットマップデータということになります。今回はアニメーションとキャラクタ画像を丸ごと差し替えたいという要件ですので、このあたりの DefineBitsLossless2, DefineShape, DefineSprite の構造を、objectID の参照関係を維持したまま作り替えてやれば、アニメーション部分の差し替えができるということになります。

同様に、背景画像については上記 227 行目の PlaceObject2 で参照されている objectID=”6″ を追っていってやれば、

182       <DefineBitsJPEG2 objectID="4">
183         <data>
184           <data>/9n/2P/Y/+AAEEp..

に辿り着きますので、この data 要素を差し替えてやれば良い、ということになります。

対象の SWF の構造がわかったところで、この XML をテンプレート化します。差し替え処理の実装方法は幾つか考えられますが、

  • XML 中の置き換えをおこなう部分を場所を一意に特定できる文字列にしておき、プログラムからは文字列置換により書き換える
  • 置き換え対象部分を独自に定義した要素などに編集しておき、適当な XML 操作のための API (僕の場合は、PHP であれば SimpleXML、Ruby であれば REXML や Libxml-Ruby などを使ったりしてます) を介して書き換える

などの方法があります。前者は単純な文字列置換なので、対象の SWF がシンプルであれば非常に簡単に実装できますが、その分、.fla の制作ルールや実行環境によってはバグやセキュリティホールを作り込んでしまう可能性もあるので、注意した方が良いかもしれません。

ちょっと長くなってきたので、実際の入れ替え処理を書いていくところは、次回以降にしたいと思います。

まとめ

以上、2回にわたってケータイサイトで FlashLite コンテンツを動的生成する方法の概要をまとめてきました。全体の流れが見えると、ベースとなる SWF の制作ルールと、システム開発コストがどのように関連してきそうか、が、なんとなく見えてくるんじゃないかな、と思います。

実際のところ、制作ルールの策定は、動的生成処理の開発コストを、システム開発側とSWF制作側とで分担する割合/バランスを探る作業であるようにも感じています。ですので、全体の流れや仕組みの見通しが立たない事には、実サービスにこういった仕組みを入れていくのは大変かもしれません。

FlashLite 動的生成のシステムには何回か関わらせていただいていますが、正直なところ、こういった流れ、とくに制作側にいろんな制作ルールをお願いするようなやり方が本当に良いのかどうかは見え切れていません。今でも、わりと試行錯誤しています。この手のシステムに関わった方と情報交換する機会がほとんど無いので、いろんな方のご意見をうかがってみたいです。

さて、次回以降は、今回紹介したサンプルコンテンツの動的生成の続きから、実際に FlashLite 動的生成をやっていて使用したコードや、ハマったところなどが紹介できればと思います。

長々と読んでいただいてありがとうございました。期待せずにお待ちください。

Tagged with:
4月 16

以前、ケータイ Flash を中心に、細々と SWF バイナリを読んでいた時期があったのですが、そんなこんななご縁で、FlashLite コンテンツの動的生成(FlashIDEを介さずに、Webアプリケーションサーバ側で、SWF を自動作成する)方法について聞かれることが多いです。なんか、最近になってやけに多くなった気がするので、ちょっと理由を考えてみたのですが、

  • 大半のユーザが FlashLite 対応機種を持つようになった
    • アバター系(キャラとか部屋とか)着せ替え提供サイトが、より高精細(キレイでなめらか)なアニメーション素材を提供できるようになった
    • ケータイでFlashゲームをやる、という文化/リテラシが浸透してきた
  • FlashLite コンテンツは、通信制限や、1URLあたりのファイルサイズ制限などのケータイ特有の制限により、FlashLite 単独で動的なコンテンツにしづらい (ActionScript により、動的な処理をすべて SWF に入れ込む、というのが現実的でなく、Webアプリと絡めて都度 SWF を作り替える必要がある)
    • しかも、たいていの場合、FlashLite バージョンは下位機種に合わせて 1.1 にする必要があって、上記の制限がなかなか緩和されない

あたりの事が関係しているような気がします(もっとも、これ以外にも複数の理由が絡んでいるでしょうけど)。

もちろん、これ系のネタは検索すればいっぱい出てくるのですが、最近やけに聞かれるのは、新旧織り交ぜた情報が出てくるので、なかなか落としどころがわかりにくい、ということも関係しているような気がします。ということで、2009年4月現時点の情報として、僕の説明の省力化と知ってる事の整理も兼ねて、この件について、これから何回かに分けてまとめていこうと思います。

話題にするのは、たぶん、以下のような内容になります。

  • FlashLite コンテンツを動的に作成するケータイサイトの開発ステップ
    • 選択できる FlashLite の生成方法について
  • swfmill と適当な XML 処理系を組み合わせた方法による FlashLite コンテンツ動的生成
    • 画像を置き換えたつもりが、画像部分だけ真っ赤になった
    • jpegの部分にpngとかgifの画像を入れたい
    • swfmillでswf2xmlしたのを、そのままxml2swfしただけなのに再生できなくなった
    • 文字の入れ替えもやりたい。デバイスフォント以外も使える?
    • ActionScript 部分を書き換えたい

後半の swfmill を使う方法は、基本的なやり方はわりと Web で目にするので、上のような実際にサービスに入れ込んでいくときのハマりどころ/課題について、具体的に書けるといいなあ、と。

ただし、改めて書くまでもないことですが、あくまで僕の見解ですので、ご参考程度としていただければと思います。むしろ、実際どういう事やってるのかって聞く機会が少ないので、教えて欲しかったりします。あと、途中で書くのに飽きたらやめるかもしれません。すみませんすみません。

では、今回は、上記の前半部分、まずは FlashLite コンテンツを動的に作成するケータイサイトの開発ステップについて書いてみます。なお、ここで話題にするのは、「ケータイサイトの作り方」ではなく、「ケータイサイトのなかでも、FlashLite コンテンツを扱う部分の作り方」だけですので、ご期待に添えられなかったらすみません。

動的生成する範囲を見積もる

まずはじめに、サイト(サービス)のどの部分(Where)を FlashLite にするのかを見積もります。

画面遷移の資料やら、あとは、サイト全容の妄想やらをベースに、どこを FlashLite コンテンツにしたいのかを整理します。

(すくなくとも現時点では、)サイト全体が FlashLite コンテンツになる、ということは稀だとおもいます。たぶん、多くの場合、

  • スプラッシュページ
  • トップページの(階層)メニュー
  • ユーザ別にカスタマイズされたコンテンツ(アバターとか)
  • ゲーム

のいずれかに収まるんじゃないかとおもいます。

FlashLite コンテンツへの入出力と、そのなかで動的に取り扱いたい部分を確認する

以下、FlashLite コンテンツを SWF と表記します。

サイト内に SWF を入れ込む範囲が見えたら、具体的に、その SWF のなかの何(What)を動的に扱いたいのかを確認します。もうちょっと細かくいうと、

  • SWF が再生される際、どういう入力(input)をもらって、
  • その入力の結果、SWF のなかの何(What)を書き換えて、
  • SWF を操作(再生)した結果、どういう出力(output)を持って違う画面に遷移するのか

を整理する事になるのですが、ここでは、少なくとも2番目のWhatを押さえておきたいです。具体的には、以下のようなものが挙がるとおもいます。

  • 画像
  • 文字列
  • アニメーション
  • その他 ( ActionScript 内のロジックとか変数とか)

ここで挙がったものの種類/組み合わせと分量、それから、この後で検討する制作フロー云々が、動的生成の難易度に大きくかかわってきます。

制作フローを確認する

サイト内でFlashLiteを入れ込む範囲が見えたら、そのコンテンツ(SWF)の制作フローを確認します。サイトの運用(コンテンツ更新)まで見据えて、「誰が(Who)」「いつ(When)、どれくらいの頻度で(How often)」制作するかを確認し、見通しを立てます。

サービス開発時に、企画、デザイナ(というのは抵抗があるんですが)、開発者という3役割があるとすれば、恐らく、多くの場合において、SWF を制作するのはデザイナであると思います。たぶん、以下のようなフローになるような気がします。(ここでは、前の項の「input/output/What」の検討を含めたフローを書きます。)

  1. SWF への入出力(input/output)に見通しを立てる (全員で)
  2. Flash IDE でプロトタイプの fla ファイルを制作し、SWF でパブリッシュ (デザイナ)
  3. SWF 中の何(What)を、どのように動的にいじりたいかを整理 (全員で) (ここまでが前の項)
  4. 動的にいじる手段を選択 (主に開発者で)
  5. 決定した手段に応じて、SWF への入出力を決定 (主に開発者とデザイナで)
  6. fla の制作ルールを決める (主に開発者とデザイナで)
  7. 上の制作ルールに基づいて Flash IDE で fla ファイルを制作し、本番用の SWF をパブリッシュ (デザイナ)
  8. 制作した SWF を必要に応じてテンプレート化する (開発者もしくはデザイナで)
  9. テンプレート化した SWF をいじるアプリケーションを実装する (開発者)
  10. できあがり。運用時の更新は、7〜8〜9の繰り返し

ここでは、たとえば「毎週火曜日に」「毎月初に」などの SWF の定常更新があると想定し、更新フローまで考えてみましたが、サイトの性質によっては、サービス開始時にひとつ SWF のテンプレートをつくってしまえば、SWF の更新が必要ない場合もあるかもしれません。

ここでのポイントは、4 の「動的にいじる手段を選択」する部分と、それ以降のフローを作成する部分にあると思います。上記の全体のフローをなんとなく踏まえて、次の項でこれを検討してみます。

SWFを動的にいじる手段を選択する

ここでは、これまでの検討材料を受けて、実際に SWF をどのように(How)動的にいじっていくかを検討していきます。まずは、比較的大きな意思決定として、

  • 有償の SWF 動的生成ソフトウェアあるいは ASP を購入/契約し、導入する
  • OSS を併用するなどしながら、自社開発する

のどちらかを選ぶ事になると思います。これの判断材料を整理するのはむずかしいのですが、僕の感覚的には、次のいずれかに当てはまるようであれば、有償のものを検討した方が良いような気がします。

  • ここまでの項の検討が思うように進まない、あるいは、わからない
  • ここまでの項で検討した結果、ぴったり要件に当てはまるプロダクト/サービスが既にあって、自社でつくるよりもどう考えても安く、さらに、自社戦略的にその仕組みを自社でつくっておく必要もなさそう
  • 組織や開発体制の都合で、前項で検討したような制作フローの後半 = SWF の制作ルールやフローの検討に各担当者を巻き込んでいくのが難しい

恐らく、ここのブログを読む人は、「FlashLite 動的生成」などのキーワードで来られてるのではないかな、と思いますが、有償のプロダクト/サービスを検討する際は、検索ワードに「エンジン」あるいは「ASP」と追加してみると、プロダクトっぽいものが引っかかりやすいようです。ここで、特定の有償プロダクトを紹介する事はしませんが、最近はそこそこの月額コストで簡単に利用できるものもありますし、ASP やソフトウェアパッケージでありながらもコンサルティングサポートや簡単なカスタマイズに対応してくれる場合もあるようです。

いっぽう、有償のプロダクト/サービスを導入した場合は、以下のようなリスク/デメリットもあります。

  • サービスがトラブったときに、手を出しにくい (とくにブラックボックスの場合)
  • 比較的自由度が低いため、サービス仕様に対応できない場合や、運用開始後の仕様変更/サービス追加に対応できない場合がある

これらのリスク/デメリットや前項までに検討した input/output/What/When/How often を鑑みた結果、OSS を併用しながら自社開発する、という選択もあると思います。また、先の What を検討した際、SWF のいじくりたい部分が多様で複雑になった場合は、有償プロダクトで対応できない、いずれのプロダクトにも要件を満たせるものが無い、ということにもなったかもしれません。その場合は、大きく分けて、次の選択肢から選ぶ事になるような気がします。

  1. SWF バイナリ構造を理解し、スクラッチで所期の SWF いじくり処理を実装する
  2. ming を使って、プログラムから SWF を作成する
  3. flasm を使ったプログラムを実装し、ActionScript 部分だけ書き換える
  4. swfmill を使ったプログラムを実装し、SWF の中身を置き換える

ここでは、1 は長くなりそうなので説明しません。やってみると面白いんですけどね。ただし、ちょっとした工夫で、手間はほとんどかけずに所期の動的生成の要件を達成する事ができるケースもあります。これは、機会があれば書きます。

2 についてですが、ming って、プログラムから SWF を作るには便利なんですけど、既にある SWF をいじくるのにはあんまし向いてないように思います。(ming 派の方がいらっしゃったら教えて欲しい点でもありますが。)

3 は、わりとやってる人多いみたいな気がします。が、僕は SWF の画像をごりごりいじるところから入った人なので、あまり詳しくありません。

ということで、ActionScript もがんばれば書き換えできるし、かつ、SWF 中の任意の要素をいじくることができる、4 の swfmill を使う方法について、今後書いていこうと思います。

ちなみに、ActionScript を書き換える場合は mtasc という選択肢もあるのですが、mtasc は FlashLite1.1 コンテンツに対応していませんので、ここでは除外しています。あと、swfmill でダンプされた ActionScript コードは若干操作しにくいですので、ActionScript は flasm、それ以外は swfmill という合わせ技もアリかもしれません。

ちなみに、お気づきかもしれませんが、ここでは、「FlashLiteコンテンツを動的に生成したい」というニーズを、「既にあるSWFに含まれる何かを入れ替えてユーザ端末に送信したい」というニーズに置き換えて話を進めてきました。これも僕の感覚なので、全然違うかもなんですけど、多くの場合、「FlashLite 動的生成」を探してる人は、こういうニーズであるような気がします。

では、次回は、swfmill を使った SWF の動的生成と、これをケータイサイトのシステムに組み込んでいく場合の流れ(前項の 5 以降のステップ)について、書いていければと思います。期待せずにお待ちくださいませ (__)

Tagged with: