@bitmagic/cli 0.1.80 → 0.1.81

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/README.md +14 -2
  2. package/assets/blender-level/bmlevel/__init__.py +14 -4
  3. package/assets/blender-level/bmlevel/core.py +62 -0
  4. package/assets/blender-level/bmlevel/scene_bpy.py +14 -1
  5. package/assets/blender-level/bmlevel/scene_null.py +3 -0
  6. package/assets/blender-level/tests/test_core.py +23 -0
  7. package/assets/skills/joining-the-game-exchange/SKILL.md +7 -5
  8. package/dist/commands/trailer-previews.d.ts +14 -0
  9. package/dist/commands/trailer-previews.js +45 -0
  10. package/dist/commands/trailer-previews.js.map +1 -0
  11. package/dist/commands/trailer-publish.d.ts +8 -0
  12. package/dist/commands/trailer-publish.js +17 -0
  13. package/dist/commands/trailer-publish.js.map +1 -1
  14. package/dist/commands/trailer.js +19 -4
  15. package/dist/commands/trailer.js.map +1 -1
  16. package/dist/package/collect-urls.d.ts +2 -2
  17. package/dist/package/collect-urls.js +2 -2
  18. package/dist/scaffold/blender-level-skill-files.js +17 -11
  19. package/dist/scaffold/blender-level-skill-files.js.map +1 -1
  20. package/dist/scaffold/project-files.js +1 -1
  21. package/dist/scaffold/skill-files.js +36 -3
  22. package/dist/scaffold/skill-files.js.map +1 -1
  23. package/dist/trailer/client.d.ts +39 -0
  24. package/dist/trailer/client.js +54 -2
  25. package/dist/trailer/client.js.map +1 -1
  26. package/dist/trailer/publish-record.d.ts +5 -0
  27. package/dist/trailer/publish-record.js +11 -0
  28. package/dist/trailer/publish-record.js.map +1 -1
  29. package/dist/trailer/publish.d.ts +48 -0
  30. package/dist/trailer/publish.js +116 -3
  31. package/dist/trailer/publish.js.map +1 -1
  32. package/package.json +4 -4
