Skip to content

A post can carry a video - #1963

Merged
wintermeyer merged 9 commits into
mainfrom
worktree-video
Sep 3, 2026
Merged

A post can carry a video#1963
wintermeyer merged 9 commits into
mainfrom
worktree-video

Conversation

@wintermeyer

Copy link
Copy Markdown
Owner

A post could carry photos but no clip. Now the composer takes a video (two minutes, 500 MB, both configurable). ffmpeg makes H.264 720p, AV1 1080p and a 360p pair for data-saving mode, every pulled frame passes the AI image check, then the post is created. Until then the text waits as a card in the author's feed with a live stage line, and a chip in the app bar shows the progress. The card plays the clip inline; VutuvWeb.PostVideoController answers byte ranges. The H.264 file federates as a video/mp4 Document, and the Mastodon API and /api/2.0 take uploads.

Left out on purpose: the bell entry from #1911, blurhash, swapping a clip on edit.

Operators: install ffmpeg and raise nginx's client_max_body_size to 520m (docs/ADMINS.md).

Closes #1906, #1907, #1908, #1909, #1910, #1911, #1912, #1913, #1914, #1915, #1924.

An AI agent wrote this text in my name. I know that is problematic.

https://claude.ai/code/session_017tGtKCW1DpJk5JmX5j41A3

#1910)

Ein Clip wird wie ein Foto sofort beim Hochladen abgelegt und von einer
Pipeline in Standbilder, eine H.264- und eine AV1-Fassung plus zwei
360p-Dateien verwandelt; jeder fertige Schritt steht als Stempel auf der
Zeile, damit ein vom Deploy getöteter Lauf dort weitermacht, wo er stand.
Die Standbilder gehen einzeln durch die vorhandene Bildprüfung, ein
abgelehntes Bild lehnt den ganzen Clip ab. Ein Beitrag mit Clip wartet
als eigene Zeile, bis der Clip fertig ist, und wird dann über denselben
Erstellungspfad angelegt, den der Composer genommen hätte.

Claude-Session: https://claude.ai/code/session_017tGtKCW1DpJk5JmX5j41A3
…tts-Chip (#1909, #1911, #1912)

Der Clip spielt auf der Karte im nativen Player mit AV1 zuerst und H.264 als
Rückfall, ausgeliefert über einen Proxy, der Byte-Ranges beantwortet, weil
Safari sonst gar nicht abspielt. Im Composer zeigt eine Kachel die Stufe der
Pipeline, ein Streifen aus Standbildern lässt das Cover wählen, und ein
Beitrag mit unfertigem Clip erscheint als Wartekarte im Feed und als Chip in
der Leiste auf jeder Seite, beides über denselben PubSub-Kanal.

Claude-Session: https://claude.ai/code/session_017tGtKCW1DpJk5JmX5j41A3
…und Doku (#1913, #1914, #1915)

Der Clip geht als ein Document video/mp4 mit Cover, Länge und Maßen ins
Fediverse, und zwar die H.264-Datei, weil Mastodon nur die ohne Re-Encode
übernimmt; ein fremdes Video wird mit Typ und Cover-Adresse in der
Bildtabelle gehalten, nur das Cover geholt und geprüft, der Clip spielt
direkt vom fremden Server. Die Mastodon-API nimmt einen Clip mit 202 an,
antwortet 206 bis er fertig ist und lehnt einen zu frühen Status mit 422 ab.

Claude-Session: https://claude.ai/code/session_017tGtKCW1DpJk5JmX5j41A3
…em Smoketest

Der Längen-Chip des Players sitzt oben links, weil Chrome und Safari vor
dem ersten Abspielen ihre Leiste unten über das Poster legen und der Chip
darunter halb verschwand; die Beschreibung eines bestehenden Clips wird
beim Verlassen des Feldes geschrieben, nicht beim Speichern.

Claude-Session: https://claude.ai/code/session_017tGtKCW1DpJk5JmX5j41A3
…rbundenen Sockets

post_videos ist ein neues Wort an der URL-Wurzel und darum reserviert; der
Fortschritts-Hook wird nur beim verbundenen Mount angehängt, weil ein nackter
Socket im Test keinen Lifecycle hat und der tote Render ohnehin keine
Nachrichten bekommt.

Claude-Session: https://claude.ai/code/session_017tGtKCW1DpJk5JmX5j41A3
Ein Erstellungspfad statt drei Kopien (Posts.create_in_context/4 für
Composer und Publisher), der Fortschritts-Hook als eigenes on_mount-Modul
statt in InitAssigns für jede Seite, Prozent-Ticks werden im Speicher
gepatcht statt pro Tick die Datenbank zu fragen, ffprobe läuft einmal pro
Job, und der Proxy geht durch den geteilten ImageProxy-Schalter. Abgeleitete
Werte in den Komponenten bleiben assign/3: als Map.put blieb die Wartekarte
auf queued stehen, während die Zeile längst abgelehnt war.

Claude-Session: https://claude.ai/code/session_017tGtKCW1DpJk5JmX5j41A3
Der Test tötete den Job, sobald die Zeile auf transcoding stand, also
manchmal bevor ffmpeg die Ausgabedatei geöffnet hatte. Der verwaiste ffmpeg
legte sie dann nach dem Aufräumen des Wiederanlaufs an und starb erst beim
nächsten Fortschritts-Write, und die Datei blieb liegen. In Produktion liegt
zwischen Tod und Wiederanlauf das 180-Sekunden-Fenster des Sweepers, im Test
nichts; jetzt wartet er auf die Temp-Datei, die der Store dafür öffentlich
nennt.

Claude-Session: https://claude.ai/code/session_017tGtKCW1DpJk5JmX5j41A3
Beide Branches hatten Strings angelegt, und der Rebase kollidierte in den
vier Katalogen 36-mal je Datei, fast alles Zeilennummern-Drift. Statt 144
Hunks von Hand zu mischen, nahm jeder Commit seine eigene Seite, dann lief
der Extract über den vereinten Code, und ein Skript setzte jeden Eintrag,
den main auch kennt, auf den Stand von main zurück und prüfte, dass keine
Übersetzung einer Seite fehlt.

Claude-Session: https://claude.ai/code/session_017tGtKCW1DpJk5JmX5j41A3
Der Test aus #1960 baute die Quellen und die erwarteten Zeilen mit je einem
eigenen Blick auf die Uhr, und auf CI lag zwischen beiden eine
Sekundengrenze: jede Zeile 07:37:52 gegen 07:37:53, roter Lauf für #1963.
Jetzt gibt es eine Liste rows, aus der beide Seiten kommen, also nur einen
Blick auf die Uhr.

Claude-Session: https://claude.ai/code/session_017tGtKCW1DpJk5JmX5j41A3
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Posts can carry a video

1 participant