odori 0.0.1 → 0.0.2

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.
@@ -0,0 +1,464 @@
1
+ import { V as VideoEntry, a as VideoLayout, C as CompiledTimeline, A as AudioTrack } from './manifest-CEuFXG0U.js';
2
+ export { b as Audio, c as AudioCue, d as AudioPolicy, e as AudioProps, f as CompiledScene, g as CreateManifestOptions, D as DUCK_GAIN, h as DUCK_RAMP_FRAMES, E as EnvelopePoint, i as ExportJob, F as Fill, j as Freeze, L as Loop, M as ManifestAsset, k as ManifestAudioCue, l as ManifestFont, m as MotionPolicy, O as OdoriRuntime, n as OdoriRuntimeProps, R as RenderManifest, S as SafeArea, o as SafeAreaPolicy, p as Scene, q as SceneTransition, r as Stagger, s as Video, t as VideoFormat, u as VideoLayoutInput, v as createRenderManifest, w as cueId, x as defaultLayout, y as defineVideoLayout, z as duckEnvelope, B as entryDurationInFrames, G as envelopeAtFrame, H as gainAtFrame, I as isAssetReference, J as resolveEntryLayout, K as sampleGainCurve, N as sortCues, P as trackDuration, Q as trackGainAtFrame, T as useSceneTransition } from './manifest-CEuFXG0U.js';
3
+ import * as react_jsx_runtime from 'react/jsx-runtime';
4
+ import { CSSProperties, RefObject } from 'react';
5
+ import { B as Brand, S as Signal, D as Duration } from './brand-D71KbhAe.js';
6
+ export { a as BrandColors, b as BrandFont, c as BrandInput, d as BrandTypography, C as ChordQuality, e as CueContext, f as CueDefinition, E as Envelope, g as SAMPLE_RATE, h as Step, i as brandCssVariables, j as chord, k as cueSamples, l as cueSignature, m as cueUrl, n as defaultBrand, o as defineBrand, p as defineCue, q as formatTimecode, r as framesFromDuration, s as gain, t as highPass, u as isCueDefinition, v as loop, w as lowPass, x as mix, y as noise, z as normalize, A as note, F as pad, G as seamless, H as seconds, I as secondsFromFrames, J as seeded, K as sequence, L as shape, M as silence, N as sine, O as step, P as sweep, Q as triangle } from './brand-D71KbhAe.js';
7
+ import { P as ParsableSchema } from './schema-DXxRezrk.js';
8
+ export { F as FieldDescriptor, I as InputSchema, d as defineInputSchema, i as isOdoriSchema, p as parseWithSchema } from './schema-DXxRezrk.js';
9
+
10
+ /**
11
+ * Integrated loudness, ITU-R BS.1770-4.
12
+ *
13
+ * The number a mix is judged by is not peak or RMS: it is K-weighted, gated
14
+ * loudness. Studio measures what it is about to hand the encoder so the
15
+ * brand's target is something you can mix toward rather than discover after an
16
+ * export.
17
+ */
18
+ type Biquad = {
19
+ b0: number;
20
+ b1: number;
21
+ b2: number;
22
+ a1: number;
23
+ a2: number;
24
+ };
25
+ /**
26
+ * Channel weights for stereo. Surround weights the surround channels higher;
27
+ * a video mix is stereo, so both channels count equally.
28
+ */
29
+ declare const integratedLufs: (channels: Float32Array[], sampleRate: number) => number | null;
30
+
31
+ type PlayerProps = {
32
+ entry: VideoEntry;
33
+ input?: Record<string, unknown>;
34
+ prepared?: unknown;
35
+ assets?: Array<{
36
+ reference: string;
37
+ url: string;
38
+ }>;
39
+ layout?: VideoLayout;
40
+ initialFrame?: number;
41
+ autoPlay?: boolean;
42
+ loop?: boolean;
43
+ controls?: boolean;
44
+ style?: CSSProperties;
45
+ muted?: boolean;
46
+ onFrame?: (frame: number) => void;
47
+ onTimeline?: (timeline: CompiledTimeline) => void;
48
+ onAudio?: (track: AudioTrack) => void;
49
+ };
50
+ /**
51
+ * The seekable frame clock. Playback advances a fractional frame counter from
52
+ * wall-clock deltas, but React only ever sees an integer frame, so a paused
53
+ * player and a render worker produce identical output.
54
+ */
55
+ declare const Player: ({ entry, input, prepared, assets, layout, initialFrame, autoPlay, loop, controls, muted, style, onFrame, onTimeline, onAudio, }: PlayerProps) => react_jsx_runtime.JSX.Element;
56
+
57
+ type AudioPlaybackOptions = {
58
+ track: AudioTrack | null;
59
+ frame: number;
60
+ fps: number;
61
+ playing: boolean;
62
+ muted?: boolean;
63
+ masterGain?: number;
64
+ /** Cue id to hear alone, or null for the whole mix. */
65
+ soloCue?: string | null;
66
+ /** True while the playhead is being dragged, which auditions under the cursor. */
67
+ scrubbing?: boolean;
68
+ /** Playback rate, so cues stay with the frame clock when it is sped up. */
69
+ rate?: number;
70
+ /**
71
+ * Called when the browser refuses to start a cue without a user gesture, and
72
+ * again when it relents. Autoplay policy is the difference between a silent
73
+ * preview and a broken one, so it is reported rather than swallowed.
74
+ */
75
+ onBlocked?: (blocked: boolean) => void;
76
+ /**
77
+ * Called with the cues whose files failed to load or decode. A cue pointing
78
+ * at a missing or wrong file is silence with no other symptom, so it is
79
+ * reported rather than swallowed.
80
+ */
81
+ onFailed?: (sources: string[]) => void;
82
+ };
83
+ /**
84
+ * Drives one HTMLAudioElement per cue from the frame clock.
85
+ *
86
+ * Audio is the one thing that cannot be derived from a frame index, so preview
87
+ * playback resyncs whenever the element drifts more than a frame from where the
88
+ * timeline says it should be. The exported mix is built separately by the
89
+ * encoder from the same cues, which keeps the file frame accurate.
90
+ */
91
+ declare const useAudioPlayback: ({ track, frame, fps, playing, muted, masterGain, soloCue, scrubbing, rate, onBlocked, onFailed, }: AudioPlaybackOptions) => void;
92
+
93
+ type PlaybackOptions = {
94
+ fps: number;
95
+ durationInFrames: number;
96
+ initialFrame?: number;
97
+ autoPlay?: boolean;
98
+ loop?: boolean;
99
+ /** Wall-clock multiplier. The frame clock keeps its rate; only time moves. */
100
+ rate?: number;
101
+ onFrame?: (frame: number) => void;
102
+ };
103
+ type Playback = {
104
+ frame: number;
105
+ playing: boolean;
106
+ rate: number;
107
+ play(): void;
108
+ pause(): void;
109
+ toggle(): void;
110
+ seek(frame: number): void;
111
+ step(delta: number): void;
112
+ restart(): void;
113
+ };
114
+ /**
115
+ * The seekable frame clock. Playback advances a fractional counter from
116
+ * wall-clock deltas, but React only ever sees an integer frame, so a paused
117
+ * player, a still, and the export worker agree by construction.
118
+ */
119
+ /**
120
+ * How far the clock moves for a wall-clock delta.
121
+ *
122
+ * Rate scales elapsed time, never the frame index, so frame 90 is the same
123
+ * image at 0.25x, 1x, and 4x, and an export ignores rate entirely.
124
+ */
125
+ declare const advanceFrames: (fractional: number, deltaMs: number, fps: number, rate?: number) => number;
126
+ declare const usePlayback: ({ fps, durationInFrames, initialFrame, autoPlay, loop, rate, onFrame, }: PlaybackOptions) => Playback;
127
+
128
+ declare global {
129
+ interface Window {
130
+ __ODORI_SET_FRAME__?: (frame: number) => void;
131
+ __ODORI_TIMELINE__?: CompiledTimeline;
132
+ __ODORI_AUDIO__?: AudioTrack;
133
+ __ODORI_READY__?: boolean;
134
+ }
135
+ }
136
+ /**
137
+ * The surface the render worker drives. It exposes an explicit frame setter
138
+ * and a readiness handshake instead of relying on timing heuristics.
139
+ */
140
+ declare const RenderSurface: ({ entry, initialFrame, input, prepared, assets, layout, }: {
141
+ entry: VideoEntry;
142
+ initialFrame?: number;
143
+ input?: Record<string, unknown>;
144
+ prepared?: unknown;
145
+ assets?: Array<{
146
+ reference: string;
147
+ url: string;
148
+ }>;
149
+ layout?: VideoLayout;
150
+ }) => react_jsx_runtime.JSX.Element;
151
+
152
+ type FrameState = {
153
+ frame: number;
154
+ fps: number;
155
+ width: number;
156
+ height: number;
157
+ durationInFrames: number;
158
+ };
159
+ type SceneState = {
160
+ id: string;
161
+ name?: string;
162
+ index: number;
163
+ start: number;
164
+ durationInFrames: number;
165
+ };
166
+ type AssetRegistry = {
167
+ resolve(reference: string): string;
168
+ list(): Array<{
169
+ reference: string;
170
+ url: string;
171
+ }>;
172
+ ready: boolean;
173
+ };
174
+ type Readiness = {
175
+ /** Hold the frame open. Call the returned function when the work is done. */
176
+ hold(): () => void;
177
+ };
178
+ /**
179
+ * Block the frame until asynchronous work finishes.
180
+ *
181
+ * The driver waits for the frame attribute, and the runtime only writes it once
182
+ * every held handle is released. Without this a component that decodes an image
183
+ * would be screenshotted before it had anything to show.
184
+ */
185
+ declare const useReadiness: () => Readiness;
186
+ /** The frame index local to the nearest scene, or the video frame at the top level. */
187
+ declare const useFrame: () => number;
188
+ declare const useVideo: () => Omit<FrameState, "frame">;
189
+ /**
190
+ * A single number components multiply their design values by.
191
+ *
192
+ * Sizing from the shorter side keeps type and spacing readable when the same
193
+ * component is composed into a 16:9, 9:16, or 1:1 frame. A width-derived scale
194
+ * would shrink a vertical cut into unreadable text.
195
+ */
196
+ declare const useDesignScale: (reference?: number) => number;
197
+ declare const useBrand: () => Brand;
198
+ declare const useLayout: () => VideoLayout;
199
+ declare const useScene: () => SceneState;
200
+ declare const useAssets: () => AssetRegistry;
201
+ declare const createAssetRegistry: (entries: Array<{
202
+ reference: string;
203
+ url: string;
204
+ }>) => AssetRegistry;
205
+
206
+ /**
207
+ * Signal to a 16 bit PCM WAV. Small, lossless, and readable by FFmpeg without
208
+ * a decoder, which is all the mix needs from a generated cue.
209
+ *
210
+ * Encoding lives in the runtime rather than the CLI so preview and export
211
+ * share it: the browser can hand the same bytes to an AudioContext that the
212
+ * render worker writes to disk.
213
+ */
214
+ declare const encodeWav: (signal: Signal) => Uint8Array;
215
+
216
+ type EasingFunction = (value: number) => number;
217
+ declare const Easing: {
218
+ linear: (value: number) => number;
219
+ bezier: (x1: number, y1: number, x2: number, y2: number) => EasingFunction;
220
+ standard: EasingFunction;
221
+ quad: (value: number) => number;
222
+ cubic: (value: number) => number;
223
+ in: (easing: EasingFunction) => EasingFunction;
224
+ out: (easing: EasingFunction) => EasingFunction;
225
+ inOut: (easing: EasingFunction) => EasingFunction;
226
+ };
227
+ type InterpolateOptions = {
228
+ easing?: EasingFunction;
229
+ extrapolateLeft?: "extend" | "clamp";
230
+ extrapolateRight?: "extend" | "clamp";
231
+ };
232
+ declare function interpolate(value: number, input: number[], output: number[], options?: InterpolateOptions): number;
233
+ declare function interpolate(value: number, input: number[], output: string[], options?: InterpolateOptions): string;
234
+ type SpringOptions = {
235
+ frame: number;
236
+ fps: number;
237
+ from?: number;
238
+ to?: number;
239
+ stiffness?: number;
240
+ damping?: number;
241
+ mass?: number;
242
+ delayInFrames?: number;
243
+ };
244
+ /**
245
+ * A deterministic damped-spring solve. The value depends only on the frame
246
+ * index, so preview and render always agree.
247
+ */
248
+ declare const spring: ({ frame, fps, from, to, stiffness, damping, mass, delayInFrames, }: SpringOptions) => number;
249
+
250
+ type CursorStop = {
251
+ /** Frame this stop is reached, from the start of the enclosing scene. */
252
+ frame: number;
253
+ /** Canvas coordinates, in the composition's own pixels. */
254
+ x: number;
255
+ y: number;
256
+ /**
257
+ * A click landing on this stop. The press is drawn at the stop's frame and
258
+ * decays over a few frames, so the pointer visibly does the thing the UI is
259
+ * about to react to.
260
+ */
261
+ click?: boolean;
262
+ /** Hold here until this many frames have passed before moving on. */
263
+ hold?: number;
264
+ };
265
+ type CursorState = {
266
+ x: number;
267
+ y: number;
268
+ /** 0 before the path starts and after it ends, 1 while it is on screen. */
269
+ visible: number;
270
+ /** 1 at the instant of a click, decaying to 0. Drives the press ring. */
271
+ pressed: number;
272
+ /** True while a click is within its press window, for a UI to react to. */
273
+ clicking: boolean;
274
+ };
275
+ /**
276
+ * Where an authored pointer is at this frame.
277
+ *
278
+ * Recording a real cursor would make a video that cannot be re-rendered: the
279
+ * path would live in a file, not in the composition, and a change of copy or
280
+ * canvas would leave it pointing at nothing. An authored path is source — it
281
+ * diffs, it survives a reflow, and it produces the same pixels every run.
282
+ *
283
+ * Movement eases between stops rather than running linearly, because a pointer
284
+ * that travels at constant speed reads as a machine. A `hold` keeps the
285
+ * pointer still without needing a duplicate stop at the same coordinates.
286
+ */
287
+ declare const cursorAt: (stops: CursorStop[], frame: number) => CursorState;
288
+ /** The last frame an authored path is still on screen, for sizing a scene. */
289
+ declare const cursorDuration: (stops: CursorStop[]) => number;
290
+
291
+ /**
292
+ * Randomness that survives a re-render.
293
+ *
294
+ * A frame is a pure function of its number. `Math.random()` breaks that in the
295
+ * quietest possible way: the preview looks fine, every export looks fine, and
296
+ * the two are different — and so are two chunks of the same export, because a
297
+ * render is parallel and each worker rolls its own numbers. Fifty particles
298
+ * that jump between chunk boundaries is the usual symptom, found late.
299
+ *
300
+ * So a composition asks for a number by name instead. The same seed always
301
+ * gives the same value, on every machine and in every worker, and a seed that
302
+ * includes the frame gives motion that is random-looking and reproducible.
303
+ */
304
+ /**
305
+ * A number in `[0, 1)` for a seed. The same seed always returns the same
306
+ * number, which is the whole point.
307
+ *
308
+ * ```tsx
309
+ * const drift = random(`particle-${index}`) * 40;
310
+ * const jitter = random([frame, index]) - 0.5;
311
+ * ```
312
+ *
313
+ * An array seed is joined, which is the convenient way to say "this thing, on
314
+ * this frame" without building the string by hand.
315
+ */
316
+ declare const random: (seed: number | string | Array<number | string>) => number;
317
+ /** A number in `[min, max)`, for a seed. */
318
+ declare const randomBetween: (seed: number | string | Array<number | string>, min: number, max: number) => number;
319
+ /** One item from a list, for a seed. Empty lists return undefined. */
320
+ declare const randomPick: <T>(seed: number | string | Array<number | string>, items: readonly T[]) => T | undefined;
321
+ /**
322
+ * A shuffled copy, for a seed. Fisher-Yates driven by the same generator, so
323
+ * the order is arbitrary but fixed — a list that reshuffles every frame is an
324
+ * animation nobody asked for.
325
+ */
326
+ declare const randomOrder: <T>(seed: number | string | Array<number | string>, items: readonly T[]) => T[];
327
+
328
+ type CanvasDraw = (context: CanvasRenderingContext2D, state: {
329
+ frame: number;
330
+ width: number;
331
+ height: number;
332
+ }) => void;
333
+ /**
334
+ * Draw to a canvas from the frame clock.
335
+ *
336
+ * The contract a video runs on is that frame N produces the same pixels every
337
+ * time. A canvas is where that is easiest to lose: the obvious way to animate
338
+ * one is `requestAnimationFrame`, which is wall time, and wall time means the
339
+ * export samples wherever the loop happened to be. Two workers rendering
340
+ * neighbouring chunks then disagree, and the seam shows.
341
+ *
342
+ * So the draw is a pure function of the frame, called synchronously before the
343
+ * browser paints, and the frame is held until it has run. The capture waits on
344
+ * the same readiness handshake an image decode uses, which is what makes the
345
+ * screenshot see finished pixels rather than an empty buffer.
346
+ */
347
+ declare const useCanvas: (draw: CanvasDraw, dependencies?: readonly unknown[]) => RefObject<HTMLCanvasElement | null>;
348
+ /**
349
+ * Rasterize HTML into a canvas, deterministically.
350
+ *
351
+ * The browser will draw an SVG containing a `foreignObject` onto a canvas, and
352
+ * a `foreignObject` can hold ordinary markup. That is the whole trick, and the
353
+ * reason it needs care: the image decode is asynchronous, so the frame has to
354
+ * be held until it lands, and the markup has to carry its own styles because
355
+ * nothing outside the SVG reaches into it.
356
+ *
357
+ * Fonts are the sharp edge. A face that is not loaded when this runs will fall
358
+ * back, and the fallback is what gets baked into the pixels — which is why the
359
+ * caller waits on `document.fonts.ready` before drawing.
360
+ */
361
+ declare const drawHtml: (context: CanvasRenderingContext2D, html: string, options: {
362
+ width: number;
363
+ height: number;
364
+ style?: string;
365
+ }) => Promise<void>;
366
+
367
+ type PlaceholderOptions = {
368
+ width?: number;
369
+ height?: number;
370
+ /** Drawn across the middle, so a fixture says what it is standing in for. */
371
+ label?: string;
372
+ /** Two colours the gradient runs between. */
373
+ from?: string;
374
+ to?: string;
375
+ /** Seed for the scatter, so two placeholders differ without differing runs. */
376
+ seed?: string;
377
+ };
378
+ /**
379
+ * A picture that ships as code.
380
+ *
381
+ * A component that shows media needs media to show, and a fixture that ships a
382
+ * JPEG cannot be reviewed in a diff, cannot be recoloured by a brand, and adds
383
+ * a binary to a repository forever. Generating an SVG instead keeps the
384
+ * registry's rule intact — install copies source — and makes the picture do
385
+ * something a file cannot: describe itself.
386
+ *
387
+ * It is deliberately obviously a placeholder. A fixture that looks like real
388
+ * photography invites someone to ship it.
389
+ */
390
+ declare const placeholderSvg: ({ width, height, label, from, to, seed, }?: PlaceholderOptions) => string;
391
+ /**
392
+ * The same picture as a data URL, which is what an `<img>` or a canvas draw
393
+ * wants. Inline rather than fetched: a fixture that needs the network is a
394
+ * fixture that fails on a plane, in CI, and in a sandboxed render.
395
+ */
396
+ declare const placeholderImage: (options?: PlaceholderOptions) => string;
397
+ /**
398
+ * A frame of a placeholder "clip": the same picture with a moving marker and a
399
+ * timecode, so a component that plays media has something to play that visibly
400
+ * advances and is still a pure function of the frame.
401
+ */
402
+ declare const placeholderFrame: (frame: number, options?: PlaceholderOptions & {
403
+ fps?: number;
404
+ }) => string;
405
+
406
+ type VideoMetadata<Input = Record<string, unknown>> = {
407
+ readonly kind: "odori-video-metadata";
408
+ id: string;
409
+ title: string;
410
+ description?: string;
411
+ duration?: Duration;
412
+ layout?: VideoLayout;
413
+ schema?: ParsableSchema<Input>;
414
+ defaultProps?: Partial<Input>;
415
+ tags?: string[];
416
+ thumbnailFrame?: number;
417
+ };
418
+ type VideoMetadataInput<Input = Record<string, unknown>> = Omit<VideoMetadata<Input>, "kind" | "id"> & {
419
+ /**
420
+ * Defaults to the entry's path under `videos/`, so the directory names a
421
+ * video the way a route names a page. Set it to keep an id stable across a
422
+ * directory move.
423
+ */
424
+ id?: string;
425
+ };
426
+ declare const isValidVideoId: (id: string) => boolean;
427
+ /**
428
+ * An id left unset is resolved from the filesystem by discovery. The empty
429
+ * string is the unresolved state: no entry reaches a manifest, a render, or
430
+ * Studio without an id stamped in.
431
+ */
432
+ declare const resolveVideoId: (id: string | undefined, pathId: string) => string;
433
+ declare const defineVideoMetadata: <Input = Record<string, unknown>>(metadata: VideoMetadataInput<Input>) => VideoMetadata<Input>;
434
+ type PrepareContext<Input> = {
435
+ input: Input;
436
+ assets: {
437
+ resolve(reference: string): Promise<string>;
438
+ };
439
+ cache: {
440
+ getOrSet<Value>(key: string, factory: () => Promise<Value>): Promise<Value>;
441
+ };
442
+ signal?: AbortSignal;
443
+ };
444
+ type PrepareFunction<Input = Record<string, unknown>, Prepared = unknown> = {
445
+ readonly kind: "odori-prepare";
446
+ version: string;
447
+ run(context: PrepareContext<Input>): Promise<Prepared>;
448
+ };
449
+ declare const definePrepare: <Input = Record<string, unknown>, Prepared = unknown>(run: (context: PrepareContext<Input>) => Promise<Prepared>, options?: {
450
+ version?: string;
451
+ }) => PrepareFunction<Input, Prepared>;
452
+
453
+ /**
454
+ * Deterministic, dependency-free content hash.
455
+ *
456
+ * The renderer only needs stable identity for cache keys and frozen manifests,
457
+ * so a 128-bit FNV-1a variant over canonical JSON is sufficient and runs
458
+ * identically in Node and the browser.
459
+ */
460
+ declare const canonicalJson: (value: unknown) => string;
461
+ declare const hashString: (input: string) => string;
462
+ declare const hashValue: (value: unknown) => string;
463
+
464
+ export { type AssetRegistry, type AudioPlaybackOptions, AudioTrack, type Biquad, Brand, type CanvasDraw, CompiledTimeline, type CursorState, type CursorStop, Duration, Easing, type EasingFunction, type FrameState, type InterpolateOptions, ParsableSchema, type PlaceholderOptions, type Playback, type PlaybackOptions, Player, type PlayerProps, type PrepareContext, type PrepareFunction, type Readiness, RenderSurface, type SceneState, Signal, type SpringOptions, VideoEntry, VideoLayout, type VideoMetadata, type VideoMetadataInput, advanceFrames, canonicalJson, createAssetRegistry, cursorAt, cursorDuration, definePrepare, defineVideoMetadata, drawHtml, encodeWav, hashString, hashValue, integratedLufs, interpolate, isValidVideoId, placeholderFrame, placeholderImage, placeholderSvg, random, randomBetween, randomOrder, randomPick, resolveVideoId, spring, useAssets, useAudioPlayback, useBrand, useCanvas, useDesignScale, useFrame, useLayout, usePlayback, useReadiness, useScene, useVideo };