@@ -153,15 +153,16 @@ walk it in \`bitmagic dev\`. \`assets add\` uploads to the CDN, \`verify\` type-
153
153
  drives a real Chrome through a settle loop, \`judge\` spends sparks and must not be run twice on
154
154
  one milestone. Run it at the END of a pass — never after an edit.
155
155
 
156
- **The gate: do not hand the level to the engine until the contact sheet has nothing wrong on it
157
- and both audit modes exit 0.** Every build writes \`build/level/<name>.glb\` already; that is free.
156
+ **The gate: do not hand the level to the engine until the build prints no \`WARNING\`, the contact
157
+ sheet has nothing wrong on it and both audit modes exit 0.** Every build writes \`build/level/<name>.glb\` already; that is free.
158
158
  Publishing it is what costs. A round trip through the engine to discover a hole in a floor is a
159
159
  round trip the audit would have made for nothing, and the station lost a day to exactly that.
160
160
 
161
161
  | The level is wrong like this | Found in |
162
162
  |---|---|
163
163
  | a hole in a floor, a doorway you cannot walk through, a route blocked by a prop, a room that is not sealed | the Blender loop — \`audit.py\`, both modes |
164
- | z-fighting, a blank wall, a sign that reads backwards, a missing ceiling, stairs ending in air, a wedge of sky beside a junction, the wrong footprint | the Blender loop — the contact sheet |
164
+ | two surfaces flickering into each other (z-fighting) | the Blender loop — the build's \`[bmlevel] WARNING\`; a still render cannot show it |
165
+ | a blank wall, a sign that reads backwards, a missing ceiling, stairs ending in air, a wedge of sky beside a junction, the wrong footprint | the Blender loop — the contact sheet |
165
166
  | the level washed out or black, an empty published game, doors animating inside walls, no NPCs, the framerate | the engine handoff — nothing in Blender can see these |
166
167
 
167
168
  ## The Blender loop
@@ -173,7 +174,10 @@ round trip the audit would have made for nothing, and the station lost a day to
173
174
  \`\`\`
174
175
 
175
176
  It prints \`LEVEL … colliders … doors … volumes …\`, \`BOUNDS\`, and a \`REGISTER bitmagic assets add …\`
176
- line to keep for step 7. Outputs: \`build/level/<name>.glb\` (one draw call per material),
177
+ line to keep for step 7. A \`[bmlevel] WARNING … will FLICKER in the game (z-fighting)\` line names
178
+ faces of two materials lying on one plane — trim flush on a wall, a sign on its board, a rug on the
179
+ floor — with game coordinates. A render does not show the flicker, so fix every one (rules below)
180
+ and rebuild before you go on. Outputs: \`build/level/<name>.glb\` (one draw call per material),
177
181
  \`<name>.blend\` (named, editable parts), \`<name>.mesh-level.json\`, \`<name>.doors.json\`. A syntax
178
182
  or geometry error prints a Python traceback naming the helper and the object — fix and rebuild;
179
183
  builds take seconds. \`python3 tools/level/<name>.py --dry-run\` gives the counts without Blender.
@@ -186,8 +190,8 @@ builds take seconds. \`python3 tools/level/<name>.py --dry-run\` gives the count
186
190
 
187
191
  Read \`build/level/review/contact-sheet.png\` (the log says which frame is which), then any single
188
192
  frame that looks wrong. You are looking for: a door with no wall around it, a room whose ceiling
189
- is missing, stairs that end in the air, a sign that reads backwards, two surfaces flickering into
190
- one another. Fix the script, rebuild, re-render. Do not go on to the audit while the sheet is
193
+ is missing, stairs that end in the air, a sign that reads backwards. (Flicker never shows in a
194
+ still frame; that is the build's \`WARNING\`.) Fix the script, rebuild, re-render. Do not go on to the audit while the sheet is
191
195
  wrong; the audit finds holes, not ugliness.
192
196
 
193
197
  The fixed set is what you read first. After that the flags turn it into a camera you can aim, so
@@ -217,8 +221,8 @@ Every check prints its ray count and its failures with coordinates; exit 1 means
217
221
  not done. A failed \`floor_continuity\` is a hole a player falls through; a failed \`door_clearance\`
218
222
  or \`link_clearance\` is a doorway or route the player cannot walk (a jamb in the opening, a table
219
223
  on the line between two rooms, a step that ends short); a failed \`sealed_envelope\` is a gap in a
220
- roof or wall. \`coplanar_overlap\` is a warning: two faces on one plane, which flicker. Never
221
- answer a failure by removing the volume or the link that found it.
224
+ roof or wall. \`coplanar_overlap\` is a warning: two faces on one plane, which flicker — fix it as
225
+ you would the build's. Never answer a failure by removing the volume or the link that found it.
222
226
 
223
227
  A failure sends you back to step 4, not on to step 7. Both modes exiting 0 with a clean contact
224
228
  sheet is the whole gate; nothing below can be trusted until you have it.
@@ -364,9 +368,11 @@ the single most common review-sheet finding.
364
368
  1. **Overlap, never abut.** Two slabs meeting edge to edge leave a hairline a ray falls through
365
369
  and a capsule catches on. Overlap by 5 cm. The audit's \`floor_continuity\` finds the seam.
366
370
  2. **Offset coplanar faces by 5 cm.** A cladding panel flush with the wall it decorates flickers.
367
- Push it out 5 cm (or in 5 cm, as an inset). \`coplanar_overlap\` warns about the ones on solids.
368
- 3. **Give layered parts distinct depths.** Casing 0.30 m, header 0.34 m, trim 0.26 m: no two
369
- layers share a face plane. The door helper does this for its own jambs and header.
371
+ Push it out 5 cm (or in 5 cm, as an inset). The build's \`[bmlevel] WARNING\` checks every face,
372
+ \`decor()\` and text included; the audit's \`coplanar_overlap\` only box colliders.
373
+ 3. **Give layered parts distinct depths.** On a 0.30 m wall: casing 0.34 m, header 0.38 m — no two
374
+ layers share a face plane, the wall's included. The door helper does this for its own jambs and
375
+ header (\`jamb_depth\` 0.34 m by default); on a thicker wall, pass a deeper \`jamb_depth\`.
370
376
 
371
377
  Bevels (\`bevel=\`) catch light on edges and read as "made", not "boxed"; 1–5 cm on furniture and
372
378
  casings, none on walls.
@@ -1 +1 @@
1
- {"version":3,"file":"blender-level-skill-files.js","sourceRoot":"","sources":["../../src/scaffold/blender-level-skill-files.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,MAAM,UAAU,wBAAwB;IACtC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8QR,CAAC;AACF,CAAC;AAED,MAAM,UAAU,iCAAiC;IAC/C,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyFR,CAAC;AACF,CAAC;AAED,MAAM,UAAU,4BAA4B;IAC1C,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4FR,CAAC;AACF,CAAC;AAED,MAAM,UAAU,4BAA4B;IAC1C,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyER,CAAC;AACF,CAAC;AAED,MAAM,UAAU,mCAAmC;IACjD,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgHR,CAAC;AACF,CAAC"}
1
+ {"version":3,"file":"blender-level-skill-files.js","sourceRoot":"","sources":["../../src/scaffold/blender-level-skill-files.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,MAAM,UAAU,wBAAwB;IACtC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkRR,CAAC;AACF,CAAC;AAED,MAAM,UAAU,iCAAiC;IAC/C,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2FR,CAAC;AACF,CAAC;AAED,MAAM,UAAU,4BAA4B;IAC1C,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4FR,CAAC;AACF,CAAC;AAED,MAAM,UAAU,4BAA4B;IAC1C,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyER,CAAC;AACF,CAAC;AAED,MAAM,UAAU,mCAAmC;IACjD,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgHR,CAAC;AACF,CAAC"}
@@ -1224,7 +1224,7 @@ the engine, already wired** — and hand-building a worse version of it is the m
1224
1224
 
1225
1225
  | The idea needs… | The engine already has | Read |
1226
1226
  |---|---|---|
1227
- | A melee weapon — sword, axe, katana, spear, staff, lightsaber | 12 built-in procedural weapons | \`combat-system.md\` |
1227
+ | A melee weapon — sword, axe, katana, spear, staff, laser sword | 12 built-in procedural weapons | \`combat-system.md\` |
1228
1228
  | A ranged weapon — pistol, rifle, bow, crossbow, bazooka, laser | Built-in set; aiming adapts to the camera mode | \`combat-system.md\` |
1229
1229
  | Punching, kicking, martial arts | \`UnarmedMeleeSystem\`, animations pre-loaded | \`combat-system.md\` |
1230
1230
  | Enemies that patrol, chase, attack, follow, flee, trade | NPC behaviours; NPCs use the built-in weapons too | \`npc-system.md\` |
@@ -198,9 +198,10 @@ and running one command again.
198
198
  per candidate window — and extract stills from the RENDERED file to check framing
199
199
  (\`ffmpeg -ss 12 -i <mp4> -frames:v 1 still.png\`). The timeline knows about events, not about
200
200
  beauty, occlusion or a camera pointing at a wall.
201
- 5. **Tune** (below), then **publish**: \`bitmagic trailer make --publish\` (or
202
- \`bitmagic trailer publish <mp4>\`) uploads to the CDN and puts the video first in the game
203
- page gallery; re-running replaces the previous trailer. Give the creator the printed URL.
201
+ 5. **Tune** (below), check the **15-second edition** too (next section), then **publish**:
202
+ \`bitmagic trailer make --publish\` (or \`bitmagic trailer publish <mp4>\`) uploads the trailer
203
+ and its edition to the CDN and puts the trailer first in the game page gallery; re-running
204
+ replaces the previous trailer. Give the creator the printed URL.
204
205
 
205
206
  Renders never overwrite — each run writes the next \`-rN\` — so compare against the previous
206
207
  cut before delivering.
@@ -210,12 +211,14 @@ cut before delivering.
210
211
  | File | What it is |
211
212
  |---|---|
212
213
  | \`trailer/shots.json\` | **The cut.** Committed. Your edits live here; \`make\` re-cuts from it every run. |
214
+ | \`trailer/preview-shots.json\` | **The 15-second edition's cut**, same format. Committed. \`"auto": true\` = written by \`make\` and rebuilt from shots.json every run; delete that line to own it. |
213
215
  | \`trailer/flythrough.json\` | A camera path you author. Committed. Records with nobody playing — the whole trailer, or just the opening shot. |
214
216
  | \`.bitmagic/trailer/recordings/<name>/\` | A recording: \`<name>_%06d.png\` at 60 fps + \`timeline.json\` (events, sounds, HUD). |
215
217
  | \`.bitmagic/trailer/<name>/analysis.json\` | Scored highlight windows: \`startFrame\`, \`endFrame\`, \`score\`, event digests (\`"f3121 explosion i0.9"\`), \`sheet\`. |
216
218
  | \`.bitmagic/trailer/<name>/sheets/\` | Contact sheets, one per window — read these. |
217
219
  | \`.bitmagic/trailer/<name>/beats.json\`, \`music.mp3\` | The bed and its beat grid. |
218
220
  | \`.bitmagic/trailer/<name>/edl.json\`, \`*-trailer[-rN].mp4\` | The frame-exact cut and the renders (each with its \`.edl.json\`). |
221
+ | \`.bitmagic/trailer/<name>/preview-edl.json\`, \`*-trailer[-rN]-preview.mp4\` | The 15-second edition's cut and render, next to the trailer it belongs to. |
219
222
 
220
223
  ### shots.json
221
224
 
@@ -255,6 +258,35 @@ direction per leg (a drifting push or a slow arc reads as deliberate; a whip-pan
255
258
  mistake), and give each leg several seconds. Six markers over 20 s is plenty for the two or three
256
259
  shots a cut will actually use — you are recording candidate footage, not the trailer.
257
260
 
261
+ ## The 15-second edition — what the game page actually plays
262
+
263
+ The game page does not open on the trailer. It loops a **15-second silent preview** behind its
264
+ first screen — 16:9 on a desktop, full-screen 9:16 on a phone — and the trailer itself is one
265
+ click away. Bitmagic's server makes those previews from the edition you upload next to the trailer;
266
+ without one it takes the trailer's FIRST 15 seconds, which are its opening, not its best moments.
267
+ So every \`make\` also renders the edition, \`*-trailer[-rN]-preview.mp4\`, and \`--publish\` uploads
268
+ it with the trailer.
269
+
270
+ - **The first cut is automatic**: the trailer's highest-scoring shots up to 15 s, in trailer order,
271
+ without the end card or any title. It lives in \`trailer/preview-shots.json\` with
272
+ \`"auto": true\` and follows shots.json on every run.
273
+ - **Make it the best 15 seconds**, not a summary: the strongest action the game has, the hook
274
+ first, no menus, no text (the page draws the game's name and the Play button over it), each shot
275
+ readable at a glance. It loops, so a last shot that leads back into the first is a bonus.
276
+ To edit it, delete the \`"auto"\` line and change the shots (\`seconds\` only — it has no music),
277
+ then run \`make\` again.
278
+ - **Phones see the centre of the frame**: the largest centred 9:16 window of the 16:9 edition. Check
279
+ it on stills from the rendered edition before publishing
280
+ (\`ffmpeg -ss 5 -i <preview.mp4> -vf "crop=ih*9/16:ih" -frames:v 1 phone.png\`): the player, the
281
+ car, the explosion must be in the middle. If the action lives at the edges, render a vertical
282
+ edition (a portrait recording, or your own 9:16 cut) and pass it with
283
+ \`bitmagic trailer publish <mp4> --preview-tall <9x16.mp4>\`.
284
+ - **Already published?** \`bitmagic trailer previews --wide <16x9.mp4> [--tall <9x16.mp4>]\`
285
+ uploads new editions for the trailer on the page; the page shows them within minutes (a browser
286
+ that already played the old ones can keep them up to a day).
287
+ - \`--no-preview\` on \`make\` or \`publish\` skips the edition; the page then loops the trailer's
288
+ first 15 seconds. Only do that when the creator asks.
289
+
258
290
  ## Craft — read \`references/trailer-craft.md\` before touching the cut
259
291
 
260
292
  Everything about WHICH moments and in what order lives in
@@ -289,6 +321,7 @@ The creator talks; you edit \`trailer/shots.json\`, run \`bitmagic trailer make\
289
321
  | "shorter / longer" | \`--seconds N\` only matters for a fresh auto cut (\`--reset-shots\`); otherwise add or remove shots |
290
322
  | "start over" | \`bitmagic trailer make --reset-shots\` |
291
323
  | "use the other recording" | \`--recording <dir>\`; shots.json from another recording is kept beside a fresh cut |
324
+ | "the page video is boring", "better preview", "the loop on the game page" | edit \`trailer/preview-shots.json\` (delete \`"auto"\` first): the best 15 s, action centred; \`make\`, then \`publish\` — or \`trailer previews --wide\` for the trailer already on the page |
292
325
 
293
326
  Then: **quota check** the labels (rule of three, one standing start), render, extract two stills
294
327
  from the changed region, compare with the previous \`-rN\`, deliver the URL.
@@ -1 +1 @@
1
- {"version":3,"file":"skill-files.js","sourceRoot":"","sources":["../../src/scaffold/skill-files.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAGvD;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,kBAAkB;IAChC,OAAO;;;;;;;;;;;;;;;;;;CAkBR,CAAC;AACF,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,EAAE,SAAS,EAAE,SAAS,EAAsB;IAC7E,2FAA2F;IAC3F,6EAA6E;IAC7E,MAAM,QAAQ,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,GAAG,GAAG,GAAG,IAAI,CAAC,CAAC;IAC7F,6FAA6F;IAC7F,0EAA0E;IAC1E,MAAM,WAAW,GACf,QAAQ,CAAC,MAAM,KAAK,CAAC;QACnB,CAAC,CAAC,EAAE;QACJ,CAAC,CAAC,8FAA8F;YAC9F,mEAAmE;YACnE,QAAQ,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,KAAK,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;YAC5C,iFAAiF,CAAC;IAExF,OAAO;;sBAEa,SAAS,SAAS,iBAAiB,CAAC,SAAS,CAAC;;;EAGlE,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqCZ,CAAC;AACF,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,kBAAkB;IAChC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6JR,CAAC;AACF,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,2BAA2B;IACzC,MAAM,MAAM,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,sCAAsC,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IAC/F,OAAO,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAC1C,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,qBAAqB;IACnC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqIR,CAAC;AACF,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,+BAA+B;IAC7C,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkER,CAAC;AACF,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,wBAAwB;IACtC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgER,CAAC;AACF,CAAC"}
1
+ {"version":3,"file":"skill-files.js","sourceRoot":"","sources":["../../src/scaffold/skill-files.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAGvD;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,kBAAkB;IAChC,OAAO;;;;;;;;;;;;;;;;;;CAkBR,CAAC;AACF,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,EAAE,SAAS,EAAE,SAAS,EAAsB;IAC7E,2FAA2F;IAC3F,6EAA6E;IAC7E,MAAM,QAAQ,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,GAAG,GAAG,GAAG,IAAI,CAAC,CAAC;IAC7F,6FAA6F;IAC7F,0EAA0E;IAC1E,MAAM,WAAW,GACf,QAAQ,CAAC,MAAM,KAAK,CAAC;QACnB,CAAC,CAAC,EAAE;QACJ,CAAC,CAAC,8FAA8F;YAC9F,mEAAmE;YACnE,QAAQ,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,KAAK,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;YAC5C,iFAAiF,CAAC;IAExF,OAAO;;sBAEa,SAAS,SAAS,iBAAiB,CAAC,SAAS,CAAC;;;EAGlE,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqCZ,CAAC;AACF,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,kBAAkB;IAChC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8LR,CAAC;AACF,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,2BAA2B;IACzC,MAAM,MAAM,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,sCAAsC,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IAC/F,OAAO,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAC1C,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,qBAAqB;IACnC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqIR,CAAC;AACF,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,+BAA+B;IAC7C,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkER,CAAC;AACF,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,wBAAwB;IACtC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgER,CAAC;AACF,CAAC"}
@@ -2,6 +2,10 @@ import type { Environment } from '../config/environments.js';
2
2
  import { type ApiDeps } from '../http/client.js';
3
3
  export declare const TRAILER_UPLOAD_URL_PATH = "/api/cli/v1/trailer/upload-url";
4
4
  export declare const TRAILER_ATTACH_PATH = "/api/cli/v1/trailer/attach";
5
+ export declare const TRAILER_PREVIEW_UPLOAD_URL_PATH = "/api/cli/v1/trailer/preview-upload-url";
6
+ export declare const TRAILER_PREVIEWS_PATH = "/api/cli/v1/trailer/previews";
7
+ /** A 15-second edition of the trailer: `wide` is 16:9 (desktop), `tall` 9:16 (phones). */
8
+ export type PreviewCut = 'wide' | 'tall';
5
9
  /** The gallery cap api-server enforces (`MAX_MEDIA_ITEMS` in game-page-edit-rules.ts). */
6
10
  export declare const MAX_GALLERY_ITEMS = 12;
7
11
  export interface TrailerUploadRequest {
@@ -50,3 +54,38 @@ export declare function mintTrailerUploadUrl(environment: Environment, accessTok
50
54
  * stacking a new one beside it.
51
55
  */
52
56
  export declare function attachTrailer(environment: Environment, accessToken: string, body: TrailerAttachRequest, deps: ApiDeps, hints?: TrailerAttachHints): Promise<TrailerAttachResult>;
57
+ export interface PreviewCutUploadRequest {
58
+ gameId: string;
59
+ /** The trailer the cut belongs to: uploaded by `trailer/upload-url`, attached or not yet. */
60
+ url: string;
61
+ cut: PreviewCut;
62
+ /** Always `video/mp4`: the only type api-server takes for a cut. */
63
+ contentType: string;
64
+ bytes: number;
65
+ }
66
+ /** `cli-trailer.ts`'s preview-upload-url response. */
67
+ export interface PreviewCutUploadTicket {
68
+ uploadUrl: string;
69
+ publicUrl: string;
70
+ path: string;
71
+ contentType: string;
72
+ }
73
+ /**
74
+ * Ask api-server for a signed PUT for one of the trailer's 15-second cuts. A 404 that is not "Game
75
+ * not found" is an api-server from before cuts existed: `unsupported`, which the publish treats as
76
+ * "the page loops the trailer's first 15 seconds", not as a failed publish.
77
+ */
78
+ export declare function mintPreviewCutUploadUrl(environment: Environment, accessToken: string, body: PreviewCutUploadRequest, deps: ApiDeps): Promise<PreviewCutUploadTicket>;
79
+ /** `cli-trailer.ts`'s previews response. */
80
+ export interface RemakePreviewsResult {
81
+ /** Whether the trailer is the page's first video — the one whose previews pages play. */
82
+ hero: boolean;
83
+ pageUrl: string;
84
+ isPublic: boolean;
85
+ published: boolean;
86
+ }
87
+ /** New cuts of a trailer already on the page: have api-server remake its previews from them. */
88
+ export declare function remakePreviews(environment: Environment, accessToken: string, body: {
89
+ gameId: string;
90
+ url: string;
91
+ }, deps: ApiDeps): Promise<RemakePreviewsResult>;
@@ -1,6 +1,8 @@
1
1
  /**
2
- * The two api-server calls behind `bitmagic trailer publish`: mint a signed PUT for the video,
3
- * and attach the uploaded URL to the game page's gallery.
2
+ * The api-server calls behind `bitmagic trailer publish`: mint a signed PUT for the video, attach
3
+ * the uploaded URL to the game page's gallery — and for the trailer's 15-second editions, mint a
4
+ * signed PUT for each cut next to the trailer, and (for `bitmagic trailer previews`) ask for the
5
+ * page's previews to be remade from new cuts.
4
6
  *
5
7
  * Status mapping is this module's own, for the reason `http/signed-upload.ts` gives: `client.ts`'s
6
8
  * `request()` reads every 403 as a Bitmagic Pro refusal, and on these routes a 403 is more often
@@ -15,6 +17,8 @@ import { isJsonObject } from '../project/world-json.js';
15
17
  import { apiServerHeaders } from '../telemetry/request-headers.js';
16
18
  export const TRAILER_UPLOAD_URL_PATH = '/api/cli/v1/trailer/upload-url';
17
19
  export const TRAILER_ATTACH_PATH = '/api/cli/v1/trailer/attach';
20
+ export const TRAILER_PREVIEW_UPLOAD_URL_PATH = '/api/cli/v1/trailer/preview-upload-url';
21
+ export const TRAILER_PREVIEWS_PATH = '/api/cli/v1/trailer/previews';
18
22
  /** The gallery cap api-server enforces (`MAX_MEDIA_ITEMS` in game-page-edit-rules.ts). */
19
23
  export const MAX_GALLERY_ITEMS = 12;
20
24
  async function readJsonBody(response) {
@@ -130,4 +134,52 @@ export async function attachTrailer(environment, accessToken, body, deps, hints
130
134
  published: parsed.published === true,
131
135
  };
132
136
  }
137
+ /**
138
+ * Ask api-server for a signed PUT for one of the trailer's 15-second cuts. A 404 that is not "Game
139
+ * not found" is an api-server from before cuts existed: `unsupported`, which the publish treats as
140
+ * "the page loops the trailer's first 15 seconds", not as a failed publish.
141
+ */
142
+ export async function mintPreviewCutUploadUrl(environment, accessToken, body, deps) {
143
+ const response = await callApi(environment, TRAILER_PREVIEW_UPLOAD_URL_PATH, 'POST', accessToken, body, deps);
144
+ if (response.status === 404) {
145
+ const parsed = await readJsonBody(response.clone());
146
+ if (parsed.error !== 'Game not found') {
147
+ throw new CliError(`api-server in the "${environment.name}" environment does not take preview cuts yet.`, 1, 'unsupported');
148
+ }
149
+ }
150
+ if (!response.ok) {
151
+ throw await refusal(response, environment, TRAILER_PREVIEW_UPLOAD_URL_PATH, body.gameId, `the ${body.cut} preview upload`);
152
+ }
153
+ const parsed = await readJsonBody(response);
154
+ if (typeof parsed.uploadUrl !== 'string' || typeof parsed.publicUrl !== 'string') {
155
+ throw new CliError('api-server minted a preview upload URL this CLI cannot read — is it newer than this CLI?');
156
+ }
157
+ return {
158
+ uploadUrl: parsed.uploadUrl,
159
+ publicUrl: parsed.publicUrl,
160
+ path: typeof parsed.path === 'string' ? parsed.path : '',
161
+ contentType: typeof parsed.contentType === 'string' ? parsed.contentType : body.contentType,
162
+ };
163
+ }
164
+ /** New cuts of a trailer already on the page: have api-server remake its previews from them. */
165
+ export async function remakePreviews(environment, accessToken, body, deps) {
166
+ const response = await callApi(environment, TRAILER_PREVIEWS_PATH, 'PUT', accessToken, body, deps);
167
+ if (response.status === 409) {
168
+ throw new CliError('The trailer this project last published is no longer on the game page. Publish one with '
169
+ + '`bitmagic trailer publish`, which uploads its 15-second edition with it.', 1, 'not_on_page');
170
+ }
171
+ if (!response.ok) {
172
+ throw await refusal(response, environment, TRAILER_PREVIEWS_PATH, body.gameId, 'remaking the previews');
173
+ }
174
+ const parsed = await readJsonBody(response);
175
+ if (typeof parsed.pageUrl !== 'string') {
176
+ throw new CliError('api-server queued the previews but answered in a shape this CLI cannot read — is it newer than this CLI?');
177
+ }
178
+ return {
179
+ hero: parsed.hero === true,
180
+ pageUrl: parsed.pageUrl,
181
+ isPublic: parsed.isPublic === true,
182
+ published: parsed.published === true,
183
+ };
184
+ }
133
185
  //# sourceMappingURL=client.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/trailer/client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/trailer/client.ts"],"names":[],"mappings":"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"}
@@ -11,6 +11,11 @@ export interface TrailerRecord {
11
11
  attached: boolean;
12
12
  /** The game page, when it was attached. */
13
13
  pageUrl?: string;
14
+ /** The 15-second editions uploaded next to it (`wide` 16:9, `tall` 9:16), by CDN URL. */
15
+ previews?: {
16
+ wide?: string;
17
+ tall?: string;
18
+ };
14
19
  }
15
20
  export declare function writeTrailerRecord(root: string, record: TrailerRecord): void;
16
21
  /** The last published trailer, or null when there has not been one this reader can trust. */
@@ -54,6 +54,17 @@ export function readTrailerRecord(root) {
54
54
  at: record.at,
55
55
  attached: record.attached,
56
56
  ...(typeof record.pageUrl === 'string' ? { pageUrl: record.pageUrl } : {}),
57
+ ...readPreviews(record.previews),
57
58
  };
58
59
  }
60
+ function readPreviews(value) {
61
+ if (typeof value !== 'object' || value === null)
62
+ return {};
63
+ const raw = value;
64
+ const previews = {
65
+ ...(typeof raw.wide === 'string' ? { wide: raw.wide } : {}),
66
+ ...(typeof raw.tall === 'string' ? { tall: raw.tall } : {}),
67
+ };
68
+ return Object.keys(previews).length > 0 ? { previews } : {};
69
+ }
59
70
  //# sourceMappingURL=publish-record.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"publish-record.js","sourceRoot":"","sources":["../../src/trailer/publish-record.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAE7B,MAAM,mBAAmB,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,SAAS,EAAE,gBAAgB,CAAC,CAAC;AAEhF,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,mBAAmB,CAAC,CAAC;AAC9C,CAAC;AAgBD,MAAM,UAAU,kBAAkB,CAAC,IAAY,EAAE,MAAqB;IACpE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACzE,EAAE,CAAC,aAAa,CAAC,iBAAiB,CAAC,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;AACpF,CAAC;AAED,6FAA6F;AAC7F,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,iBAAiB,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;IACzE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC/D,MAAM,MAAM,GAAG,MAAiC,CAAC;IACjD,IACE,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ;WAC9B,OAAO,MAAM,CAAC,GAAG,KAAK,QAAQ;WAC9B,OAAO,MAAM,CAAC,WAAW,KAAK,QAAQ;WACtC,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ;WAChC,OAAO,MAAM,CAAC,EAAE,KAAK,QAAQ;WAC7B,OAAO,MAAM,CAAC,QAAQ,KAAK,SAAS,EACvC,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO;QACL,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,GAAG,EAAE,MAAM,CAAC,GAAG;QACf,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,EAAE,EAAE,MAAM,CAAC,EAAE;QACb,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,GAAG,CAAC,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC3E,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"publish-record.js","sourceRoot":"","sources":["../../src/trailer/publish-record.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAE7B,MAAM,mBAAmB,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,SAAS,EAAE,gBAAgB,CAAC,CAAC;AAEhF,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,mBAAmB,CAAC,CAAC;AAC9C,CAAC;AAkBD,MAAM,UAAU,kBAAkB,CAAC,IAAY,EAAE,MAAqB;IACpE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACzE,EAAE,CAAC,aAAa,CAAC,iBAAiB,CAAC,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;AACpF,CAAC;AAED,6FAA6F;AAC7F,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,iBAAiB,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;IACzE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC/D,MAAM,MAAM,GAAG,MAAiC,CAAC;IACjD,IACE,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ;WAC9B,OAAO,MAAM,CAAC,GAAG,KAAK,QAAQ;WAC9B,OAAO,MAAM,CAAC,WAAW,KAAK,QAAQ;WACtC,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ;WAChC,OAAO,MAAM,CAAC,EAAE,KAAK,QAAQ;WAC7B,OAAO,MAAM,CAAC,QAAQ,KAAK,SAAS,EACvC,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO;QACL,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,GAAG,EAAE,MAAM,CAAC,GAAG;QACf,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,EAAE,EAAE,MAAM,CAAC,EAAE;QACb,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,GAAG,CAAC,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC1E,GAAG,YAAY,CAAC,MAAM,CAAC,QAAQ,CAAC;KACjC,CAAC;AACJ,CAAC;AAED,SAAS,YAAY,CAAC,KAAc;IAClC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,EAAE,CAAC;IAC3D,MAAM,GAAG,GAAG,KAAgC,CAAC;IAC7C,MAAM,QAAQ,GAAG;QACf,GAAG,CAAC,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3D,GAAG,CAAC,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC5D,CAAC;IACF,OAAO,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;AAC9D,CAAC"}
@@ -1,6 +1,11 @@
1
1
  import type { Output } from '../output.js';
2
+ import { type PreviewCut } from './client.js';
2
3
  /** api-server's gallery video cap (`POST_MEDIA_VIDEO_MAX_BYTES`), checked here before any request. */
3
4
  export declare const MAX_TRAILER_BYTES: number;
5
+ /** api-server's cap on a 15-second edition (`PREVIEW_CUT_MAX_BYTES`), checked here before any request. */
6
+ export declare const MAX_PREVIEW_CUT_BYTES: number;
7
+ /** The edition `make` renders beside a trailer: `<game>-trailer-r3.mp4` → `<game>-trailer-r3-preview.mp4`. */
8
+ export declare function renderedPreviewFor(trailerFile: string): string;
4
9
  export interface TrailerPublishDeps {
5
10
  fetch: typeof globalThis.fetch;
6
11
  sleep: (ms: number) => Promise<void>;
@@ -16,6 +21,14 @@ export interface TrailerPublishOptions {
16
21
  file: string;
17
22
  /** Add it to the game page gallery after the upload. */
18
23
  attach: boolean;
24
+ /**
25
+ * The 15-second editions to upload with it: `wide` (16:9) and `tall` (9:16), file paths. Without
26
+ * `wide`, the edition `make` rendered beside the trailer goes when it is there. `false`: none.
27
+ */
28
+ previews?: {
29
+ wide?: string;
30
+ tall?: string;
31
+ } | false;
19
32
  output: Output;
20
33
  deps?: TrailerPublishDeps;
21
34
  }
@@ -29,6 +42,11 @@ export interface TrailerPublishResult {
29
42
  isPublic?: boolean;
30
43
  /** The previous trailer this one replaced in the gallery, when there was one. */
31
44
  replacedUrl?: string;
45
+ /** The 15-second editions uploaded with it, by CDN URL. */
46
+ previews?: {
47
+ wide?: string;
48
+ tall?: string;
49
+ };
32
50
  }
33
51
  /**
34
52
  * Does the file start the way its extension promises? An MP4 (and every ISO-BMFF sibling: .m4v,
@@ -42,4 +60,34 @@ export declare function readTrailerFile(file: string): {
42
60
  contentType: string;
43
61
  resolved: string;
44
62
  };
63
+ /** A 15-second edition's bytes, or the refusal: an MP4 under the cut cap. Nothing here touches the network. */
64
+ export declare function readPreviewCutFile(file: string, cut: PreviewCut): {
65
+ bytes: Uint8Array;
66
+ resolved: string;
67
+ };
45
68
  export declare function runTrailerPublish(options: TrailerPublishOptions): Promise<TrailerPublishResult>;
69
+ export interface TrailerPreviewsOptions {
70
+ root: string;
71
+ /** A 16:9 15-second edition (.mp4). */
72
+ wide?: string;
73
+ /** A 9:16 15-second edition (.mp4). */
74
+ tall?: string;
75
+ output: Output;
76
+ deps?: TrailerPublishDeps;
77
+ }
78
+ export interface TrailerPreviewsResult {
79
+ /** The trailer the editions belong to: the one this project last put on the page. */
80
+ trailerUrl: string;
81
+ previews: {
82
+ wide?: string;
83
+ tall?: string;
84
+ };
85
+ /** Whether that trailer is the page's first video, the one whose previews pages play. */
86
+ hero: boolean;
87
+ pageUrl: string;
88
+ }
89
+ /**
90
+ * `bitmagic trailer previews`: new 15-second editions for the trailer already on the page. Uploads
91
+ * them next to it and has api-server remake the page's previews from them.
92
+ */
93
+ export declare function runTrailerPreviews(options: TrailerPreviewsOptions): Promise<TrailerPreviewsResult>;
@@ -11,8 +11,16 @@
11
11
  * upload is accepted and the item is stored; the page simply is not live yet, and the command says
12
12
  * so rather than printing a URL that 404s without explanation.
13
13
  *
14
+ * The trailer's 15-second editions go with it (docs/game-media-previews.md in the Bitmagic repo):
15
+ * a game page loops a 15-second silent preview, 16:9 on a desktop and 9:16 on a phone, and the
16
+ * server cuts it from the edition uploaded next to the trailer, else from the trailer's first 15
17
+ * seconds. `make` renders the 16:9 edition beside the trailer as `<trailer>-preview.mp4`, and that
18
+ * file goes up with the trailer unless another (or none) is named. A cut that cannot be uploaded
19
+ * is a warning, not a failed publish: the page then loops the trailer's first 15 seconds.
20
+ *
14
21
  * Exposed as a function (not only a command) so `bitmagic trailer make --publish` can hand the
15
- * fresh render straight to it.
22
+ * fresh render straight to it. `runTrailerPreviews` is `bitmagic trailer previews`: new editions
23
+ * for the trailer already on the page.
16
24
  */
17
25
  import * as fs from 'fs';
18
26
  import * as path from 'path';
@@ -22,10 +30,16 @@ import { resolveEnvironmentForCommand, resolveAuth0ClientId } from '../config/en
22
30
  import { getAccessToken } from '../auth/session.js';
23
31
  import { putSignedUrl } from '../http/signed-upload.js';
24
32
  import { readPublishRecord } from '../publish/record.js';
25
- import { attachTrailer, mintTrailerUploadUrl } from './client.js';
33
+ import { attachTrailer, mintPreviewCutUploadUrl, mintTrailerUploadUrl, remakePreviews, } from './client.js';
26
34
  import { readTrailerRecord, writeTrailerRecord } from './publish-record.js';
27
35
  /** api-server's gallery video cap (`POST_MEDIA_VIDEO_MAX_BYTES`), checked here before any request. */
28
36
  export const MAX_TRAILER_BYTES = 200 * 1024 * 1024;
37
+ /** api-server's cap on a 15-second edition (`PREVIEW_CUT_MAX_BYTES`), checked here before any request. */
38
+ export const MAX_PREVIEW_CUT_BYTES = 100 * 1024 * 1024;
39
+ /** The edition `make` renders beside a trailer: `<game>-trailer-r3.mp4` → `<game>-trailer-r3-preview.mp4`. */
40
+ export function renderedPreviewFor(trailerFile) {
41
+ return trailerFile.replace(/(\.[^./\\]+)?$/, '-preview.mp4');
42
+ }
29
43
  const CONTENT_TYPES = {
30
44
  '.mp4': 'video/mp4',
31
45
  '.webm': 'video/webm',
@@ -87,11 +101,44 @@ export function readTrailerFile(file) {
87
101
  }
88
102
  return { bytes, contentType, resolved };
89
103
  }
104
+ /** A 15-second edition's bytes, or the refusal: an MP4 under the cut cap. Nothing here touches the network. */
105
+ export function readPreviewCutFile(file, cut) {
106
+ const what = cut === 'wide' ? 'The 16:9 preview' : 'The 9:16 preview';
107
+ if (path.extname(file).toLowerCase() !== '.mp4') {
108
+ throw new CliError(`${what} must be an .mp4 (got ${file}). \`bitmagic trailer make\` renders one beside the trailer.`);
109
+ }
110
+ const { bytes, resolved } = readTrailerFile(file);
111
+ if (bytes.byteLength > MAX_PREVIEW_CUT_BYTES) {
112
+ const mib = (bytes.byteLength / (1024 * 1024)).toFixed(1);
113
+ throw new CliError(`${what} ${resolved} is ${mib} MiB; a 15-second edition may be at most ${MAX_PREVIEW_CUT_BYTES / (1024 * 1024)} MiB.`);
114
+ }
115
+ return { bytes, resolved };
116
+ }
117
+ /** The editions to upload: what was named, else the render's own `<trailer>-preview.mp4`. */
118
+ function resolvePreviewFiles(trailerFile, previews) {
119
+ if (previews === false)
120
+ return {};
121
+ const files = {};
122
+ const wide = previews?.wide ?? renderedPreviewFor(path.resolve(trailerFile));
123
+ if (previews?.wide !== undefined || fs.existsSync(wide))
124
+ files.wide = readPreviewCutFile(wide, 'wide');
125
+ if (previews?.tall !== undefined)
126
+ files.tall = readPreviewCutFile(previews.tall, 'tall');
127
+ return files;
128
+ }
129
+ /** Mint and PUT one edition next to the trailer; its CDN URL. */
130
+ async function uploadPreviewCut(context, cut, file) {
131
+ const ticket = await mintPreviewCutUploadUrl(context.environment, context.accessToken, { gameId: context.gameId, url: context.trailerUrl, cut, contentType: 'video/mp4', bytes: file.bytes.byteLength }, context.deps);
132
+ await putSignedUrl(ticket.uploadUrl, file.bytes, { 'Content-Type': ticket.contentType }, context.deps);
133
+ context.output.info(`${cut === 'wide' ? '16:9' : '9:16'} preview uploaded: ${ticket.publicUrl}`);
134
+ return ticket.publicUrl;
135
+ }
90
136
  export async function runTrailerPublish(options) {
91
137
  const { root, output } = options;
92
138
  const deps = options.deps ?? defaultDeps();
93
139
  // Every local refusal first, before a token is read or a request made.
94
140
  const { bytes, contentType, resolved } = readTrailerFile(options.file);
141
+ const previewFiles = resolvePreviewFiles(options.file, options.previews);
95
142
  const context = loadProjectContext(root);
96
143
  const gameId = context.metadata.gameId;
97
144
  const environment = resolveEnvironmentForCommand({ cwd: root, baseDir: deps.baseDir });
@@ -102,6 +149,28 @@ export async function runTrailerPublish(options) {
102
149
  // only the content type (storage/game-media.ts), and an extra signed-looking header is a GCS 403.
103
150
  await putSignedUrl(ticket.uploadUrl, bytes, { 'Content-Type': ticket.contentType }, deps);
104
151
  output.info(`Trailer uploaded: ${ticket.publicUrl}`);
152
+ // The editions go up before the attach, so the preview job the attach queues finds them. A cut
153
+ // that fails is a warning: the trailer still goes on the page, and the page loops its first
154
+ // 15 seconds until `bitmagic trailer previews` uploads the cut.
155
+ const previews = {};
156
+ const cutContext = { environment, accessToken, gameId, trailerUrl: ticket.publicUrl, deps, output };
157
+ for (const cut of ['wide', 'tall']) {
158
+ const file = previewFiles[cut];
159
+ if (file === undefined)
160
+ continue;
161
+ try {
162
+ previews[cut] = await uploadPreviewCut(cutContext, cut, file);
163
+ }
164
+ catch (error) {
165
+ if (!(error instanceof CliError))
166
+ throw error;
167
+ output.info(`Warning: the ${cut === 'wide' ? '16:9' : '9:16'} preview was not uploaded (${error.message}) — the page loops the `
168
+ + "trailer's first 15 seconds until `bitmagic trailer previews` uploads it.");
169
+ if (error.code === 'unsupported')
170
+ break;
171
+ }
172
+ }
173
+ const previewsPart = Object.keys(previews).length > 0 ? { previews } : {};
105
174
  const now = deps.now ?? Date.now;
106
175
  const previous = readTrailerRecord(root);
107
176
  if (!options.attach) {
@@ -112,9 +181,10 @@ export async function runTrailerPublish(options) {
112
181
  bytes: bytes.byteLength,
113
182
  at: new Date(now()).toISOString(),
114
183
  attached: false,
184
+ ...previewsPart,
115
185
  });
116
186
  output.info('Not attached (--no-attach).');
117
- return { publicUrl: ticket.publicUrl, bytes: bytes.byteLength, contentType, attached: false };
187
+ return { publicUrl: ticket.publicUrl, bytes: bytes.byteLength, contentType, attached: false, ...previewsPart };
118
188
  }
119
189
  // The previous trailer — for THIS game, and only one that was actually put on the page — is what
120
190
  // the new one replaces. An upload-only record is not in the gallery, so there is nothing to swap.
@@ -130,6 +200,7 @@ export async function runTrailerPublish(options) {
130
200
  at: new Date(now()).toISOString(),
131
201
  attached: true,
132
202
  pageUrl: attached.pageUrl,
203
+ ...previewsPart,
133
204
  });
