@remotion/web-renderer 4.0.374 → 4.0.376

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 (46) hide show
  1. package/dist/artifact.d.ts +24 -0
  2. package/dist/artifact.js +59 -0
  3. package/dist/calculate-transforms.d.ts +9 -0
  4. package/dist/calculate-transforms.js +61 -0
  5. package/dist/composable.d.ts +3 -0
  6. package/dist/compose-canvas.d.ts +1 -0
  7. package/dist/compose-canvas.js +14 -0
  8. package/dist/compose-svg.d.ts +9 -0
  9. package/dist/compose-svg.js +29 -0
  10. package/dist/compose.js +6 -36
  11. package/dist/create-scaffold.d.ts +31 -0
  12. package/dist/create-scaffold.js +92 -0
  13. package/dist/esm/index.mjs +7851 -147
  14. package/dist/find-capturable-elements.d.ts +2 -0
  15. package/dist/find-capturable-elements.js +28 -0
  16. package/dist/frame-range.d.ts +2 -0
  17. package/dist/frame-range.js +15 -0
  18. package/dist/index.d.ts +5 -0
  19. package/dist/index.js +1 -0
  20. package/dist/mediabunny-mappings.d.ts +9 -0
  21. package/dist/mediabunny-mappings.js +53 -0
  22. package/dist/parse-transform-origin.d.ts +4 -0
  23. package/dist/parse-transform-origin.js +7 -0
  24. package/dist/props-if-has-props.d.ts +37 -0
  25. package/dist/props-if-has-props.js +1 -0
  26. package/dist/render-media-on-web.d.ts +45 -0
  27. package/dist/render-media-on-web.js +190 -0
  28. package/dist/render-still-on-web.d.ts +17 -11
  29. package/dist/render-still-on-web.js +70 -99
  30. package/dist/take-screenshot.d.ts +12 -0
  31. package/dist/take-screenshot.js +18 -0
  32. package/dist/throttle-progress.d.ts +6 -0
  33. package/dist/throttle-progress.js +43 -0
  34. package/dist/update-time.d.ts +14 -0
  35. package/dist/update-time.js +17 -0
  36. package/dist/validate-video-frame.d.ts +16 -0
  37. package/dist/validate-video-frame.js +34 -0
  38. package/dist/wait-for-ready.d.ts +6 -1
  39. package/dist/wait-for-ready.js +20 -9
  40. package/dist/with-resolvers.d.ts +10 -0
  41. package/dist/with-resolvers.js +9 -0
  42. package/package.json +12 -7
  43. package/dist/find-canvas-elements.d.ts +0 -2
  44. package/dist/find-canvas-elements.js +0 -12
  45. package/dist/find-svg-elements.d.ts +0 -2
  46. package/dist/find-svg-elements.js +0 -12
@@ -0,0 +1,2 @@
1
+ import type { Composable } from './composable';
2
+ export declare const findCapturableElements: (element: HTMLDivElement) => Composable[];
@@ -0,0 +1,28 @@
1
+ export const findCapturableElements = (element) => {
2
+ const canvasAndSvgElements = element.querySelectorAll('svg,canvas,img');
3
+ const composables = [];
4
+ Array.from(canvasAndSvgElements).forEach((capturableElement) => {
5
+ if (capturableElement.tagName.toLocaleLowerCase() === 'canvas') {
6
+ const canvas = capturableElement;
7
+ composables.push({
8
+ type: 'canvas',
9
+ element: canvas,
10
+ });
11
+ }
12
+ else if (capturableElement.tagName.toLocaleLowerCase() === 'svg') {
13
+ const svg = capturableElement;
14
+ composables.push({
15
+ type: 'svg',
16
+ element: svg,
17
+ });
18
+ }
19
+ else if (capturableElement.tagName.toLocaleLowerCase() === 'img') {
20
+ const img = capturableElement;
21
+ composables.push({
22
+ type: 'img',
23
+ element: img,
24
+ });
25
+ }
26
+ });
27
+ return composables;
28
+ };
@@ -0,0 +1,2 @@
1
+ export type FrameRange = number | [number, number];
2
+ export declare const getRealFrameRange: (durationInFrames: number, frameRange: FrameRange | null) => [number, number];
@@ -0,0 +1,15 @@
1
+ export const getRealFrameRange = (durationInFrames, frameRange) => {
2
+ if (frameRange === null) {
3
+ return [0, durationInFrames - 1];
4
+ }
5
+ if (typeof frameRange === 'number') {
6
+ if (frameRange < 0 || frameRange >= durationInFrames) {
7
+ throw new Error(`Frame number is out of range, must be between 0 and ${durationInFrames - 1} but got ${frameRange}`);
8
+ }
9
+ return [frameRange, frameRange];
10
+ }
11
+ if (frameRange[1] >= durationInFrames || frameRange[0] < 0) {
12
+ throw new Error(`The "durationInFrames" of the composition was evaluated to be ${durationInFrames}, but frame range ${frameRange.join('-')} is not inbetween 0-${durationInFrames - 1}`);
13
+ }
14
+ return frameRange;
15
+ };
package/dist/index.d.ts CHANGED
@@ -1 +1,6 @@
1
+ export type { WebRendererCodec, WebRendererContainer, WebRendererQuality, } from './mediabunny-mappings';
2
+ export { renderMediaOnWeb } from './render-media-on-web';
3
+ export type { RenderMediaOnWebOptions, RenderMediaOnWebProgress, RenderMediaOnWebProgressCallback, } from './render-media-on-web';
1
4
  export { renderStillOnWeb } from './render-still-on-web';
