| … | … | @@ -265,10 +265,14 @@ substrate; what is missing is generalising it to images and naming it as the mod |
| 265 | 265 | This is the constraint that decides whether the pyramid is clean or partial. |
| 266 | 266 | |
| 267 | 267 | hypernote generates its ladder with **ffmpeg via `bare-subprocess`, on a server** |
| 268 | | (`lib/media/video-processor.js`). A handset posting a photo would have to produce four levels at post |
| 269 | | time: CPU, battery, and an image encoder that may not exist in Bare on that device. **Video is |
| 270 | | already solved — HLS *is* the manifest. Images are not, and the phone is where most posts come |
| 271 | | from.** |
| 268 | (`lib/media/video-processor.js:1,19-20,28`). A handset producing levels at post time costs CPU and |
| 269 | battery, and needs an encoder that is actually present on the device. |
| 270 | |
| 271 | ⚠ **Corrected 2026-09-05 — see §6.6.5.** An earlier version of this paragraph said images were the |
| 272 | unsolved case because Bare might lack an image encoder on a handset. That was written without |
| 273 | checking: images are downsampled in the **browser**, not in Bare, and `favela-android` is a WebView |
| 274 | shell — so the encoder is almost certainly already there. Video is the case that needs a real |
| 275 | decision. |
| 272 | 276 | |
| 273 | 277 | So the pyramid must be allowed to be **partial**, and that has three consequences: |
| 274 | 278 | |
| … | … | @@ -284,6 +288,41 @@ So the pyramid must be allowed to be **partial**, and that has three consequence |
| 284 | 288 | worse a floor than what we ship now, and improves monotonically as encoders become available. That |
| 285 | 289 | is the property that makes it safe to adopt before the image encoder question is settled. |
| 286 | 290 | |
| 291 | ### 6.6.5 What we actually use today — measured, not assumed |
| 292 | |
| 293 | | | tool | where it runs | |
| 294 | |---|---|---| |
| 295 | | **image** | browser **`<canvas>`** — `createImageBitmap` → `drawImage` → `toBlob`, driven by `resize-plan.js` (`MAX_BYTES`, `resizePlan`, `aspectRatioOf`) | **client-side**, at compose time (`favela-web/web/static/compose.js:12,29-35`) | |
| 296 | | **video** | the **`ffmpeg` / `ffprobe` system binaries** via `spawn` from `bare-subprocess` | **server-side** (`hypernote/lib/media/video-processor.js`) | |
| 297 | |
| 298 | **Images are close to free, and the ladder is half-built already.** `resizePlan` is a level-decision |
| 299 | function that currently emits **one** level. Emitting N is a small change to existing code, costs no |
| 300 | binary size because canvas is in the runtime, and — since `favela-android` is a WebView shell |
| 301 | (hyper-social #45 decision 1) — the handset gets the same path. ⚠ Unverified against the actual |
| 302 | WebView; verify before relying on it. |
| 303 | |
| 304 | **Video is the case that needs a decision, and three earlier claims of mine were wrong:** |
| 305 | |
| 306 | 1. **No app of ours depends on `bare-ffmpeg`.** It is a transitive dependency of `keet-cli` only. |
| 307 | hypernote has **no ffmpeg dependency at all** — it shells out to a **system binary** |
| 308 | (`_ffmpegPath = opts.ffmpegPath || 'ffmpeg'`), which is exactly why it works on a server and |
| 309 | cannot work on a phone. |
| 310 | 2. **"No alternative to ffmpeg" was false.** Platform video encoders exist on both handsets — Android |
| 311 | `MediaCodec`, iOS VideoToolbox — reachable through the capability bridge that hyper-social #45 |
| 312 | already chose as the direction. |
| 313 | 3. **The phone may not need to transcode video at all.** §6.6.4's partial pyramid lets it post one |
| 314 | level and leave the ladder to a bridge or a well-resourced peer. **Playback needs nothing either** |
| 315 | — the WebView plays HLS natively. |
| 316 | |
| 317 | **`bare-ffmpeg` 1.1.0**, if we do want on-device transcode, is a Bare **native addon** rather than a |
| 318 | subprocess, which is what makes it viable where `spawn` is not. It ships prebuilds for |
| 319 | `android-arm`/`arm64`/`ia32`/`x64` and `ios-arm64`. **Measured: `prebuilds/android-arm64/bare-ffmpeg.bare` |
| 320 | is 29 MB** (linux-x64 is 39 MB; all 13 platforms together are 396 MB, but pgh's `by-arch/` publishing |
| 321 | prunes to the target). Against an APK that hyper-social #39 fought from 527 MB down to 117 MB, that is |
| 322 | roughly a 25% increase. |
| 323 | |
| 324 | ⇒ **It is a capability purchase, not a requirement.** Buy it where the ladder is actually produced. |
| 325 | |
| 287 | 326 | ⛔ **Who derived a level still matters.** §8's *"never re-encode a record you did not author"* cost two |
| 288 | 327 | of wj62's posts. Levels generated at authoring are the author's; a level a bridge generated later is |
| 289 | 328 | derived, and the manifest must say so rather than presenting it as authored bytes. |