白黒地帯

ゲームとか色々、Twitterに書ききれないことなど。

webp・apngアニメを編集する

意外と沼ったのでメモ。

以下の方針でいきます。

  • 既存のwebp・apngアニメを画像編集して再度webp・apngアニメにする
  • アルファ値(半透明)が扱えないためgifアニメにはしない
  • コマごとの可変表示フレームを扱う(例:1コマ目が0.2秒表示、2コマ目が0.5秒表示、3コマ目が0.8秒表示)
  • なるべく処理を自動化するためcliやプログラム等から行う

結論としては

  1. ImageMagickでwebp・apngアニメをコマごとに連番pngに分割
  2. webp・apngアニメの各コマ表示時間情報を後述のノートブックで取得
  3. 連番pngをペイントソフト等で編集
  4. 取得したディレイ情報をもとにImageMagickで連番pngを結合しwebpアニメを作成

が一番楽かなという感じです。

悩みとしては、今のところcliだとapngの作成がつらいことです(詳細は後述)。
特に外部制限がなければwebpアニメを作成する方が楽かと思います。

以下では実際に上記の手順でwebp・apngアニメを編集する手順を書いていきます。
なお作業はWindows環境で行っていますが、ImageMagickも後述のノートブックもMac・Linux環境で利用可能なので同様にできると思います(動作未確認)。
スマホでの作業の話はできていないのでご了承ください。

webp・apngアニメを編集してwebpアニメを作成する

ImageMagickのインストール

imagemagick.org

自分はPortable版にしてるんですがもちろんインストーラー版でもよいと思います。
動作確認version: ImageMagick 7.1.1-15 Q16 x64 a0a5f3d:20230730

v7とv6ではコマンドが異なるらしいので、既インストールのものを使う場合はv7を使ってるかは確認しておいたほうがいいかもしれません。

webp・apngアニメを連番pngに分割する

以降は編集元アニメの例として、以下のゴーストにゃんぷっぷーが移動して消えるapngアニメ、ghostreach_anime.apngを使います。

Image Credit: blobs.gg

記事冒頭の条件を試せるようにコマ画像がアルファ値を持ち、かつ1コマ目と最後の6コマ目の表示時間が間のコマより長いアニメになっています。

まず最初のコマごとの画像分割ですが、これはImageMagickのコマンド一発でできます。

magick apng:ghostreach_anime.apng -coalesce image%04d.png

コマンドを実行するとアニメを構成する6コマそれぞれの画像image0000.png~image0005.pngが出力されます。

補足:
ImageMagickではこのコマンドに限らずapngを扱うときはapng:をファイル名の前に付けないと静止画像pngとして扱ってしまいます。

また、-coalesceオプションがない場合はファイルに格納されている画像がそのまま出力されます。
例えばサイズ削減のために2コマ目以降は実際には差分画像のみを格納している場合、-coalesceオプションなしではその差分画像のみを出力します。
差分画像は基本編集向きではないと思いますので、基本-coalesceオプションありで出力しましょう(デバッグ等には使えます)。

各コマ表示時間情報を取得

各コマを分割しましたが、各コマの表示時間を把握しておかないと編集後に元に戻せません。

ここは悩んだのですが、最終的に情報取得用のColabノートブックを作成してみました。
こちらから誰でも使えます。

colab.research.google.com

使い方は簡単で、webp・apngアニメをアップロードしimage.webpまたはimage.apngにリネーム後「ランタイム」>「すべてのセルを実行」するだけです。
※以下のような警告が出ますが、認証などは必要ありません

画像のアップロードは以下画像の赤丸の部分をクリックしてフォルダを展開後画像をドラッグアンドドロップ、ランタイムメニューは赤線の部分です。
画像のリネームは右クリックからできるなど、初めての人もある程度直感的に使えるかなと思います。

実行すると色々出ますが、ページ中程に以下のように各コマの表示時間が出ます。
apngの場合単位は秒です。

なおwebpアニメの場合単位はミリ秒(1/1000秒)になります。
これは仕様の違いによるもので、それぞれの形式で正確な情報を出すようにしています。

また、ノートブックの一番下には画像を合成するためのImageMagickコマンドが出力されます。