5
+ export type { RenderStillOnWebImageFormat as RenderStillImageFormat, RenderStillOnWebOptions, } from './render-still-on-web';
6
+ export type { OnFrameCallback } from './validate-video-frame';
package/dist/index.js CHANGED
@@ -1 +1,2 @@
1
+ export { renderMediaOnWeb } from './render-media-on-web';
1
2
  export { renderStillOnWeb } from './render-still-on-web';
@@ -0,0 +1,9 @@
1
+ import type { Quality } from 'mediabunny';
2
+ import { type OutputFormat, type VideoCodec } from 'mediabunny';
3
+ export type WebRendererCodec = 'h264' | 'h265' | 'vp8' | 'vp9' | 'av1';
4
+ export type WebRendererContainer = 'mp4' | 'webm';
5
+ export type WebRendererQuality = 'very-low' | 'low' | 'medium' | 'high' | 'very-high';
6
+ export declare const codecToMediabunnyCodec: (codec: WebRendererCodec) => VideoCodec;
7
+ export declare const containerToMediabunnyContainer: (container: WebRendererContainer) => OutputFormat;
8
+ export declare const getDefaultVideoCodecForContainer: (container: WebRendererContainer) => WebRendererCodec;
9
+ export declare const getQualityForWebRendererQuality: (quality: WebRendererQuality) => Quality;
@@ -0,0 +1,53 @@
1
+ import { Mp4OutputFormat, QUALITY_HIGH, QUALITY_LOW, QUALITY_MEDIUM, QUALITY_VERY_HIGH, QUALITY_VERY_LOW, WebMOutputFormat, } from 'mediabunny';
2
+ export const codecToMediabunnyCodec = (codec) => {
3
+ switch (codec) {
4
+ case 'h264':
5
+ return 'avc';
6
+ case 'h265':
7
+ return 'hevc';
8
+ case 'vp8':
9
+ return 'vp8';
10
+ case 'vp9':
11
+ return 'vp9';
12
+ case 'av1':
13
+ return 'av1';
14
+ default:
15
+ throw new Error(`Unsupported codec: ${codec}`);
16
+ }
17
+ };
18
+ export const containerToMediabunnyContainer = (container) => {
19
+ switch (container) {
20
+ case 'mp4':
21
+ return new Mp4OutputFormat();
22
+ case 'webm':
23
+ return new WebMOutputFormat();
24
+ default:
25
+ throw new Error(`Unsupported container: ${container}`);
26
+ }
27
+ };
28
+ export const getDefaultVideoCodecForContainer = (container) => {
29
+ switch (container) {
30
+ case 'mp4':
31
+ return 'h264';
32
+ case 'webm':
33
+ return 'vp8';
34
+ default:
35
+ throw new Error(`Unsupported container: ${container}`);
36
+ }
37
+ };
38
+ export const getQualityForWebRendererQuality = (quality) => {
39
+ switch (quality) {
40
+ case 'very-low':
41
+ return QUALITY_VERY_LOW;
42
+ case 'low':
43
+ return QUALITY_LOW;
44
+ case 'medium':
45
+ return QUALITY_MEDIUM;
46
+ case 'high':
47
+ return QUALITY_HIGH;
48
+ case 'very-high':
49
+ return QUALITY_VERY_HIGH;
50
+ default:
51
+ throw new Error(`Unsupported quality: ${quality}`);
52
+ }
53
+ };
@@ -0,0 +1,4 @@
1
+ export declare const parseTransformOrigin: (transformOrigin: string) => {
2
+ x: number;
3
+ y: number;
4
+ } | null;
@@ -0,0 +1,7 @@
1
+ export const parseTransformOrigin = (transformOrigin) => {
2
+ if (transformOrigin.trim() === '') {
3
+ return null;
4
+ }
5
+ const [x, y] = transformOrigin.split(' ');
6
+ return { x: parseFloat(x), y: parseFloat(y) };
7
+ };
@@ -0,0 +1,37 @@
1
+ import type { ComponentType } from 'react';
2
+ import type { CalculateMetadataFunction } from 'remotion';
3
+ import type { AnyZodObject, z } from 'zod';
4
+ export type InferProps<Schema extends AnyZodObject, Props extends Record<string, unknown>> = AnyZodObject extends Schema ? {} extends Props ? Record<string, unknown> : Props : {} extends Props ? z.input<Schema> : // Props and schema specified
5
+ z.input<Schema> & Props;
6
+ export type DefaultPropsIfHasProps<Schema extends AnyZodObject, Props> = AnyZodObject extends Schema ? {} extends Props ? {
7
+ defaultProps?: z.input<Schema> & Props;
8
+ } : {
9
+ defaultProps: Props;
10
+ } : {} extends Props ? {
11
+ defaultProps: z.input<Schema>;
12
+ } : {
13
+ defaultProps: z.input<Schema> & Props;
14
+ };
15
+ type LooseComponentType<T> = ComponentType<T> | ((props: T) => React.ReactNode);
16
+ type OptionalDimensions<Schema extends AnyZodObject, Props extends Record<string, unknown>> = {
17
+ component: LooseComponentType<Props>;
18
+ id: string;
19
+ width?: number;
20
+ height?: number;
21
+ calculateMetadata: CalculateMetadataFunction<InferProps<Schema, Props>>;
22
+ };
23
+ type MandatoryDimensions<Schema extends AnyZodObject, Props extends Record<string, unknown>> = {
24
+ component: LooseComponentType<Props>;
25
+ id: string;
26
+ width: number;
27
+ height: number;
28
+ calculateMetadata?: CalculateMetadataFunction<InferProps<Schema, Props>> | null;
29
+ };
30
+ export type CompositionCalculateMetadataOrExplicit<Schema extends AnyZodObject, Props extends Record<string, unknown>> = ((OptionalDimensions<Schema, Props> & {
31
+ fps?: number;
32
+ durationInFrames?: number;
33
+ }) | (MandatoryDimensions<Schema, Props> & {
34
+ fps: number;
35
+ durationInFrames: number;
36
+ })) & DefaultPropsIfHasProps<Schema, Props>;
37
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,45 @@
1
+ import { type LogLevel } from 'remotion';
2
+ import type { AnyZodObject, z } from 'zod';
3
+ import { type OnArtifact } from './artifact';
4
+ import { type FrameRange } from './frame-range';
5
+ import type { WebRendererContainer, WebRendererQuality } from './mediabunny-mappings';
6
+ import { type WebRendererCodec } from './mediabunny-mappings';
7
+ import type { CompositionCalculateMetadataOrExplicit } from './props-if-has-props';
8
+ import { type OnFrameCallback } from './validate-video-frame';
9
+ export type InputPropsIfHasProps<Schema extends AnyZodObject, Props> = AnyZodObject extends Schema ? {} extends Props ? {
10
+ inputProps?: z.input<Schema> & Props;
11
+ } : {
12
+ inputProps: Props;
13
+ } : {} extends Props ? {
14
+ inputProps: z.input<Schema>;
15
+ } : {
16
+ inputProps: z.input<Schema> & Props;
17
+ };
18
+ type MandatoryRenderMediaOnWebOptions<Schema extends AnyZodObject, Props extends Record<string, unknown>> = {
19
+ composition: CompositionCalculateMetadataOrExplicit<Schema, Props>;
20
+ };
21
+ export type RenderMediaOnWebProgress = {
22
+ renderedFrames: number;
23
+ encodedFrames: number;
24
+ };
25
+ export type RenderMediaOnWebProgressCallback = (progress: RenderMediaOnWebProgress) => void;
26
+ type OptionalRenderMediaOnWebOptions<Schema extends AnyZodObject> = {
27
+ delayRenderTimeoutInMilliseconds: number;
28
+ logLevel: LogLevel;
29
+ schema: Schema | undefined;
30
+ mediaCacheSizeInBytes: number | null;
31
+ codec: WebRendererCodec;
32
+ container: WebRendererContainer;
33
+ signal: AbortSignal | null;
34
+ onProgress: RenderMediaOnWebProgressCallback | null;
35
+ hardwareAcceleration: 'no-preference' | 'prefer-hardware' | 'prefer-software';
36
+ keyframeIntervalInSeconds: number;
37
+ videoBitrate: number | WebRendererQuality;
38
+ frameRange: FrameRange | null;
39
+ transparent: boolean;
40
+ onArtifact: OnArtifact | null;
41
+ onFrame: OnFrameCallback | null;
42
+ };
43
+ export type RenderMediaOnWebOptions<Schema extends AnyZodObject, Props extends Record<string, unknown>> = MandatoryRenderMediaOnWebOptions<Schema, Props> & Partial<OptionalRenderMediaOnWebOptions<Schema>> & InputPropsIfHasProps<Schema, Props>;
44
+ export declare const renderMediaOnWeb: <Schema extends AnyZodObject, Props extends Record<string, unknown>>(options: RenderMediaOnWebOptions<Schema, Props>) => Promise<ArrayBuffer>;
45
+ export {};
@@ -0,0 +1,190 @@
1
+ import { BufferTarget, Output, VideoSample, VideoSampleSource } from 'mediabunny';
2
+ import { Internals } from 'remotion';
3
+ import { handleArtifacts } from './artifact';
4
+ import { createScaffold } from './create-scaffold';
5
+ import { getRealFrameRange } from './frame-range';
6
+ import { codecToMediabunnyCodec, containerToMediabunnyContainer, getDefaultVideoCodecForContainer, getQualityForWebRendererQuality, } from './mediabunny-mappings';
7
+ import { createFrame } from './take-screenshot';
8
+ import { createThrottledProgressCallback } from './throttle-progress';
9
+ import { validateVideoFrame } from './validate-video-frame';
10
+ import { waitForReady } from './wait-for-ready';
11
+ // TODO: More containers
12
+ // TODO: Audio
13
+ // TODO: Metadata
14
+ // TODO: Validating inputs
15
+ // TODO: Web file system API
16
+ // TODO: Apply defaultCodec
17
+ const internalRenderMediaOnWeb = async ({ composition, inputProps, delayRenderTimeoutInMilliseconds, logLevel, mediaCacheSizeInBytes, schema, codec, container, signal, onProgress, hardwareAcceleration, keyframeIntervalInSeconds, videoBitrate, frameRange, transparent, onArtifact, onFrame, }) => {
18
+ var _a, _b, _c, _d, _e, _f, _g;
19
+ const cleanupFns = [];
20
+ const format = containerToMediabunnyContainer(container);
21
+ if (codec &&
22
+ !format.getSupportedCodecs().includes(codecToMediabunnyCodec(codec))) {
23
+ return Promise.reject(new Error(`Codec ${codec} is not supported for container ${container}`));
24
+ }
25
+ const resolved = await Internals.resolveVideoConfig({
26
+ calculateMetadata: (_a = composition.calculateMetadata) !== null && _a !== void 0 ? _a : null,
27
+ signal: signal !== null && signal !== void 0 ? signal : new AbortController().signal,
28
+ defaultProps: (_b = composition.defaultProps) !== null && _b !== void 0 ? _b : {},
29
+ inputProps: inputProps !== null && inputProps !== void 0 ? inputProps : {},
30
+ compositionId: composition.id,
31
+ compositionDurationInFrames: (_c = composition.durationInFrames) !== null && _c !== void 0 ? _c : null,
32
+ compositionFps: (_d = composition.fps) !== null && _d !== void 0 ? _d : null,
33
+ compositionHeight: (_e = composition.height) !== null && _e !== void 0 ? _e : null,
34
+ compositionWidth: (_f = composition.width) !== null && _f !== void 0 ? _f : null,
35
+ });
36
+ const realFrameRange = getRealFrameRange(resolved.durationInFrames, frameRange);
37
+ if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
38
+ return Promise.reject(new Error('renderMediaOnWeb() was cancelled'));
39
+ }
40
+ const { delayRenderScope, div, cleanupScaffold, timeUpdater, collectAssets } = await createScaffold({
41
+ width: resolved.width,
42
+ height: resolved.height,
43
+ fps: resolved.fps,
44
+ durationInFrames: resolved.durationInFrames,
45
+ Component: composition.component,
46
+ resolvedProps: resolved.props,
47
+ id: resolved.id,
48
+ delayRenderTimeoutInMilliseconds,
49
+ logLevel,
50
+ mediaCacheSizeInBytes,
51
+ schema: schema !== null && schema !== void 0 ? schema : null,
52
+ audioEnabled: true,
53
+ videoEnabled: true,
54
+ initialFrame: 0,
55
+ defaultCodec: resolved.defaultCodec,
56
+ defaultOutName: resolved.defaultOutName,
57
+ });
58
+ const artifactsHandler = handleArtifacts({
59
+ ref: collectAssets,
60
+ onArtifact,
61
+ });
62
+ cleanupFns.push(() => {
63
+ cleanupScaffold();
64
+ });
65
+ const output = new Output({
66
+ format,
67
+ target: new BufferTarget(),
68
+ });
69
+ try {
70
+ if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
71
+ throw new Error('renderMediaOnWeb() was cancelled');
72
+ }
73
+ await waitForReady({
74
+ timeoutInMilliseconds: delayRenderTimeoutInMilliseconds,
75
+ scope: delayRenderScope,
76
+ signal,
77
+ apiName: 'renderMediaOnWeb',
78
+ });
79
+ if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
80
+ throw new Error('renderMediaOnWeb() was cancelled');
81
+ }
82
+ cleanupFns.push(() => {
83
+ if (output.state === 'finalized' || output.state === 'canceled') {
84
+ return;
85
+ }
86
+ output.cancel();
87
+ });
88
+ const videoSampleSource = new VideoSampleSource({
89
+ codec: codecToMediabunnyCodec(codec),
90
+ bitrate: typeof videoBitrate === 'number'
91
+ ? videoBitrate
92
+ : getQualityForWebRendererQuality(videoBitrate),
93
+ sizeChangeBehavior: 'deny',
94
+ hardwareAcceleration,
95
+ latencyMode: 'quality',
96
+ keyFrameInterval: keyframeIntervalInSeconds,
97
+ alpha: transparent ? 'keep' : 'discard',
98
+ });
99
+ cleanupFns.push(() => {
100
+ videoSampleSource.close();
101
+ });
102
+ output.addVideoTrack(videoSampleSource);
103
+ await output.start();
104
+ if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
105
+ throw new Error('renderMediaOnWeb() was cancelled');
106
+ }
107
+ const progress = {
108
+ renderedFrames: 0,
109
+ encodedFrames: 0,
110
+ };
111
+ const throttledOnProgress = createThrottledProgressCallback(onProgress);
112
+ for (let i = realFrameRange[0]; i <= realFrameRange[1]; i++) {
113
+ (_g = timeUpdater.current) === null || _g === void 0 ? void 0 : _g.update(i);
114
+ await waitForReady({
115
+ timeoutInMilliseconds: delayRenderTimeoutInMilliseconds,
116
+ scope: delayRenderScope,
117
+ signal,
118
+ apiName: 'renderMediaOnWeb',
119
+ });
120
+ if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
121
+ throw new Error('renderMediaOnWeb() was cancelled');
122
+ }
123
+ const imageData = await createFrame({
124
+ div,
125
+ width: resolved.width,
126
+ height: resolved.height,
127
+ });
128
+ await artifactsHandler.handle({ imageData, frame: i });
129
+ if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
130
+ throw new Error('renderMediaOnWeb() was cancelled');
131
+ }
132
+ const timestamp = Math.round(((i - realFrameRange[0]) / resolved.fps) * 1000000);
133
+ const videoFrame = new VideoFrame(imageData, {
134
+ timestamp,
135
+ });
136
+ progress.renderedFrames++;
137
+ throttledOnProgress === null || throttledOnProgress === void 0 ? void 0 : throttledOnProgress({ ...progress });
138
+ // Process frame through onFrame callback if provided
139
+ let frameToEncode = videoFrame;
140
+ if (onFrame) {
141
+ const returnedFrame = await onFrame(videoFrame);
142
+ frameToEncode = validateVideoFrame({
143
+ originalFrame: videoFrame,
144
+ returnedFrame,
145
+ expectedWidth: resolved.width,
146
+ expectedHeight: resolved.height,
147
+ expectedTimestamp: timestamp,
148
+ });
149
+ }
150
+ await videoSampleSource.add(new VideoSample(frameToEncode));
151
+ progress.encodedFrames++;
152
+ throttledOnProgress === null || throttledOnProgress === void 0 ? void 0 : throttledOnProgress({ ...progress });
153
+ frameToEncode.close();
154
+ if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
155
+ throw new Error('renderMediaOnWeb() was cancelled');
156
+ }
157
+ }
158
+ // Call progress one final time to ensure final state is reported
159
+ onProgress === null || onProgress === void 0 ? void 0 : onProgress({ ...progress });
160
+ videoSampleSource.close();
161
+ await output.finalize();
162
+ return output.target.buffer;
163
+ }
164
+ finally {
165
+ cleanupFns.forEach((fn) => fn());
166
+ }
167
+ };
168
+ export const renderMediaOnWeb = (options) => {
169
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
170
+ const container = (_a = options.container) !== null && _a !== void 0 ? _a : 'mp4';
171
+ const codec = (_b = options.codec) !== null && _b !== void 0 ? _b : getDefaultVideoCodecForContainer(container);
172
+ return internalRenderMediaOnWeb({
173
+ ...options,
174
+ delayRenderTimeoutInMilliseconds: (_c = options.delayRenderTimeoutInMilliseconds) !== null && _c !== void 0 ? _c : 30000,
175
+ logLevel: (_d = options.logLevel) !== null && _d !== void 0 ? _d : 'info',
176
+ schema: (_e = options.schema) !== null && _e !== void 0 ? _e : undefined,
177
+ mediaCacheSizeInBytes: (_f = options.mediaCacheSizeInBytes) !== null && _f !== void 0 ? _f : null,
178
+ codec,
179
+ container,
180
+ signal: (_g = options.signal) !== null && _g !== void 0 ? _g : null,
181
+ onProgress: (_h = options.onProgress) !== null && _h !== void 0 ? _h : null,
182
+ hardwareAcceleration: (_j = options.hardwareAcceleration) !== null && _j !== void 0 ? _j : 'no-preference',
183
+ keyframeIntervalInSeconds: (_k = options.keyframeIntervalInSeconds) !== null && _k !== void 0 ? _k : 5,
184
+ videoBitrate: (_l = options.videoBitrate) !== null && _l !== void 0 ? _l : 'medium',
185
+ frameRange: (_m = options.frameRange) !== null && _m !== void 0 ? _m : null,
186
+ transparent: (_o = options.transparent) !== null && _o !== void 0 ? _o : false,
187
+ onArtifact: (_p = options.onArtifact) !== null && _p !== void 0 ? _p : null,
188
+ onFrame: (_q = options.onFrame) !== null && _q !== void 0 ? _q : null,
189
+ });
190
+ };
@@ -1,17 +1,23 @@
1
- import { type ComponentType, type LazyExoticComponent } from 'react';
2
- import type { LogLevel } from 'remotion';
3
- type MandatoryRenderStillOnWebOptions = {
4
- Component: LazyExoticComponent<ComponentType<Record<string, unknown>>> | ComponentType<Record<string, unknown>>;
5
- width: number;
6
- height: number;
7
- fps: number;
8
- durationInFrames: number;
1
+ import { type LogLevel } from 'remotion';
2
+ import type { AnyZodObject } from 'zod';
3
+ import type { OnArtifact } from './artifact';
4
+ import type { CompositionCalculateMetadataOrExplicit } from './props-if-has-props';
5
+ import type { InputPropsIfHasProps } from './render-media-on-web';
6
+ export type RenderStillOnWebImageFormat = 'png' | 'jpeg' | 'webp';
7
+ type MandatoryRenderStillOnWebOptions<Schema extends AnyZodObject, Props extends Record<string, unknown>> = {
9
8
  frame: number;
9
+ imageFormat: RenderStillOnWebImageFormat;
10
+ } & {
11
+ composition: CompositionCalculateMetadataOrExplicit<Schema, Props>;
10
12
  };
11
- type OptionalRenderStillOnWebOptions = {
13
+ type OptionalRenderStillOnWebOptions<Schema extends AnyZodObject> = {
12
14
  delayRenderTimeoutInMilliseconds: number;
13
15
  logLevel: LogLevel;
16
+ schema: Schema | undefined;
17
+ mediaCacheSizeInBytes: number | null;
18
+ signal: AbortSignal | null;
19
+ onArtifact: OnArtifact | null;
14
20
  };
15
- type RenderStillOnWebOptions = MandatoryRenderStillOnWebOptions & Partial<OptionalRenderStillOnWebOptions>;
16
- export declare const renderStillOnWeb: (options: RenderStillOnWebOptions) => Promise<Blob>;
21
+ export type RenderStillOnWebOptions<Schema extends AnyZodObject, Props extends Record<string, unknown>> = MandatoryRenderStillOnWebOptions<Schema, Props> & Partial<OptionalRenderStillOnWebOptions<Schema>> & InputPropsIfHasProps<Schema, Props>;
22
+ export declare const renderStillOnWeb: <Schema extends AnyZodObject, Props extends Record<string, unknown>>(options: RenderStillOnWebOptions<Schema, Props>) => Promise<Blob>;
17
23
  export {};
@@ -1,110 +1,81 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import ReactDOM from 'react-dom/client';
3
- import { Internals } from 'remotion';
4
- import { compose } from './compose';
5
- import { findCanvasElements } from './find-canvas-elements';
6
- import { findSvgElements } from './find-svg-elements';
1
+ import { Internals, } from 'remotion';
2
+ import { handleArtifacts } from './artifact';
3
+ import { createScaffold } from './create-scaffold';
4
+ import { takeScreenshot } from './take-screenshot';
7
5
  import { waitForReady } from './wait-for-ready';
8
- const COMP_ID = 'markup';
9
- const internalRenderStillOnWeb = async ({ Component, width, height, fps, durationInFrames, frame, delayRenderTimeoutInMilliseconds, logLevel, }) => {
10
- const div = document.createElement('div');
11
- // Match same behavior as renderEntry.tsx
12
- div.style.display = 'flex';
13
- div.style.backgroundColor = 'transparent';
14
- div.style.position = 'fixed';
15
- div.style.width = `${width}px`;
16
- div.style.height = `${height}px`;
17
- div.style.zIndex = '-9999';
18
- document.body.appendChild(div);
19
- if (!ReactDOM.createRoot) {
20
- throw new Error('@remotion/web-renderer requires React 18 or higher');
6
+ async function internalRenderStillOnWeb({ frame, delayRenderTimeoutInMilliseconds, logLevel, inputProps, schema, imageFormat, mediaCacheSizeInBytes, composition, signal, onArtifact, }) {
7
+ var _a, _b, _c, _d, _e, _f;
8
+ const resolved = await Internals.resolveVideoConfig({
9
+ calculateMetadata: (_a = composition.calculateMetadata) !== null && _a !== void 0 ? _a : null,
10
+ signal: signal !== null && signal !== void 0 ? signal : new AbortController().signal,
11
+ defaultProps: (_b = composition.defaultProps) !== null && _b !== void 0 ? _b : {},
12
+ inputProps: inputProps !== null && inputProps !== void 0 ? inputProps : {},
13
+ compositionId: composition.id,
14
+ compositionDurationInFrames: (_c = composition.durationInFrames) !== null && _c !== void 0 ? _c : null,
15
+ compositionFps: (_d = composition.fps) !== null && _d !== void 0 ? _d : null,
16
+ compositionHeight: (_e = composition.height) !== null && _e !== void 0 ? _e : null,
17
+ compositionWidth: (_f = composition.width) !== null && _f !== void 0 ? _f : null,
18
+ });
19
+ if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
20
+ return Promise.reject(new Error('renderStillOnWeb() was cancelled'));
21
21
  }
22
- // TODO: Env variables
23
- // TODO: Input Props
24
- // TODO: Default props
25
- // TODO: calculateMetadata()
26
- // TODO: getRemotionEnvironment()
27
- // TODO: delayRender()
28
- // TODO: Video config
29
- // TODO: window.remotion_isPlayer
30
- const root = ReactDOM.createRoot(div);
31
- const delayRenderScope = {
32
- remotion_renderReady: true,
33
- remotion_delayRenderTimeouts: {},
34
- remotion_puppeteerTimeout: delayRenderTimeoutInMilliseconds,
35
- remotion_attempt: 0,
36
- };
37
- root.render(_jsx(Internals.RemotionEnvironmentContext, { value: {
38
- isStudio: false,
39
- isRendering: true,
40
- isPlayer: false,
41
- isReadOnlyStudio: false,
42
- isClientSideRendering: true,
43
- }, children: _jsx(Internals.DelayRenderContextType.Provider, { value: delayRenderScope, children: _jsx(Internals.CompositionManagerProvider, { initialCanvasContent: {
44
- type: 'composition',
45
- compositionId: COMP_ID,
46
- }, onlyRenderComposition: null,
47
- // TODO: Hardcoded
48
- currentCompositionMetadata: {
49
- // TODO: Empty
50
- props: {},
51
- durationInFrames,
52
- fps,
53
- height,
54
- width,
55
- defaultCodec: null,
56
- defaultOutName: null,
57
- defaultVideoImageFormat: null,
58
- defaultPixelFormat: null,
59
- defaultProResProfile: null,
60
- }, initialCompositions: [
61
- {
62
- id: COMP_ID,
63
- component: Component,
64
- nonce: 0,
65
- // TODO: Do we need to allow to set this?
66
- defaultProps: undefined,
67
- folderName: null,
68
- parentFolderName: null,
69
- schema: null,
70
- calculateMetadata: null,
71
- durationInFrames,
72
- fps,
73
- height,
74
- width,
75
- },
76
- ], children: _jsx(Internals.RemotionRoot
77
- // TODO: Hardcoded
78
- , {
79
- // TODO: Hardcoded
80
- audioEnabled: false,
81
- // TODO: Hardcoded
82
- videoEnabled: true, logLevel: logLevel, numberOfAudioTags: 0,
83
- // TODO: Hardcoded
84
- audioLatencyHint: "interactive", frameState: {
85
- [COMP_ID]: frame,
86
- }, children: _jsx(Internals.CanUseRemotionHooks, { value: true, children: _jsx(Component, {}) }) }) }) }) }));
87
- // TODO: Scope cancelRender()
88
- await waitForReady(delayRenderTimeoutInMilliseconds, delayRenderScope);
89
- const canvasElements = findCanvasElements(div);
90
- const svgElements = findSvgElements(div);
91
- const composed = await compose({
92
- composables: [...canvasElements, ...svgElements],
93
- width,
94
- height,
22
+ const { delayRenderScope, div, cleanupScaffold, collectAssets } = await createScaffold({
23
+ width: resolved.width,
24
+ height: resolved.height,
25
+ delayRenderTimeoutInMilliseconds,
26
+ logLevel,
27
+ resolvedProps: resolved.props,
28
+ id: resolved.id,
29
+ mediaCacheSizeInBytes,
30
+ audioEnabled: false,
31
+ Component: composition.component,
32
+ videoEnabled: true,
33
+ durationInFrames: resolved.durationInFrames,
34
+ fps: resolved.fps,
35
+ schema: schema !== null && schema !== void 0 ? schema : null,
36
+ initialFrame: frame,
37
+ defaultCodec: resolved.defaultCodec,
38
+ defaultOutName: resolved.defaultOutName,
95
39
  });
96
- const imageData = await composed.convertToBlob({
97
- type: 'image/png',
40
+ const artifactsHandler = handleArtifacts({
41
+ ref: collectAssets,
42
+ onArtifact,
98
43
  });
99
- root.unmount();
100
- div.remove();
101
- return imageData;
102
- };
44
+ try {
45
+ if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
46
+ throw new Error('renderStillOnWeb() was cancelled');
47
+ }
48
+ await waitForReady({
49
+ timeoutInMilliseconds: delayRenderTimeoutInMilliseconds,
50
+ scope: delayRenderScope,
51
+ signal,
52
+ apiName: 'renderStillOnWeb',
53
+ });
54
+ if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
55
+ throw new Error('renderStillOnWeb() was cancelled');
56
+ }
57
+ const imageData = await takeScreenshot({
58
+ div,
59
+ width: resolved.width,
60
+ height: resolved.height,
61
+ imageFormat,
62
+ });
63
+ await artifactsHandler.handle({ imageData, frame });
64
+ return imageData;
65
+ }
66
+ finally {
67
+ cleanupScaffold();
68
+ }
69
+ }
103
70
  export const renderStillOnWeb = (options) => {
104
- var _a, _b;
71
+ var _a, _b, _c, _d, _e, _f;
105
72
  return internalRenderStillOnWeb({
106
73
  ...options,
107
74
  delayRenderTimeoutInMilliseconds: (_a = options.delayRenderTimeoutInMilliseconds) !== null && _a !== void 0 ? _a : 30000,
108
75
  logLevel: (_b = options.logLevel) !== null && _b !== void 0 ? _b : 'info',
76
+ schema: (_c = options.schema) !== null && _c !== void 0 ? _c : undefined,
77
+ mediaCacheSizeInBytes: (_d = options.mediaCacheSizeInBytes) !== null && _d !== void 0 ? _d : null,
78
+ signal: (_e = options.signal) !== null && _e !== void 0 ? _e : null,
79
+ onArtifact: (_f = options.onArtifact) !== null && _f !== void 0 ? _f : null,
109
80
  });
110
81
  };
@@ -0,0 +1,12 @@
1
+ import type { RenderStillOnWebImageFormat } from './render-still-on-web';
2
+ export declare const createFrame: ({ div, width, height, }: {
3
+ div: HTMLDivElement;
4
+ width: number;
5
+ height: number;
6
+ }) => Promise<OffscreenCanvas>;
7
+ export declare const takeScreenshot: ({ div, width, height, imageFormat, }: {
8
+ div: HTMLDivElement;
9
+ width: number;
10
+ height: number;
11
+ imageFormat: RenderStillOnWebImageFormat;
12
+ }) => Promise<Blob>;