Flash Lite の中身を動的に変更する

swfmill というツールの存在を知った。swf ファイルと XML ファイルを相互に変換するという。Flash Lite 1.1 の中身を動的に更新したいという声が社内で上がっていた(何年周回遅れで??)ので、それを試しに使ってみる事にした。

サンプルで使う Flash Lite 1.1 のファイルは以下のようなものを用意した。
手抜きだが必要充分だろう。

This movie requires Flash Player 4.0.0
QRコード
http://dev.uniba.jp/~rei/swfmill/test.swf

swfmill の利用実績は豊富にある。
まずは、Flash4 の仕様に準拠するために文字コードが UTF-8 でない Flash Lite 1.1 に対応するために下記ページで配布されているパッチを当てる必要があるらしい。


インストール方法も上記ページ内の解説に準じたが、今回はシステムにインストールする必要はなかったので、make まで止めておいて、出来たバイナリを動的生成用に作った CGI と同じディレクトリに置いておく。

% ./configure
% make
% cp src/swfmill [CGIの置かれているPath]

CGI は個人的な趣味で Ruby で作る事にした。が、今回使う CentOS 4 の社内サーバは Ruby 1.8.1 なので、こっそりアップデートする事にした。おそらく社内で Ruby 使ってるのはまだ俺だけだから多分大丈夫。とはいえ、いつでも戻せるように、RPM パッケージの形で入れたいから checkinstall を使う

# yum remove ruby-libs
# wget ftp://ftp.ruby-lang.org/pub/ruby/1.8/ruby-1.8.7-p248.tar.gz
# tar xvzf ruby-1.8.7-p248.tar.gz
# cd ruby-1.8.7
# ./configure –prefix=/usr
# make
# checkinstall –fstrans=no
# rpm -ivh /usr/src/redhat/RPMS/i386/ruby-1.8.7-p248-1.i386.rpm

なぜ Ruby を新しくしたかというと、swfmill の吐き出す XML をパースするのに Nokogiri を使いたかったのだが、RubyGems を使うには、Ruby 1.8.1 では少々古過ぎるらしいから。

なのでそのまま RubyGems も入れる

# wget http://rubyforge.org/frs/download.php/60718/rubygems-1.3.5.tgz
# tar xvzf rubygems-1.3.5.tgz
# cd rubygems-1.3.5
# checkinstall -R “ruby setup.rb”
# rpm -ivh /usr/src/redhat/RPMS/i386/rubygems-1.3.5-1.i386.rpm

Nokogiri も。最初は標準の REXML を使っていたがちょっと馴染まなかった。こっちの方がやりやすい。

# gem sources -a http://gems.github.com
# gem install Nokogiri

これを用いた CGI のソースは以下の通り。

#!/usr/bin/ruby

# ↓この行は古い libxml2 を使っている時に Nokogiri のエラー表示を抑制する指定
I_KNOW_I_AM_USING_AN_OLD_AND_BUGGY_VERSION_OF_LIBXML2 = true

require "base64"
require "rubygems"
require "nokogiri"
require "open3"

# 置換える内容
ans = [1, 2, 1]
image_suffix = 3

# 必要なファイルの Path
swf_name = "test.swf"
tmp_path = "/tmp/temp_xml_for_swfmill.xml"

# ---------

# 元 SWF を XML に変換して読み込む
data = IO.popen("./swfmill -e cp932 swf2xml #{swf_name} stdout", 'r+') { |io|
io.read
}
doc = Nokogiri::XML.parse(data)

# クイズの正解を置換え
pre = ""
i = 0
doc.root.search('/swf/Header/tags/DoAction[1]/actions/PushData/items/StackString').each do |nd|
if pre.match(/^a[0-9]+$/) then
nd["value"] = ans[i].to_s
i += 1
end
pre = nd["value"]
end

# 表示される文言を置換え
doc.root.search('/swf/Header/tags/DefineEditText[1]')[0]["initialText"] = "クイズ\rこの人男!?女!?\r"
doc.root.search('/swf/Header/tags/DefineEditText[@objectID="23"]')[0]["initialText"] = "おしまいです\r"

# 画像を置換え
i = image_suffix
doc.root.search('/swf/Header/tags/DefineBitsJPEG2/data/data').each do |image|
image.content = Base64::encode64("\xFF\xD9\xFF\xD8" + File.open("elem#{i}.jpg").read).gsub("\n", "")
i += 1
end

# XML をテンポラリファイルに出力
File.open(tmp_path, "w+") { |tpf|
doc.write_to(tpf)
tpf.close
}

# swf を出力
print "Content-type: application/x-shockwave-flash\n\n"
print IO.popen("./swfmill -e cp932 xml2swf #{tmp_path} stdout", 'r+') { |io|
io.read
}

ホントはテンポラリファイルを使わずにやろうと思ったが、popen した時に余り長い入出力を行なうと怒られてしまう?ようでダメだった。ここは本筋じゃないので、今回余り深追いはしていない。

上記の CGI を呼ぶと、以下のような SWF ファイルを得られる

This movie requires Flash Player 4.0.0
QRコード
http://dev.uniba.jp/~rei/swfmill/test.cgi

試しに使ってみる、というところまでなのでこの辺りで充分だろう。
後は、注意点と参考リンクのメモ書き

preload preload preload