134
205
  output.info(`Attached to the game page: ${attached.pageUrl}`);
135
206
  if (!attached.published || !attached.isPublic) {
@@ -144,6 +215,48 @@ export async function runTrailerPublish(options) {
144
215
  published: attached.published,
145
216
  isPublic: attached.isPublic,
146
217
  ...(replaceUrl !== undefined ? { replacedUrl: replaceUrl } : {}),
218
+ ...previewsPart,
147
219
  };
148
220
  }
221
+ /**
222
+ * `bitmagic trailer previews`: new 15-second editions for the trailer already on the page. Uploads
223
+ * them next to it and has api-server remake the page's previews from them.
224
+ */
225
+ export async function runTrailerPreviews(options) {
226
+ const { root, output } = options;
227
+ const deps = options.deps ?? defaultDeps();
228
+ const files = {};
229
+ if (options.wide !== undefined)
230
+ files.wide = readPreviewCutFile(options.wide, 'wide');
231
+ if (options.tall !== undefined)
232
+ files.tall = readPreviewCutFile(options.tall, 'tall');
233
+ if (files.wide === undefined && files.tall === undefined) {
234
+ throw new CliError('Name an edition to upload: --wide <16:9 .mp4> and/or --tall <9:16 .mp4>.');
235
+ }
236
+ const context = loadProjectContext(root);
237
+ const gameId = context.metadata.gameId;
238
+ const record = readTrailerRecord(root);
239
+ if (record === null || record.gameId !== gameId || !record.attached) {
240
+ throw new CliError('This project has not put a trailer on its game page yet. Publish one with `bitmagic trailer publish` '
241
+ + '(it uploads the 15-second edition `make` rendered beside it).');
242
+ }
243
+ const environment = resolveEnvironmentForCommand({ cwd: root, baseDir: deps.baseDir });
244
+ const accessToken = await getAccessToken(environment, resolveAuth0ClientId(environment), { fetch: deps.fetch, sleep: deps.sleep, now: deps.now }, deps.baseDir);
245
+ const cutContext = { environment, accessToken, gameId, trailerUrl: record.url, deps, output };
246
+ const previews = { ...record.previews };
247
+ for (const cut of ['wide', 'tall']) {
248
+ const file = files[cut];
249
+ if (file !== undefined)
250
+ previews[cut] = await uploadPreviewCut(cutContext, cut, file);
251
+ }
252
+ const remade = await remakePreviews(environment, accessToken, { gameId, url: record.url }, deps);
253
+ writeTrailerRecord(root, { ...record, previews });
254
+ output.info('The page\'s previews are being remade from your editions; they show within a few minutes.');
255
+ output.info('A browser that already played the old ones can keep them for up to a day.');
256
+ if (!remade.hero) {
257
+ output.info('Note: that trailer is not the first video in the game page gallery, so the page loops the first video\'s '
258
+ + 'preview instead. Move the trailer to the front on the My games page.');
259
+ }
260
+ return { trailerUrl: record.url, previews, hero: remade.hero, pageUrl: remade.pageUrl };
261
+ }
149
262
  //# sourceMappingURL=publish.js.map