magick -loop 0 -delay 300x1000 "image0000.png" -delay 100x1000 "image0001.png" -delay 100x1000 "image0002.png" -delay 100x1000 "image0003.png" -delay 100x1000 "image0004.png" -delay 800x1000 "image0005.png" image.webp

後で使いますので表示後もノートブックは閉じずにそのままにしておいてください。

連番pngを編集する

ここはお好みのペイントソフト等で。

自分は今のところクリスタがいいかなと思っています。

今回はゴーストと一緒に動く音符を描き足してみました。

連番pngを再度結合する

以下のようにimage0000.png~を編集し上書き保存できたら、コマの結合に入ります。

結合には「各コマ表示時間情報を取得」のところで最後に出力されたコマンドを使います。

magick -loop 0 -delay 300x1000 "image0000.png" -delay 100x1000 "image0001.png" -delay 100x1000 "image0002.png" -delay 100x1000 "image0003.png" -delay 100x1000 "image0004.png" -delay 800x1000 "image0005.png" image.webp

実行するとimage.webpという名前でコマ結合後のwebpアニメができます。

以下にこれを元apngと並べて表示してみます。

確かに表示タイミングは変えずに画像だけ編集することができました!
※Chromeで確認しています。他ブラウザでは表示が異なる場合があります

一からwebpアニメを作る場合

この場合も連番画像を作った後に上記手順を流用できます。

連番pngを結合するところのコマンドは少しわかりにくいですが、以下のようになっています。

magick -loop 0 [-delay <ms>x1000 <filename>] image.webp

<ms>が表示時間のミリ秒、<filename>がファイル名で、[]の部分はファイル数分繰り返しになっています。

まあ新規で作成する場合には自動化するところが少ないのであまり手順の恩恵はなく、直接apngかwebpで出力できたりプレビューができるソフトを使ったほうがいいかもですが…
一応、コマンドで生成できるとプレビュー機能を持たないペイントソフトでも素早く結果を生成して確認することができるという利点はあります。

補足情報・他のやり方等

以上の手順で目的は達成できるのですが、おまけの情報や、このソフトではできないの?という疑問等について以下に書いておきたいと思います。

webpとapngの表示時間保持仕様の違い

まずwebpは表示時間をミリ秒整数で保持します(以下のFrame Duration)。

developers.google.com

一方でapngはdelay_numとdelay_denという2つの整数値を保持し、delay_num/delay_den秒を表示時間とします。 wiki.mozilla.org

よって、apngの表示時間をwebpでそのまま保持できるとは限らないことに気をつけてください。
たとえば60fpsの1F=0.01666...秒はapngでは1/60として保持できますが、webpでは最も近い値でも17ミリ秒としてしか保持できません。
以下は検証用比較動画で、一番左がwebp、一番右がapngです(真ん中は今は無視してください)。
アニメは6fpsで、ページ表示から30秒程度経った状態です。
6fpsなら1Fは0.16666...秒ですので、webpでは167ミリ秒として保持されます。
小さい差ですが、表示から時間が経つとズレがある程度目立つようになるのがわかると思います。

表示時間をどのように処理するかは表示側ソフトの処理次第ですし、小さい差なので気にならない可能性は高いのですが、一応覚えておくといいのかなと思います。

他のソフトを使用する

guiなので手順には入れていないけれど使いやすそうなものや、やってみたけどうまくいかなかったものなど。

アニメ画像に変換する君でapng・webpアニメ作成

apps.microsoft.com

連番画像を入力してapng・webpアニメ、チェック用のhtmlも出力できるアプリケーションです。 fpsも好きに設定でき、1000fpsでも出力はできたので実質上限はなさそうです。
また容量最適化をオンにすればかなり出力ファイルサイズを小さくできたのでその点もよさげです。

惜しむらくは1F1枚の画像を使用するため表示フレーム数分の画像が必要な点でしょうか。
フレーム数分画像コピーするスクリプト等を用意すれば快適そうです。

アニメーションWebP作成/アニメーションPNG APNG作成

オンラインツールで広告が異常に多かったり、異様に目を引くUIで品質が低いものはアクセスしたくないので調査していないのですが、以下のサイトはその辺がまともそうだったのでao-systemという企業製のため信頼して使用できるかなと思います。

ao-system.net

ao-system.net

