From 8f4f4af7934cb5b36b4fa140bea7eae69bbbe24f Mon Sep 17 00:00:00 2001 From: MelekTaus Date: Sun, 11 Oct 2026 12:12:56 +0200 Subject: [PATCH] Videos: a new page for the video asset, its tab and worker (#402) - Videos: getting a video in, the conversion, the asset, the Video tab, wearing it on a sprite, and the worker container. - API: the videos routes and what a record says on its way. - Architecture: the new files on both sides. - Development: the PHP test, ffmpeg in the CI image, 1994 checks. - Sidebar: Videos after Fonts. Co-Authored-By: Claude Opus 5.5 --- API.md | 8 +++-- Architecture.md | 23 +++++++++---- Development.md | 9 +++-- Videos.md | 91 +++++++++++++++++++++++++++++++++++++++++++++++++ _Sidebar.md | 1 + 5 files changed, 121 insertions(+), 11 deletions(-) create mode 100644 Videos.md diff --git a/API.md b/API.md index a288f3c..97804af 100644 --- a/API.md +++ b/API.md @@ -28,6 +28,9 @@ All routes go through `api/index.php?r=…` (the site's nginx does not pass `PAT | GET | `sounds` | `[{file,name,bytes,updatedAt,owner,deletable}]` — what the sound picker offers | | POST | `sounds&name=jump.wav` | **raw sound bytes** → `{file,name,bytes}` (201); **422** for bytes that are no sound | | DELETE | `sounds/{file}` | 204, **403**, **409** like an image | +| GET | `videos` | `[{file,name,status,…,bytes,updatedAt,owner,deletable}]` — what the video picker offers. `status` is `queued`, `converting` (with `progress`, 0 to 1), `failed` (with `error`) or `ready` (with `width`, `height`, `duration`, `fps`, `audio` and `poster`, the `.jpg` beside the MP4) | +| POST | `videos&name=holiday.mov` | **raw video bytes** → the same record (201). The name `{folder}/{sha256}.mp4` is the hash of the upload and known at once; the worker converts it later (see [Videos](Videos)). The same bytes again answer with what is there, a failed one is tried again. **422** for bytes that cannot be a video (the start of an ISO/MP4, Matroska, AVI or MPEG-TS file), **413** above 2 GB. Unlike every other upload it is streamed to disk, so the 256 MB ceiling of the other routes does not apply | +| DELETE | `videos/{file}` | 204, **403**, **409** like an image; also for one that waits or failed, which drops the upload | | POST | `pixels` | **raw cel bytes** → `{file,bytes}` (201) — the pixels of one layer on one frame of [pixel art](Pixel-Art); **422** for anything that is not a cel. No listing and no delete: a cel is found by the name in the project | | GET | `plugins` | `[{file,bytes,updatedAt}]` — every `.js` in `data/plugins/`, sorted by file name | | GET | `plugins/{file}` | the module source as `text/javascript` (`nosniff`, `no-cache`); 404 for anything but a bare file name there — see [Plugins](Plugins) | @@ -49,7 +52,7 @@ is still the price of admission. See [Accounts](Accounts). ## Uploaded files belong to an account (#344) -Every account uploads images, fonts and sounds into a **folder of its own**, and the folder is part +Every account uploads images, fonts, sounds and videos into a **folder of its own**, and the folder is part of the name the project stores: `{folder}/{sha256}.{ext}`, for example `data/sounds/a590cab0f8b7/f1f2…f558.wav`. The folder is a random 12-character name kept as `folder` in `users.json`, given with the first upload. It is not the account name, so renaming an @@ -122,7 +125,8 @@ the server strips directories, control characters and anything not valid UTF-8, pickers show; an upload that carries no `name` leaves the known one alone rather than erasing it. A file uploaded before names were kept simply has `""`, and reads as it always did. -**What the web server hands out as files** is only images, fonts, sounds and cels. Everything else +**What the web server hands out as files** is only images, fonts, sounds, videos (with their +posters and details) and cels. A video waiting in `data/videos/queue/` is hidden like a project. Everything else under `data/` (projects, snapshots, releases, accounts, sessions) is written readable for PHP alone (`0640`, `AtomicFile::HIDDEN`), so nginx answers **403** for it and the sign-in cannot be walked around. Every file the API writes goes through `AtomicFile`: a temp file beside it, its mode, then a diff --git a/Architecture.md b/Architecture.md index ea5d36b..233765f 100644 --- a/Architecture.md +++ b/Architecture.md @@ -128,7 +128,7 @@ web/index.html Vite source page (dev) index.html built page web/play.html a released game (#266) play.html built page (generated, served) src/main.ts, play.ts the two entries; page.ts is the canvas both are (the WebGL context, root, resize) src/core/model/ Project (object holders), Asset + AssetTypes, SceneObject (and what a list calls one: - `objectName`, `groupName`), Properties (value types, and the walks over a nested value: `mapNested`, a struct field taken out or renamed), hierarchy, walk (generic parent/child walks), instance (container content resolved from its template), usages (where an asset is used), container (the cycle guard), placement (what may be dropped where), texture (frames, the slice grid, a frame's collision outlines, and the nine-slice and sort line of any picture, #308), cutTarget (which picture a nine-slice or sort line is set on: a texture frame, or pixel art and vector art as a whole, #308), frameOutline (#193: a frame's outlines as a collider wears them, centred, a pixel to a unit, cut into pieces once per drawn list), traceCollision (#161: a frame's outlines read off its picture — the pixels from an alpha threshold on are solid, each 4-connected island is walked round along the pixel edges and cut down to a few corners without ever crossing itself; crumbs below 1 % of the frame drop out), generated (pictures that are drawn rather than uploaded, #185: the key `gen:@`, the register of readers that tell the picture path an asset's canvas size and revision, and the `fingerprint` pixel art and vector art make their revision with), vectorart (#129: the canvas as a property, which ring is worth drawing, and the revision as a fingerprint of the drawing rather than a counter, a texture fill's picture included) + vectorPath (what a shape draws, as plain commands: closed rings fill, every ring strokes, a segment is a line while neither of its ends carries a handle; a paint resolved to sorted stops or the picture it names) + vectorPaint (#186: gradients and texture fills — where a paint lies when it is switched on, the colour between two stops, adding, moving and removing a stop, a paint moved with its shape, and the rule that a texture fill takes uploaded pictures only) + vectorBoolean (#187: union, subtract, intersect and exclude on the sampled outlines, each shape read by the even-odd rule; the result rings in the order Pixi's hole detection needs, every outline followed by its holes and preceded by the islands inside them) + vectorCurves (#208: every sampled point remembers its cubic and its `t`, so a run of result points along one cubic becomes that cubic again, cut with de Casteljau), pixelart (#130: the palette a picture is painted in, how the layers of a frame come together, the sheet of all frames, a frame id read as a cut of it, and the revision) + pixelCel (the file of one layer on one frame, and its SHA-256 name worked out in the browser) + pixelDraw (what the tools do to the indices: brush, line, rectangle, ellipse, fill, resize about an anchor, merge, a palette colour taken out) + pixelEdit (every edit of layers, frames and cels as a function from one state to the next), polygon (an outline checked, cut into convex pieces and clipped to a rectangle), tile (the three kinds and their block shapes, reading a tile's pictures, the animation step), autotile (which quarter tile a neighbourhood asks for, the 47/16 tiles a block produces, 1 for a plain tile), tilemap (the cells of a layer, which neighbours a cell is joined to, the cells a stroke or a rectangle covers), tilemapCollision (the solid cells of a map, drawn in or out by their tile's collision scale and merged into rectangles, one group per one-way angle and per surface (`tileMaterials`, #193), the outlined cells as polygons — an autotile's outlines cut into its quarters and pieced together per cell like its picture —, where one sits in the world, and which cells of a placed map a world rectangle can reach, in cells (`cellsWithin`, #184)), tilemapMemo (what a map's cells work out to, kept while they hold the same: a version per cell map compared cell by cell, since strokes paint in place), animation (which frame at time t, a row's value there, what a row may aim at, the players on an object; KeyRef and its lookups) + animationPreview (the root at a frame, written into copies), curve (#247: a drawn easing evaluated at t — each segment a cubic solved for t by bisection, held to its rules by `sanitizedCurve`: ends at 0 and 1, no point overtaking another, no handle past its neighbour — and which keys and `Lerp` nodes use one) + curvePresets (the built-in eases traced as curves, a segment halved only where it strays), graphLookup (what a graph sees of the project: behaviors to run, state machines to read, curves to ease along), structTypes (#253: Struct assets as types — every typed struct variable, struct field and `Pack`/`Expand` found and brought in line with its asset, a renamed field carried through every value first, the cycle rule), Scope (how long a shared value lives: per instance, one for the run, or one per scene and rebuilt with every scene that loads, #164 — for a state machine's current state and for the values of a Variables asset), collisionLayers (which colliders may meet: the project's named layers, a collider's layer and mask, and the one rule `layersMeet` the engine, Solids and the event pair search all answer by), renderLayers (#302: which cameras see an object — the project's named render layers, an object's own choice or its parent's, a camera's `Sees` and `Overlay`) + `objectName`, `groupName`), Properties (value types, and the walks over a nested value: `mapNested`, a struct field taken out or renamed), hierarchy, walk (generic parent/child walks), instance (container content resolved from its template), usages (where an asset is used), container (the cycle guard), placement (what may be dropped where), texture (frames, the slice grid, a frame's collision outlines, and the nine-slice and sort line of any picture, #308), cutTarget (which picture a nine-slice or sort line is set on: a texture frame, or pixel art and vector art as a whole, #308), frameOutline (#193: a frame's outlines as a collider wears them, centred, a pixel to a unit, cut into pieces once per drawn list), traceCollision (#161: a frame's outlines read off its picture — the pixels from an alpha threshold on are solid, each 4-connected island is walked round along the pixel edges and cut down to a few corners without ever crossing itself; crumbs below 1 % of the frame drop out), generated (pictures that are drawn rather than uploaded, #185: the key `gen:@`, the register of readers that tell the picture path an asset's canvas size and revision, and the `fingerprint` pixel art and vector art make their revision with), vectorart (#129: the canvas as a property, which ring is worth drawing, and the revision as a fingerprint of the drawing rather than a counter, a texture fill's picture included) + vectorPath (what a shape draws, as plain commands: closed rings fill, every ring strokes, a segment is a line while neither of its ends carries a handle; a paint resolved to sorted stops or the picture it names) + vectorPaint (#186: gradients and texture fills — where a paint lies when it is switched on, the colour between two stops, adding, moving and removing a stop, a paint moved with its shape, and the rule that a texture fill takes uploaded pictures only) + vectorBoolean (#187: union, subtract, intersect and exclude on the sampled outlines, each shape read by the even-odd rule; the result rings in the order Pixi's hole detection needs, every outline followed by its holes and preceded by the islands inside them) + vectorCurves (#208: every sampled point remembers its cubic and its `t`, so a run of result points along one cubic becomes that cubic again, cut with de Casteljau), pixelart (#130: the palette a picture is painted in, how the layers of a frame come together, the sheet of all frames, a frame id read as a cut of it, and the revision) + pixelCel (the file of one layer on one frame, and its SHA-256 name worked out in the browser) + pixelDraw (what the tools do to the indices: brush, line, rectangle, ellipse, fill, resize about an anchor, merge, a palette colour taken out) + pixelEdit (every edit of layers, frames and cels as a function from one state to the next), polygon (an outline checked, cut into convex pieces and clipped to a rectangle), tile (the three kinds and their block shapes, reading a tile's pictures, the animation step), autotile (which quarter tile a neighbourhood asks for, the 47/16 tiles a block produces, 1 for a plain tile), tilemap (the cells of a layer, which neighbours a cell is joined to, the cells a stroke or a rectangle covers), tilemapCollision (the solid cells of a map, drawn in or out by their tile's collision scale and merged into rectangles, one group per one-way angle and per surface (`tileMaterials`, #193), the outlined cells as polygons — an autotile's outlines cut into its quarters and pieced together per cell like its picture —, where one sits in the world, and which cells of a placed map a world rectangle can reach, in cells (`cellsWithin`, #184)), tilemapMemo (what a map's cells work out to, kept while they hold the same: a version per cell map compared cell by cell, since strokes paint in place), animation (which frame at time t, a row's value there, what a row may aim at, the players on an object; KeyRef and its lookups) + animationPreview (the root at a frame, written into copies), curve (#247: a drawn easing evaluated at t — each segment a cubic solved for t by bisection, held to its rules by `sanitizedCurve`: ends at 0 and 1, no point overtaking another, no handle past its neighbour — and which keys and `Lerp` nodes use one) + curvePresets (the built-in eases traced as curves, a segment halved only where it strays), graphLookup (what a graph sees of the project: behaviors to run, state machines to read, curves to ease along), structTypes (#253: Struct assets as types — every typed struct variable, struct field and `Pack`/`Expand` found and brought in line with its asset, a renamed field carried through every value first, the cycle rule), Scope (how long a shared value lives: per instance, one for the run, or one per scene and rebuilt with every scene that loads, #164 — for a state machine's current state and for the values of a Variables asset), video (#402: a video or a clip of it by id, the stretch a clip plays inside its video, and the key `video:#~~~` a playing video goes by where a picture is named by `TextureRef.file`), collisionLayers (which colliders may meet: the project's named layers, a collider's layer and mask, and the one rule `layersMeet` the engine, Solids and the event pair search all answer by), renderLayers (#302: which cameras see an object — the project's named render layers, an object's own choice or its parent's, a camera's `Sees` and `Overlay`) src/core/properties/ PropertySchema, kinds (one row per value kind), listItems (an item's kind, read off its value), resolve (the only override merge), deviation (what is gold), collider/camera/bounds geometry, clip (the region clip areas cut each object to, a convex polygon, #260) (with the side a one-way collider blocks from, and the rectangle around a shape a broad phase looks at first, #184), convex (capsules and polygons: rounded convex pieces against each other), ray (where a ray meets a shape, in double precision), rigidbody (what the engine reads off a rigidbody, a collider's material and the scene), joint (what it reads off a joint: its settings, where its far end is, its length and a hinge's limits, #154), aspect, enabled, editorRun, assetRef/textureRef (how a reference reads), declaredRows (the row a Property node declares in a shader or particle graph, and the params that node has in both) src/core/util/ list (take out, put back and move an item — what a command's undo is made of), vec (dot, the closest point on a segment — shared by the collider maths, the outline hit area and the polygon grips; a cubic Bezier at `t` and cut in two at `t` — shared by vector art, its Boolean operations and the curve grips), color (`mixColor`, channel by channel — keys of an animation, stops of a gradient, the particles' Mix colour), recorder (#253: assignments taken back last first — the undo of an edit whose reach is not known in advance) src/core/commands/ undoable commands (pixelArtCommands: one command runs any pixel art edit and keeps the state before it, #130), CommandStack, SetBagValueCommand base, RenameCommand base (undone by renaming back: variable, struct field, port), MoveCommand base (undone by moving back: tile picture, tilemap layer, vector shape), vectorArtCommands (#129: the shapes of a drawing, one gesture one command), one import command shape for texture/font/sound, require.ts (validated lookups, and what an instance may not change), naming.ts @@ -153,8 +153,9 @@ src/core/store/ serialize (untrusted JSON → Project), projectPlugins (w ReleaseStore/HttpReleaseStore (a project's released copy and the link to it, #266), AuthStore/HttpAuthStore (signing in and out, #177; changing the password, #182), FileStore (what the server holds of one kind: upload and list, no delete) + HttpFileStore (one - generic client over the three file collections) — TextureStore, FontStore and SoundStore are its - type parameters, nothing else + generic client over the four file collections) — TextureStore, FontStore, SoundStore and + VideoStore (#402: an upload with its status on the way to an MP4) are its type + parameters, nothing else src/core/time.ts the global clock: one scaled step a frame, shared by the world, the particles, the shaders and the animation preview src/core/music/ time (ticks, 480 to the quarter, tempo and meter, a position as bar, beat and tick), sequence (the notes of a window of playing time, across the seam of a loop — a pure rule), Scheduler @@ -239,6 +240,10 @@ src/viewport/ WorldViewport (the drawing of a world or a holder, the ba resolution; registers vector art in both halves of the picture path; PaintCache keeps a painter's gradients and hands the ones it no longer draws to Pixi's texture garbage collector, and a texture fill waits for its picture instead of drawing without it, #186), + VideoLibrary (#402: one `