@nodaro/shared 3.6.0 → 3.8.0
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/dist/index.cjs +265 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +167 -9
- package/dist/index.d.ts +167 -9
- package/dist/index.js +254 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/normalize-model-input.test.ts +32 -0
- package/src/__tests__/scene3d-render-pricing.test.ts +157 -0
- package/src/__tests__/scene3d-v2.test.ts +4 -1
- package/src/image-overlay-layers.ts +10 -0
- package/src/index.ts +3 -0
- package/src/model-catalog.ts +106 -0
- package/src/model-constants.ts +30 -0
- package/src/node-default-mappings.ts +4 -0
- package/src/pipeline-types.ts +2 -0
- package/src/pro-3d-render.ts +55 -0
- package/src/reference-board-templates.ts +6 -1
- package/src/scene3d-render-pricing.ts +145 -0
- package/src/scene3d-v2.ts +3 -1
- package/src/scene3d.ts +19 -1
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a Scene3D render COSTS as a function of FRAME SIZE.
|
|
3
|
+
*
|
|
4
|
+
* A render's work is per pixel per frame, and until the 2560 px cap landed
|
|
5
|
+
* every admissible frame was small enough that one flat price was honest. It
|
|
6
|
+
* no longer is: on the software GL path a container actually uses, one frame
|
|
7
|
+
* costs ~65 ms at 1920x1080, ~97 ms at 2560x1440 and ~154 ms at 2560x2560, and
|
|
8
|
+
* peak memory roughly doubles across that range. A single price for all three
|
|
9
|
+
* is either an overcharge on the small frame everyone renders or a giveaway on
|
|
10
|
+
* the large one almost nobody does.
|
|
11
|
+
*
|
|
12
|
+
* So the price is TIERED by frame size, and the tiers are the frames that were
|
|
13
|
+
* measured — not invented brackets:
|
|
14
|
+
*
|
|
15
|
+
* | Tier | Frame | Price |
|
|
16
|
+
* |----------|------------------------------------|--------------|
|
|
17
|
+
* | `base` | longest side <= 1920 px | 1x |
|
|
18
|
+
* | `large` | longer than 1920 px, <= 5.12 MP | 1.5x |
|
|
19
|
+
* | `xlarge` | longer than 1920 px, over 5.12 MP | 2.5x |
|
|
20
|
+
*
|
|
21
|
+
* Two rules make that table decidable for any frame, in this order:
|
|
22
|
+
*
|
|
23
|
+
* 1. **The 1920 gate.** A frame whose LONGEST side is at most 1920 px is
|
|
24
|
+
* `base`, whatever its area. This is a promise, not an optimisation:
|
|
25
|
+
* every frame that was renderable before the cap moved keeps exactly the
|
|
26
|
+
* price it had, so raising the ceiling cannot make an existing workflow
|
|
27
|
+
* more expensive.
|
|
28
|
+
* 2. **Nearest measured reference, by pixel area.** Above the gate a frame is
|
|
29
|
+
* priced at whichever measured frame it is closer to in PIXEL COUNT, and
|
|
30
|
+
* `SCENE3D_RENDER_XLARGE_MIN_AREA_PX` is exactly the midpoint between
|
|
31
|
+
* them. 2560x1440 (3.69 MP) and 2560x1097 are `large`; 2048x2560 (5.24 MP)
|
|
32
|
+
* and 2560x2560 (6.55 MP) are `xlarge`.
|
|
33
|
+
*
|
|
34
|
+
* The multipliers are the measured cost ratios rounded to halves (1.49 -> 1.5,
|
|
35
|
+
* 2.37 -> 2.5), which is why they are stated as multipliers and not as three
|
|
36
|
+
* unrelated numbers: an operator who reprices the base render keeps the whole
|
|
37
|
+
* ladder consistent, and `scene3DRenderTierCredits` is the one place the
|
|
38
|
+
* arithmetic happens.
|
|
39
|
+
*
|
|
40
|
+
* This module carries the SHAPE of the price (which tier, which identifier,
|
|
41
|
+
* which multiplier). It carries no rates: what a tier actually costs is a
|
|
42
|
+
* `model_pricing` row, so a deployment can price its own hardware.
|
|
43
|
+
*/
|
|
44
|
+
import { PRO3D_RENDER_CREDIT_ID } from "./pro-3d-render.js"
|
|
45
|
+
import { SCENE3D_PLAN_TYPE } from "./scene3d.js"
|
|
46
|
+
|
|
47
|
+
/** Cheapest first. A vocabulary, so a surface can render the whole ladder. */
|
|
48
|
+
export const SCENE3D_RENDER_TIERS = ["base", "large", "xlarge"] as const
|
|
49
|
+
export type Scene3DRenderTier = (typeof SCENE3D_RENDER_TIERS)[number]
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The longest side (px) that still renders at the base price.
|
|
53
|
+
*
|
|
54
|
+
* Deliberately NOT `SCENE3D_LIMITS.maxDimensionPx` minus something: it is the
|
|
55
|
+
* old ceiling, frozen, because its job is to hold every pre-cap frame at the
|
|
56
|
+
* price it already had. Raising the cap again moves `maxDimensionPx` and must
|
|
57
|
+
* leave this alone.
|
|
58
|
+
*/
|
|
59
|
+
export const SCENE3D_RENDER_BASE_MAX_PX = 1920
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Pixel area at which a frame stops being priced as 2560x1440 and starts being
|
|
63
|
+
* priced as 2560x2560 — the exact midpoint of those two measured frames
|
|
64
|
+
* (3,686,400 and 6,553,600). Only consulted for frames past the 1920 gate.
|
|
65
|
+
*/
|
|
66
|
+
export const SCENE3D_RENDER_XLARGE_MIN_AREA_PX = 5_120_000
|
|
67
|
+
|
|
68
|
+
/** The measured cost ratios, rounded to halves. `base` is 1 by definition. */
|
|
69
|
+
export const SCENE3D_RENDER_TIER_MULTIPLIERS: Readonly<Record<Scene3DRenderTier, number>> = {
|
|
70
|
+
base: 1,
|
|
71
|
+
large: 1.5,
|
|
72
|
+
xlarge: 2.5,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Which tier a frame renders at.
|
|
77
|
+
*
|
|
78
|
+
* Total and defensive on purpose: it is read from a route guard, an
|
|
79
|
+
* orchestrator payload builder and a canvas badge, all of which hold a plan
|
|
80
|
+
* that has not necessarily been parsed yet. Anything that is not a pair of
|
|
81
|
+
* positive finite numbers is `base` — the safe answer for a display, and
|
|
82
|
+
* harmless for a charge because the route's Zod refuses such a plan anyway.
|
|
83
|
+
*/
|
|
84
|
+
export function scene3DRenderTier(width: unknown, height: unknown): Scene3DRenderTier {
|
|
85
|
+
const w = typeof width === "number" && Number.isFinite(width) && width > 0 ? width : 0
|
|
86
|
+
const h = typeof height === "number" && Number.isFinite(height) && height > 0 ? height : 0
|
|
87
|
+
if (w === 0 || h === 0) return "base"
|
|
88
|
+
if (Math.max(w, h) <= SCENE3D_RENDER_BASE_MAX_PX) return "base"
|
|
89
|
+
return w * h > SCENE3D_RENDER_XLARGE_MIN_AREA_PX ? "xlarge" : "large"
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* A tier's price, from the base render's price.
|
|
94
|
+
*
|
|
95
|
+
* `Math.ceil` matches how the platform rounds every other derived credit
|
|
96
|
+
* figure, so a tier can never round DOWN into charging less than the base
|
|
97
|
+
* render it is a multiple of.
|
|
98
|
+
*/
|
|
99
|
+
export function scene3DRenderTierCredits(baseCredits: number, tier: Scene3DRenderTier): number {
|
|
100
|
+
return Math.ceil(baseCredits * SCENE3D_RENDER_TIER_MULTIPLIERS[tier])
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** The flat identifier a render settles under when its frame is base-sized. */
|
|
104
|
+
export const RENDER_VIDEO_CREDIT_ID = "render-video"
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* The identifier one render-video request settles under.
|
|
108
|
+
*
|
|
109
|
+
* `base` keeps the BARE `render-video` id rather than gaining a `:base`
|
|
110
|
+
* suffix. That is what makes this change free of a price move: an existing
|
|
111
|
+
* deployment's configured `render-video` row keeps pricing every frame it
|
|
112
|
+
* priced before, and the two new rows only ever describe frames that were not
|
|
113
|
+
* renderable at all until the cap moved.
|
|
114
|
+
*
|
|
115
|
+
* Only `3d-scene` plans are tiered. A scene-graph or template render admits
|
|
116
|
+
* frames up to 3840 px today at the flat price, and re-tiering those would be
|
|
117
|
+
* a price INCREASE on work people already run — a separate decision, not a
|
|
118
|
+
* side effect of this one.
|
|
119
|
+
*/
|
|
120
|
+
export function renderVideoCreditId(input: unknown): string {
|
|
121
|
+
if (!input || typeof input !== "object") return RENDER_VIDEO_CREDIT_ID
|
|
122
|
+
const body = input as { planType?: unknown; plan?: unknown }
|
|
123
|
+
if (body.planType !== SCENE3D_PLAN_TYPE) return RENDER_VIDEO_CREDIT_ID
|
|
124
|
+
if (!body.plan || typeof body.plan !== "object") return RENDER_VIDEO_CREDIT_ID
|
|
125
|
+
const plan = body.plan as { width?: unknown; height?: unknown }
|
|
126
|
+
const tier = scene3DRenderTier(plan.width, plan.height)
|
|
127
|
+
return tier === "base" ? RENDER_VIDEO_CREDIT_ID : `${RENDER_VIDEO_CREDIT_ID}:3d-${tier}`
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* The per-frame unit a 3D Render Pro run's render stage reads.
|
|
132
|
+
*
|
|
133
|
+
* Pro prices its render per OUTPUT FRAME, so the frame-size tier belongs on
|
|
134
|
+
* that unit rather than on a whole-run identifier — the same ladder, applied
|
|
135
|
+
* where Pro actually multiplies. `base` keeps the existing
|
|
136
|
+
* `pro-3d-render:render-frame:<quality>` spelling so the rows an operator has
|
|
137
|
+
* already configured keep serving every frame they serve today.
|
|
138
|
+
*
|
|
139
|
+
* The rates themselves are deployment configuration and live nowhere in this
|
|
140
|
+
* repository; this function only says which row to read.
|
|
141
|
+
*/
|
|
142
|
+
export function pro3DRenderFrameUnit(quality: string, tier: Scene3DRenderTier = "base"): string {
|
|
143
|
+
const unit = `${PRO3D_RENDER_CREDIT_ID}:render-frame:${quality}`
|
|
144
|
+
return tier === "base" ? unit : `${unit}:${tier}`
|
|
145
|
+
}
|
package/src/scene3d-v2.ts
CHANGED
|
@@ -91,7 +91,9 @@ export const SCENE3D_V2_LIMITS = {
|
|
|
91
91
|
defaultFps: 24,
|
|
92
92
|
/** Even integers only — an odd axis breaks H.264 chroma subsampling. */
|
|
93
93
|
minDimensionPx: 100,
|
|
94
|
-
maxDimensionPx
|
|
94
|
+
/** Deliberately kept equal to v1's `SCENE3D_LIMITS.maxDimensionPx`: one
|
|
95
|
+
* renderer draws both versions, so one measured ceiling bounds both. */
|
|
96
|
+
maxDimensionPx: SCENE3D_LIMITS.maxDimensionPx,
|
|
95
97
|
minEntities: 1,
|
|
96
98
|
/** SEMANTIC entities, not exported mesh nodes. */
|
|
97
99
|
maxEntities: 100,
|
package/src/scene3d.ts
CHANGED
|
@@ -33,6 +33,15 @@
|
|
|
33
33
|
* Sampling itself lives with the renderer (`packages/remotion`) — this file
|
|
34
34
|
* only guarantees the data it samples is well-formed.
|
|
35
35
|
*
|
|
36
|
+
* Camera and object RIGS are deliberately absent, and their absence is a
|
|
37
|
+
* decision rather than an unfinished TODO: spline rails, follow-path and
|
|
38
|
+
* track-to constraints, and procedural noise modifiers are authored UPSTREAM
|
|
39
|
+
* (in Blender) and reach this contract already BAKED — v1 as keyframes on the
|
|
40
|
+
* tracks above, v2 as one camera sample per frame. The format carries no
|
|
41
|
+
* constraint or noise vocabulary ON PURPOSE, because evaluating a rig in two
|
|
42
|
+
* different renderers cannot be guaranteed to agree frame for frame, and that
|
|
43
|
+
* agreement is the promise everything else here rests on.
|
|
44
|
+
*
|
|
36
45
|
* ## Revisions
|
|
37
46
|
*
|
|
38
47
|
* A plan is IMMUTABLE. Every accepted edit produces a NEW `revisionId` and
|
|
@@ -59,7 +68,16 @@ export const SCENE3D_DEFAULT_DURATION_SECONDS = 4
|
|
|
59
68
|
*/
|
|
60
69
|
export const SCENE3D_LIMITS = {
|
|
61
70
|
minDimensionPx: 100,
|
|
62
|
-
|
|
71
|
+
/** Applies to BOTH axes, so the worst admissible frame is SQUARE, not merely
|
|
72
|
+
* wider — 2560x2560 is 3.2x the pixels of 1920x1080, and that is the frame
|
|
73
|
+
* this bound was measured at. Raised from 1920 once that cost was measured
|
|
74
|
+
* rather than assumed: on the software GL path a container actually uses,
|
|
75
|
+
* an animated 2560x2560 frame costs ~154 ms/frame against ~65 ms at
|
|
76
|
+
* 1920x1080, so even `maxDurationInFrames` (3600) lands near 9 min against
|
|
77
|
+
* the render worker's 25-minute budget. Peak memory is the real price —
|
|
78
|
+
* ~3.1 GB across the browser process tree versus ~1.6 GB — and is the number
|
|
79
|
+
* to re-measure before widening this again. */
|
|
80
|
+
maxDimensionPx: 2560,
|
|
63
81
|
minFps: 15,
|
|
64
82
|
maxFps: 60,
|
|
65
83
|
minDurationInFrames: 1,
|