@remotion/web-renderer 4.0.375 → 4.0.377

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 +18 -0
  8. package/dist/compose-svg.d.ts +1 -0
  9. package/dist/compose-svg.js +34 -0
  10. package/dist/compose.js +2 -42
  11. package/dist/create-scaffold.d.ts +31 -0
  12. package/dist/create-scaffold.js +92 -0
  13. package/dist/esm/index.mjs +7846 -179
  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 -12
  29. package/dist/render-still-on-web.js +69 -104
  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 +18 -14
  40. package/package.json +13 -8
  41. package/dist/error-boundary.d.ts +0 -16
  42. package/dist/error-boundary.js +0 -20
  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,18 +1,23 @@
1
- import { type ComponentType, type LazyExoticComponent } from 'react';
2
- import type { LogLevel } from 'remotion';
3
- type MandatoryRenderStillOnWebOptions<T extends Record<string, unknown>> = {
4
- Component: LazyExoticComponent<ComponentType<T>> | ComponentType<T>;
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;
10
- inputProps: T;
9
+ imageFormat: RenderStillOnWebImageFormat;
10
+ } & {
11
+ composition: CompositionCalculateMetadataOrExplicit<Schema, Props>;
11
12
  };
12
- type OptionalRenderStillOnWebOptions = {
13
+ type OptionalRenderStillOnWebOptions<Schema extends AnyZodObject> = {
13
14
  delayRenderTimeoutInMilliseconds: number;
14
15
  logLevel: LogLevel;
16
+ schema: Schema | undefined;
17
+ mediaCacheSizeInBytes: number | null;
18
+ signal: AbortSignal | null;
19
+ onArtifact: OnArtifact | null;
15
20
  };
16
- type RenderStillOnWebOptions<T extends Record<string, unknown>> = MandatoryRenderStillOnWebOptions<T> & Partial<OptionalRenderStillOnWebOptions>;
17
- export declare const renderStillOnWeb: <T extends Record<string, unknown>>(options: RenderStillOnWebOptions<T>) => 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>;
18
23
  export {};
@@ -1,116 +1,81 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { flushSync } from 'react-dom';
3
- import ReactDOM from 'react-dom/client';
4
- import { Internals } from 'remotion';
5
- import { compose } from './compose';
6
- import { findCanvasElements } from './find-canvas-elements';
7
- 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';
8
5
  import { waitForReady } from './wait-for-ready';
9
- import { withResolvers } from './with-resolvers';
10
- const COMP_ID = 'markup';
11
- async function internalRenderStillOnWeb({ Component, width, height, fps, durationInFrames, frame, delayRenderTimeoutInMilliseconds, logLevel, inputProps, }) {
12
- const div = document.createElement('div');
13
- // Match same behavior as renderEntry.tsx
14
- div.style.display = 'flex';
15
- div.style.backgroundColor = 'transparent';
16
- div.style.position = 'fixed';
17
- div.style.width = `${width}px`;
18
- div.style.height = `${height}px`;
19
- div.style.zIndex = '-9999';
20
- document.body.appendChild(div);
21
- if (!ReactDOM.createRoot) {
22
- throw new Error('@remotion/web-renderer requires React 18 or higher');
23
- }
24
- // TODO: Env variables
25
- // TODO: Input Props
26
- // TODO: Default props
27
- // TODO: getInputProps()
28
- // TODO: calculateMetadata()
29
- // TODO: getRemotionEnvironment()
30
- // TODO: delayRender()
31
- // TODO: Video config
32
- const { promise, resolve, reject } = withResolvers();
33
- // TODO: This might not work in React 18
34
- const root = ReactDOM.createRoot(div, {
35
- onUncaughtError: (err) => {
36
- reject(err);
37
- },
38
- });
39
- const delayRenderScope = {
40
- remotion_renderReady: true,
41
- remotion_delayRenderTimeouts: {},
42
- remotion_puppeteerTimeout: delayRenderTimeoutInMilliseconds,
43
- remotion_attempt: 0,
44
- };
45
- flushSync(() => {
46
- root.render(_jsx(Internals.RemotionEnvironmentContext, { value: {
47
- isStudio: false,
48
- isRendering: true,
49
- isPlayer: false,
50
- isReadOnlyStudio: false,
51
- isClientSideRendering: true,
52
- }, children: _jsx(Internals.DelayRenderContextType.Provider, { value: delayRenderScope, children: _jsx(Internals.CompositionManager.Provider, { value: {
53
- compositions: [
54
- {
55
- id: COMP_ID,
56
- // @ts-expect-error
57
- component: Component,
58
- nonce: 0,
59
- // TODO: Do we need to allow to set this?
60
- defaultProps: undefined,
61
- folderName: null,
62
- parentFolderName: null,
63
- schema: null,
64
- calculateMetadata: null,
65
- durationInFrames,
66
- fps,
67
- height,
68
- width,
69
- },
70
- ],
71
- canvasContent: {
72
- type: 'composition',
73
- compositionId: COMP_ID,
74
- },
75
- currentCompositionMetadata: {
76
- props: inputProps,
77
- durationInFrames,
78
- fps,
79
- height,
80
- width,
81
- defaultCodec: null,
82
- defaultOutName: null,
83
- defaultVideoImageFormat: null,
84
- defaultPixelFormat: null,
85
- defaultProResProfile: null,
86
- },
87
- folders: [],
88
- }, children: _jsx(Internals.RemotionRoot, { audioEnabled: false, videoEnabled: true, logLevel: logLevel, numberOfAudioTags: 0, audioLatencyHint: "interactive", frameState: {
89
- [COMP_ID]: frame,
90
- }, children: _jsx(Internals.CanUseRemotionHooks, { value: true, children: _jsx(Component, { ...inputProps }) }) }) }) }) }));
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,
91
18
  });
92
- resolve();
93
- await promise;
94
- await waitForReady(delayRenderTimeoutInMilliseconds, delayRenderScope);
95
- const canvasElements = findCanvasElements(div);
96
- const svgElements = findSvgElements(div);
97
- const composed = await compose({
98
- composables: [...canvasElements, ...svgElements],
99
- width,
100
- height,
19
+ if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
20
+ return Promise.reject(new Error('renderStillOnWeb() was cancelled'));
21
+ }
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,
101
39
  });
102
- const imageData = await composed.convertToBlob({
103
- type: 'image/png',
40
+ const artifactsHandler = handleArtifacts({
41
+ ref: collectAssets,
42
+ onArtifact,
104
43
  });
105
- root.unmount();
106
- div.remove();
107
- return imageData;
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
+ }
108
69
  }
109
70
  export const renderStillOnWeb = (options) => {
110
- var _a, _b;
71
+ var _a, _b, _c, _d, _e, _f;
111
72
  return internalRenderStillOnWeb({
112
73
  ...options,
113
74
  delayRenderTimeoutInMilliseconds: (_a = options.delayRenderTimeoutInMilliseconds) !== null && _a !== void 0 ? _a : 30000,
114
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,
115
80
  });
116
81
  };