画像を読み込んで各コマのミリ秒で表示時間を設定できます(一括指定も可)。
この各コマ指定が嬉しいところ。個人的に可変表示時間のパラパラアニメにはfpsという概念が合わないと思っているので(?)

画像の一括読み込みができないことと、冒頭の手順のように元アニメがあって表示時間が決まっている場合には手打ちで写すことにはなりますが、出力をそのまますぐページ上で確認できる利点もありますので、最後の連番画像の結合のところはこれを使うのもありかもしれませんね。
出力もapng・webpともに表示時間の指定バッチリですし、差分画像にもちゃんとなっています。

クリスタでapng出力

成功手順中でクリスタで連番画像を編集していましたが、実はクリスタの「うごくイラスト」機能を使っていればそのままapng出力が使えます。
とはいえ成功手順の方の利点として

  • 元のアニメの表示時間を自動で復元して結合できる
  • 「うごくイラスト」では60fpsが上限

というのがあります。 60fps以上なんて使うことがあるの?とお思いかもしれませんが、例えば表示時間130msの2コマからなるアニメーションは、60fpsでは完全には表現できません。
(この表示時間は私が編集用に集めたwebpアニメに実際にありました)

それはそれとして、クリスタは新規にapngアニメを作るにはいいソフトのように思います。
以下の手順で連番画像読み込みもできますし、fpsは60上限ですがそれ以内で自由、表示フレームの追加も簡単です。

cg-squid.com

出力されるapngも愚直に連結ではなく、2コマ目以降は差分画像が使用されますし、継続フレームはdelay_num/delay_denが適切に設定されます。
(例えば、全て1/10のコマで構成されるようなことがありません)

なんだかクリスタの回し者みたいになってしまいましたが、他のソフトとの格闘を思うと連番ファイルの扱いやすさや出力仕様のまともさが嬉しいところです。
ちなみにLINEアニメーションスタンプがapngらしく容量制限もあるそうなので、その対応のために機能がブラッシュアップされているのかなと推測しています。

ImageMagickで表示時間情報を取得:△

結論としては1/100秒単位での表示になるので、webpでもapngでも正確な値を取得できません。

手順ではapng・webpのコマの表示時間の取得にノートブックを用意していました。
もしImageMagickで取得できれば全手順がIMで済むので楽そうです。

ImageMagickで画像情報を取得するにはidentifyコマンドを使います。以下のようにフォーマットに%Tを指定するとコマごとの表示時間出力になります。
例のアニメは10fpsでこの後の説明がわかりにくいため、こちらは6fpsで各コマの表示は4/1/1/1/1/4Fのapngアニメの出力です。

>identify -format "%T\n" apng:ghostreach_anime_6fps.apng
66
16
16
16
16
66

1/6秒=0.1666...ですので、出力はどうやら1/100秒単位のようです。 このままではwebpのミリ秒も、apngの分数表記も正確に取得できません。
なお、有効桁数指定-precisionを指定しても効果がありませんでした。

ImageMagickでapngをwebpに変換:△

結果としては、表示時間が1/100秒単位に変換されてしまうため、アニメの表示時間設定によってはその影響を受けます。

上の項目と同じ6fpsのアニメで、以下のコマンドでapng→webp変換をしてみます。

magick apng:ghostreach_anime_6fps.apng -coalesce im_ghostreach_anime_6fps.webp

出力されたwebpの表示時間情報は以下の通りです。

[0] 660 [ms]
[1] 160 [ms]
[2] 160 [ms]
[3] 160 [ms]
[4] 160 [ms]
[5] 660 [ms]

元の1/6秒をwebpの仕様のミリ秒で表現するなら166ミリ秒か167ミリ秒あたりになってほしいですが、1ミリ秒の位が抜けています。
前述の、1/100秒単位の表示時間情報をそのまま使用している感じがありますね。

ImageMagickでwebpをapngに変換:(ほとんど)✕

結論としては、25fpsで処理されるため元と表示時間が変わってしまう可能性が高いです。

magick image.webp -coalesce apng:im_webp_to_apng_ghostreach_anime.apng

出力されたapngの表示時間情報はこちらです。

