@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.
- package/README.md +14 -2
- package/assets/blender-level/bmlevel/__init__.py +14 -4
- package/assets/blender-level/bmlevel/core.py +62 -0
- package/assets/blender-level/bmlevel/scene_bpy.py +14 -1
- package/assets/blender-level/bmlevel/scene_null.py +3 -0
- package/assets/blender-level/tests/test_core.py +23 -0
- package/assets/skills/joining-the-game-exchange/SKILL.md +7 -5
- package/dist/commands/trailer-previews.d.ts +14 -0
- package/dist/commands/trailer-previews.js +45 -0
- package/dist/commands/trailer-previews.js.map +1 -0
- package/dist/commands/trailer-publish.d.ts +8 -0
- package/dist/commands/trailer-publish.js +17 -0
- package/dist/commands/trailer-publish.js.map +1 -1
- package/dist/commands/trailer.js +19 -4
- package/dist/commands/trailer.js.map +1 -1
- package/dist/package/collect-urls.d.ts +2 -2
- package/dist/package/collect-urls.js +2 -2
- package/dist/scaffold/blender-level-skill-files.js +17 -11
- package/dist/scaffold/blender-level-skill-files.js.map +1 -1
- package/dist/scaffold/project-files.js +1 -1
- package/dist/scaffold/skill-files.js +36 -3
- package/dist/scaffold/skill-files.js.map +1 -1
- package/dist/trailer/client.d.ts +39 -0
- package/dist/trailer/client.js +54 -2
- package/dist/trailer/client.js.map +1 -1
- package/dist/trailer/publish-record.d.ts +5 -0
- package/dist/trailer/publish-record.js +11 -0
- package/dist/trailer/publish-record.js.map +1 -1
- package/dist/trailer/publish.d.ts +48 -0
- package/dist/trailer/publish.js +116 -3
- package/dist/trailer/publish.js.map +1 -1
- 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
|
|
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
|
-
|
|
|
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.
|
|
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
|
|
190
|
-
|
|
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
|
|
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).
|
|
368
|
-
|
|
369
|
-
|
|
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
|
|
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,
|
|
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),
|
|
202
|
-
\`bitmagic trailer publish <mp4>\`) uploads
|
|
203
|
-
|
|
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
|
|
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"}
|
package/dist/trailer/client.d.ts
CHANGED
|
@@ -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>;
|
package/dist/trailer/client.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The
|
|
3
|
-
*
|
|
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":"AAAA
|
|
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;
|
|
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>;
|
package/dist/trailer/publish.js
CHANGED
|
@@ -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
|