@remotion/web-renderer 4.0.375 → 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.
- package/dist/artifact.d.ts +24 -0
- package/dist/artifact.js +59 -0
- package/dist/calculate-transforms.d.ts +9 -0
- package/dist/calculate-transforms.js +61 -0
- package/dist/composable.d.ts +3 -0
- package/dist/compose-canvas.d.ts +1 -0
- package/dist/compose-canvas.js +14 -0
- package/dist/compose-svg.d.ts +9 -0
- package/dist/compose-svg.js +29 -0
- package/dist/compose.js +6 -36
- package/dist/create-scaffold.d.ts +31 -0
- package/dist/create-scaffold.js +92 -0
- package/dist/esm/index.mjs +7844 -171
- package/dist/find-capturable-elements.d.ts +2 -0
- package/dist/find-capturable-elements.js +28 -0
- package/dist/frame-range.d.ts +2 -0
- package/dist/frame-range.js +15 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +1 -0
- package/dist/mediabunny-mappings.d.ts +9 -0
- package/dist/mediabunny-mappings.js +53 -0
- package/dist/parse-transform-origin.d.ts +4 -0
- package/dist/parse-transform-origin.js +7 -0
- package/dist/props-if-has-props.d.ts +37 -0
- package/dist/props-if-has-props.js +1 -0
- package/dist/render-media-on-web.d.ts +45 -0
- package/dist/render-media-on-web.js +190 -0
- package/dist/render-still-on-web.d.ts +17 -12
- package/dist/render-still-on-web.js +69 -104
- package/dist/take-screenshot.d.ts +12 -0
- package/dist/take-screenshot.js +18 -0
- package/dist/throttle-progress.d.ts +6 -0
- package/dist/throttle-progress.js +43 -0
- package/dist/update-time.d.ts +14 -0
- package/dist/update-time.js +17 -0
- package/dist/validate-video-frame.d.ts +16 -0
- package/dist/validate-video-frame.js +34 -0
- package/dist/wait-for-ready.d.ts +6 -1
- package/dist/wait-for-ready.js +18 -14
- package/package.json +12 -8
- package/dist/error-boundary.d.ts +0 -16
- package/dist/error-boundary.js +0 -20
- package/dist/find-canvas-elements.d.ts +0 -2
- package/dist/find-canvas-elements.js +0 -12
- package/dist/find-svg-elements.d.ts +0 -2
- package/dist/find-svg-elements.js +0 -12
|
@@ -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,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
|
@@ -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,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
|
|
2
|
-
import type {
|
|
3
|
-
type
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
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<
|
|
17
|
-
export declare const renderStillOnWeb: <
|
|
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 {
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
|
103
|
-
|
|
40
|
+
const artifactsHandler = handleArtifacts({
|
|
41
|
+
ref: collectAssets,
|
|
42
|
+
onArtifact,
|
|
104
43
|
});
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
};
|