概要

gotosocial に HEVC な動画がアップロードできるように、gotosocial を fork して変更を加えました。 このコミットでは、gotosocialが HEVC 動画を処理しようとして失敗する現象を修正してあります。処理の内容は動画のメタ情報とデータからサムネイルを作ることですが、現時点でサムネイルはダミーのブランク画像なので、そもそも動画のメタ情報すらパースする必要がありません。したがって、(念のため) HEVC のときのみ、動画のメタ情報に依存しない固定のダミー画像を生成するようにしました。

以下日記です。

背景

Gotosocial は ActivityPub を実装した SNS サーバの一つで、有名な Mastodon の alternative の一つであるというとわかりやすい。 Gotosocial の特徴はその軽量さで、Golang で記述されいるため他のスクリプト言語よりも高速に動作する。 例えば Mastodon は Ruby で書かれているし、[[Misskey|https://github.com/misskey-dev/misskey]] は TypeScript で記述されている (i.e. node ランタイム上で動作する)。 このようなスクリプト言語と比べれば、gotosocial が一部機能を実装していないことを鑑みても、実行速度や RAM 消費量は小さいことは納得できる。 また、golang は static link が基本なので、環境構築が楽なことも利点に挙げられる。ビルドも簡単だ。 RAM 1GB の VPS で動作する Fediverse のお一人様サーバとしては Mastodon より Gotosocial のほうが適していると考え、このごろはこれを使用している。

最近 (2024年1月25日) 、歴史ある格闘ゲームの鉄拳 8 が発売された。 これが異常に面白くて、また自分のプレイを見て復習し対戦相手への対策を考えるのも楽しい。 よく gotosocial にプレイ動画を上げて、自分で復習したり、フォロワーに見てもらったりしている。 あわよくば、ストリートファイト 6 のように Youtube や Twitch の有名ストリーマーに取り上げてもらって、めちゃめちゃ流行ってほしいと思ってその手助けにもなればと思っている (フォロワー 15 人)。

楽しく鉄拳を遊ぶ rollman054 の様子

目的

現状で、私の Gotosocial の attachment size 制限は 40MB にしてある。 一方、鉄拳 8 のプレイ動画は、1 ゲームあたり長くて 5 分 (1 ラウンド 1 分で 3 本先取) かかり、この動画を 40 MB に収まるように H.264 でエンコードするとかなり画質が落ちる。

H.264 で 40 MB になるようエンコードされた 2 分 16 秒の動画

attachment size 制限を緩和することも考えたが、attachment は私自身のの AWS S3 に保存される。 闇雲に緩和して、調子に乗って動画をアップロードしていると、請求が高額になることも考えられる。 今回は、最近流行りの HEVC (High Efficiency Video Codec, H.265 とも) でエンコードして、40 MB でもそこそこ見られる画質になるようにすることにした。

ここで標題の問題が現れる。 最新版の Gotosocial v0.13.1 に HEVC でエンコードされた動画をアップロードすると、アップロードはつつがなく完了するものの、 その後クライアントアプリケーションから、サーバーが 422 Unprocesable Entity を返したことが通知される。 今回はこれを修正し、Gotosocial に HEVC でエンコードされた動画をアップロードできるようにする。

手法

問題は、Gotosocial が動画のコーデックメタデータとデータからサムネイルを作成しようとして失敗しているところにある。 サムネイルの構築の手順は大抵以下の通りだろう: まずデータをデコードするための必要なメタデータを取得し、それに従ってデータをパースし 1 フレームだけデコードする。 gotosocial はこの手順のうち、メタデータを取得する部分をサードパーティライブラリの abema/go-mp4 で処理している。 ここで、メタデータが取得できずに失敗し、エラーとして動画のアップロード受付処理を停止しています。

go-mp4 が HEVC に対応していない場合、またはバグで対応できていない場合、その解決は多少難しい。 というのも、動画コーデックやコンテナのあたりはライセンスがしっかりしており、仕様書を読むだけで多額の金銭を要求されがちだからだ。 今回も御多分に漏れず、HEVC の仕様書は有料なようだ。

一方、gotosocial がサムネイル用にデータを 1 フレームだけデコードする処理を見てみると、ダミーの黒いブランク画像を生成していた。

	// Create new empty "frame" image.
	// TODO: decode frame from video file.
	video.frame = blankImage(width, height)

https://github.com/superseriousbusiness/gotosocial/blob/ccecf5a7e4000f40522a790fcdaf2bf72d43d552/internal/media/video.go#L128

つまり、コンテナのメタデータ領域より (将来の実装のために) width と height はパースし、以降の処理はそれに従うものの、実際にデータはパースしておらず、サムネイルのデータに意味はない。 ということは、width と height にもほぼ意味がない。動画の再生はブラウザの <video> で制御されるはずだから、サーバーからのデータは動画それ自体以外不要だからだ。 したがって、今回は、HEVC の動画がアップロードされた場合は、そのサムネイルは「固定サイズの」ブランク画像とすることで、動画がアップロードできるようになった。

https://github.com/RollMan/gotosocial/commit/d14b14819032ef3736d81a9bfaf6dd803617bb59

結果

H.264 では動きが激しい部分でブロックノイズが酷くなりほぼモザイク状態だったのが、HEVC では見られる程度にはなったのがわかる。 40MB でこれだけ見られるならば OK ということにする。

日記

abema/go-mp4 の HEVC 対応

abema/go-mp4 では HEVC のデコードに対応しているようだ。

今回の本当の原因は、gotosocial が動画のコーデックを avc1 で決め打ちしていたのが原因か。

結局サムネイルは作成していないのだから、width と height を取得する処理は無駄であり、全種類の動画において固定サイズのブランクサムネイルを返しバイナリ・動作の軽量化を図るとして今回のコードの一部を PR に出そうと思ったが、 avc1 の null check の分岐に hvc1 のチェックも書けばサムネイルが作成できるようになるのだから、ここまで書いてから PR を出すことにした。 そうするとデータのデコーダも書かないといけなくなるが……

Mastodon の実装の大きさ

HEVC でエンコードした動画を含む投稿が mstdn.jp に federation されたものを、mstdn.jp のアカウントからダウンロードすると、H.264 であった。 mstdn.jp は federation された動画を (自インスタンスからアップロードされた動画も?) 再エンコードしているようだ。 mstdn.jp が動画をかなり大きめにキャッシュしているようであることは、自分がアップロードした動画のダウンロード (ストリーミング速度) が、 自分のインスタンスからアクセスする場合より mstdn.jp からアクセスする場合のほうが高速であったことから察していた (私のインスタンスからのダウンロードは us-east の S3 の帯域幅がボトルネックになる) が、実際は federation したデータすべてを自ストレージにコピーしている可能性が高い。 そう考えると、やはり mastodon はお一人様サーバとしては大きすぎる。規模の小さい VPS では再エンコードの計算負荷も馬鹿にならないし、(おそらく設定等で変更できるだろうが) federation のコピーをすべて保持していたらストレージはすぐ個人使用の範疇を超えるだろう。