[0] 2/5 [s] (1/25+1/25+1/25+1/25+1/25+1/25+1/25+1/25+1/25+1/25)
[1] 3/25 [s] (1/25+1/25+1/25)
[2] 3/25 [s] (1/25+1/25+1/25)
[3] 3/25 [s] (1/25+1/25+1/25)
[4] 3/25 [s] (1/25+1/25+1/25)
[5] 27/25 [s] (1/25+1/25+1/25+...

表示時間の右の(1/25+1/25+1/25+というのは、内部的には1/25 [s]のコマが集合して1コマをなしている、という意味です。
具体的には、実際に画像が表示されるコマ以降に、1x1の透明ピクセルのコマがblend_op=1で存在します。
1/25秒の画像のコマ、次に1/25秒の透明ピクセルのコマを重ねる、次に1/25秒の透明ピクセルのコマを重ねる…というデータになっているわけです。
上記の1コマ目ならそのような1/25秒のコマが合計10あって2/5秒表示される、ということです。

なにかがおかしいのは確かですが、計算してみると元のアニメとも表示時間が合っていません。
1コマ目から、0.4/0.12/0.12/0.12/0.12/1.08秒です。
繰り返しになりますが、元は0.3/0.1/0.1/0.1/0.1/0.8秒でした。

ImageMagickのソースをそこまで追えているわけではないのでおそらくなのですが、ImageMagickがapngのエンコードにffmpegを使っておりその影響と思われます。
またImageMagickからffmpegへフレームレート指定することもできないようですので、webp→apng変換は避けたほうが無難かと思われます。
動画の変換を画像編集ソフトにやらせるな、ということかもしれません。とはいえ、後述のようにffmpegでもなかなかうまくはいかないのですが…

ffmpegでwebpアニメを処理:(公式には)✕

動画ということでffmpegを何人かからオススメされたのですが、まずffmpegにはwebpのdemuxerがなくwebpアニメを処理することはできません。

trac.ffmpeg.org

webpアニメを入力すると以下の通りアニメ用チャンクがサポートされていないメッセージが出力されます。

[webp @ 000001c140f8f900] skipping unsupported chunk: ANIM
[webp @ 000001c140f8f900] skipping unsupported chunk: ANMF

なお未検証ですが、非公式にwebp demuxerパッチはあるようです。 nico-lab.net

ffmpegでapngを連番画像に分割:△

結論としては表示時間が1秒未満のコマだけで構成されているapngならばコマごとの連番画像に分割できます。 そのためのコマンドは以下です。

ffmpeg -max_fps 1 -default_fps 1 -i icon_blink.apng -r 1 ffmpeg_frames_icon_blink_%04d.png

入力apngのコマでdelay_den/delay_numが-max_fpsの指定を越えたコマは強制的にdelay_den/delay_num=-default_fps/1にされて処理されます。
-max_fpsに1を指定して全てのコマのfpsを1に揃え、同じfpsで出力側も処理します。

ただし表示時間が1秒以上のコマは1fps未満ですので、-default_fps強制の対象にならず、意図したとおりに処理できません。
また、-max_fpsに1未満の小数を指定すると0=制限なし扱いになります。
なので、「表示時間が1秒未満のコマだけで構成されている」という条件付きになります。

ffmpegで画像ファイルからapng:△

結論としては、今のところ意図した出力が得られていません。

複数枚の画像ファイルからアニメーションを作成し、かつ可変表示時間を適用するには-f concatにテキストファイルでファイルと表示時間情報を与えます。
"数値上は"例と同じapngファイルができるコマンドと指示テキストファイルの内容は以下の通りになります。

ffmpeg -f concat -i image0000.txt -vf fps=1000 -loglevel debug -plays 0 ffmpeg_concat_ghostreach_anime.apng

image0000.txt

file .\\image0000.png
option framerate 1000
inpoint 0
outpoint 0.3
file .\\image0001.png
option framerate 1000
inpoint 0
outpoint 0.1
file .\\image0002.png
option framerate 1000
inpoint 0
outpoint 0.1
file .\\image0003.png
option framerate 1000
inpoint 0
outpoint 0.1
file .\\image0004.png
option framerate 1000
inpoint 0
outpoint 0.1
file .\\image0005.png
option framerate 1000
inpoint 0
outpoint 0.799
file .\\image0005.png
option framerate 1000
inpoint 0
outpoint 0.001

これで作成された ffmpeg_concat_ghostreach_anime.apngの情報は以下の通りになります。

[0] 3/10 [s] (1/1000+1/1000+1/1000+...
[1] 1/10 [s] (1/1000+1/1000+1/1000+...
[2] 1/10 [s] (1/1000+1/1000+1/1000+...
[3] 1/10 [s] (1/1000+1/1000+1/1000+...
[4] 1/10 [s] (1/1000+1/1000+1/1000+...
[5] 4/5 [s] (1/1000+1/1000+1/1000+...

ImageMagickの項目でも出ましたが、 (1/1000+1/1000+1/1000+...は画像が実際にあるコマに続いて1x1の透明ピクセルが重なることで1コマを表しているコマです。

仕様上は確かに同じになりますが…。
実際にChromeで表示させてみると1コマの表示時間が非常に遅くなりました。
1コマ目だけでもブラウザ側で300コマの合成処理を行わなくてはいけないデータになっている影響かと思います。
また、ファイルサイズも元apngが79KBなのに対して出力apngは200KBになったので、やはり使いにくいかと思います。

なおテキストファイルのframerate指定及び出力の1000fpsはかなり大きいように見えますが、これはミリ秒を格納するwebpからの変換を意図してのことです。
入力画像の最小fpsが分かっていてそれを指定できるならば、根本的解決ではないにしろ使用できるかもしれません。
10fps程度を指定するのなら、先述の透明コマの件もあまり気にならないでしょう。

ここで指示テキストファイルに注目すると、少し変わった点があるのですがお気づきでしょうか。
image0005.pngのレコードが末尾に2回あります。
これはこちらの問題の対策です。

stackoverflow.com

指示テキストファイルの末尾のduration(またはinpoint/outpoint)が無視されてしまうため、最後のコマと同じ画像で非常に小さい表示時間のコマを追加しています。
ただしこれも曲者で、上記のテキストファイルでは1000fpsに対して-1F/+1Fとしていますが、この数値で必ずうまくいくわけではなく、1Fずれる可能性があります。
数値としては実用上問題ないといえばないのですが…

また別のコマンドですが、かなり欲しい出力に近いものも紹介します。

ffmpeg -f concat -safe 0 -i image0000.txt -loglevel debug -plays 0 ffmpeg_concat_ghostreach_anime_no_output_fps.apng

違いとしては出力の-vf fps=1000の指定がないことです。 これで出力されたapngの情報は以下のとおりです。

[0] 3/10 [s] 
[1] 1/10 [s] 
[2] 1/10 [s] 
[3] 1/10 [s] 
[4] 1/10 [s] 
[5] 7/5 [s] (7/10+7/10)

最後のコマの表示時間が0.7秒x2になっています。
ここさえなんとかなれば完璧なのですが…

他にも指示テキストファイルの末尾のduration指定をなしにしたり、時間指定をinpoint/outpointではなくdurationにもしてみたのですが どうしても意図した通りの、例apngと同じものを作成できませんでした。

ffmpegでapng→webp:△

例に使ったapngをwebpに変換してみます。

ffmpeg -i ghostreach_anime.apng -loop 0 -loglevel debug ffmpeg_apng_to_webp_image.webp

これで生成されたwebpの情報を見てみると…

[0] 300 [ms]
[1] 100 [ms]
[2] 100 [ms]
[3] 100 [ms]
[4] 100 [ms]
[5] 140 [ms]

こんな感じでなぜか最後のコマだけ表示時間が元より短いです。
実際に表示させてみても確かに短いので、前のコマとのブレンドの関係等はなさそうです。
末尾が短いのは指示テキストファイルの件と似ていますが今回は関係ないはずなので、こちらは原因不明です…

おわりに

なんかffmpegだけやたらしつこいのは、最初にwebpアニメを分割したいと言った時に3人ほどからオススメされたからですw
↓すべての始まり

misskey.io

画像編集ソフトからしか攻める気はなかったのでたしかに動画方面の発想はなかった、さぞやスッキリと解決してくれるんだろうなと思い色々いじくっていました。

ここまで色々調べたらIMやffmpegももちろんOSSですから、修正を貢献するのが筋というものかもしれませんが、つ、疲れた…
現時点のwork aroundということでひとつよろしくお願いします。

まあ正直ImageMagickもffmpegも老舗of老舗で、ここ数日でマスターできるほどちゃちいものでは全くないので見落としはありそうなのですが…