@remotion/web-renderer 4.0.428 → 4.0.430
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/README.md +7 -7
- package/dist/add-sample.js +20 -0
- package/dist/artifact.js +56 -0
- package/dist/audio.js +42 -0
- package/dist/can-use-webfs-target.js +19 -0
- package/dist/compose.js +85 -0
- package/dist/create-scaffold.js +104 -0
- package/dist/drawing/border-radius.js +151 -0
- package/dist/drawing/calculate-object-fit.js +208 -0
- package/dist/drawing/calculate-transforms.js +127 -0
- package/dist/drawing/clamp-rect-to-parent-bounds.js +18 -0
- package/dist/drawing/do-rects-intersect.js +6 -0
- package/dist/drawing/draw-background.js +62 -0
- package/dist/drawing/draw-border.js +353 -0
- package/dist/drawing/draw-box-shadow.js +103 -0
- package/dist/drawing/draw-dom-element.js +85 -0
- package/dist/drawing/draw-element.js +84 -0
- package/dist/drawing/draw-outline.js +93 -0
- package/dist/drawing/draw-rounded.js +34 -0
- package/dist/drawing/drawn-fn.js +1 -0
- package/dist/drawing/fit-svg-into-its-dimensions.js +35 -0
- package/dist/drawing/get-clipped-background.d.ts +8 -0
- package/dist/drawing/get-clipped-background.js +14 -0
- package/dist/drawing/get-padding-box.js +30 -0
- package/dist/drawing/get-pretransform-rect.js +49 -0
- package/dist/drawing/handle-3d-transform.js +26 -0
- package/dist/drawing/handle-mask.js +21 -0
- package/dist/drawing/has-transform.js +14 -0
- package/dist/drawing/mask-image.js +14 -0
- package/dist/drawing/opacity.js +7 -0
- package/dist/drawing/overflow.js +14 -0
- package/dist/drawing/parse-linear-gradient.js +260 -0
- package/dist/drawing/parse-transform-origin.js +7 -0
- package/dist/drawing/precompose.d.ts +11 -0
- package/dist/drawing/precompose.js +14 -0
- package/dist/drawing/process-node.js +122 -0
- package/dist/drawing/round-to-expand-rect.js +7 -0
- package/dist/drawing/text/apply-text-transform.js +12 -0
- package/dist/drawing/text/draw-text.js +53 -0
- package/dist/drawing/text/find-line-breaks.text.js +118 -0
- package/dist/drawing/text/get-collapsed-text.d.ts +1 -0
- package/dist/drawing/text/get-collapsed-text.js +46 -0
- package/dist/drawing/text/handle-text-node.js +24 -0
- package/dist/drawing/text/parse-paint-order.d.ts +8 -0
- package/dist/drawing/transform-in-3d.js +177 -0
- package/dist/drawing/transform-rect-with-matrix.js +19 -0
- package/dist/drawing/transform.js +10 -0
- package/dist/drawing/turn-svg-into-drawable.js +41 -0
- package/dist/esm/index.mjs +37 -4
- package/dist/frame-range.js +15 -0
- package/dist/get-audio-encoding-config.js +18 -0
- package/dist/get-biggest-bounding-client-rect.js +43 -0
- package/dist/index.js +2 -0
- package/dist/internal-state.js +36 -0
- package/dist/mediabunny-mappings.js +63 -0
- package/dist/output-target.js +1 -0
- package/dist/props-if-has-props.js +1 -0
- package/dist/render-media-on-web.js +304 -0
- package/dist/render-operations-queue.js +3 -0
- package/dist/render-still-on-web.js +110 -0
- package/dist/send-telemetry-event.js +22 -0
- package/dist/take-screenshot.js +30 -0
- package/dist/throttle-progress.js +43 -0
- package/dist/tree-walker-cleanup-after-children.js +33 -0
- package/dist/update-time.js +17 -0
- package/dist/validate-video-frame.js +34 -0
- package/dist/wait-for-ready.js +39 -0
- package/dist/walk-tree.js +14 -0
- package/dist/web-fs-target.js +41 -0
- package/dist/with-resolvers.js +9 -0
- package/package.json +10 -9
package/dist/esm/index.mjs
CHANGED
|
@@ -984,7 +984,7 @@ var sendUsageEvent = async ({
|
|
|
984
984
|
}
|
|
985
985
|
await LicensingInternals.internalRegisterUsageEvent({
|
|
986
986
|
licenseKey: licenseKey === "free-license" ? null : licenseKey,
|
|
987
|
-
event: "
|
|
987
|
+
event: "web-render",
|
|
988
988
|
host,
|
|
989
989
|
succeeded,
|
|
990
990
|
isStill,
|
|
@@ -3308,6 +3308,16 @@ var findWords = (span) => {
|
|
|
3308
3308
|
return tokens;
|
|
3309
3309
|
};
|
|
3310
3310
|
|
|
3311
|
+
// src/drawing/text/parse-paint-order.ts
|
|
3312
|
+
var parsePaintOrder = (paintOrder) => {
|
|
3313
|
+
const paintOrderValue = paintOrder || "normal";
|
|
3314
|
+
const paintOrderParts = paintOrderValue === "normal" ? ["fill", "stroke"] : paintOrderValue.split(/\s+/).filter(Boolean);
|
|
3315
|
+
const strokeIndex = paintOrderParts.indexOf("stroke");
|
|
3316
|
+
const fillIndex = paintOrderParts.indexOf("fill");
|
|
3317
|
+
const strokeFirst = strokeIndex !== -1 && (fillIndex === -1 || strokeIndex < fillIndex);
|
|
3318
|
+
return { strokeFirst };
|
|
3319
|
+
};
|
|
3320
|
+
|
|
3311
3321
|
// src/drawing/text/parse-text-shadow.ts
|
|
3312
3322
|
var parseTextShadow = (textShadowValue) => {
|
|
3313
3323
|
return parseShadowValues(textShadowValue);
|
|
@@ -3325,12 +3335,16 @@ var drawText = ({
|
|
|
3325
3335
|
fontFamily,
|
|
3326
3336
|
fontSize,
|
|
3327
3337
|
fontWeight,
|
|
3338
|
+
fontStyle,
|
|
3328
3339
|
direction,
|
|
3329
3340
|
writingMode,
|
|
3330
3341
|
letterSpacing,
|
|
3331
3342
|
textTransform,
|
|
3332
3343
|
webkitTextFillColor,
|
|
3333
|
-
|
|
3344
|
+
webkitTextStrokeWidth,
|
|
3345
|
+
webkitTextStrokeColor,
|
|
3346
|
+
textShadow: textShadowValue,
|
|
3347
|
+
paintOrder
|
|
3334
3348
|
} = computedStyle;
|
|
3335
3349
|
const isVertical = writingMode !== "horizontal-tb";
|
|
3336
3350
|
if (isVertical) {
|
|
@@ -3342,9 +3356,15 @@ var drawText = ({
|
|
|
3342
3356
|
}
|
|
3343
3357
|
contextToDraw.save();
|
|
3344
3358
|
const fontSizePx = parseFloat(fontSize);
|
|
3345
|
-
contextToDraw.font = `${fontWeight} ${fontSizePx}px ${fontFamily}`;
|
|
3359
|
+
contextToDraw.font = `${fontStyle} ${fontWeight} ${fontSizePx}px ${fontFamily}`;
|
|
3346
3360
|
contextToDraw.fillStyle = onlyBackgroundClipText ? "black" : webkitTextFillColor;
|
|
3347
3361
|
contextToDraw.letterSpacing = letterSpacing;
|
|
3362
|
+
const strokeWidth = parseFloat(webkitTextStrokeWidth);
|
|
3363
|
+
const hasStroke = strokeWidth > 0;
|
|
3364
|
+
if (hasStroke) {
|
|
3365
|
+
contextToDraw.strokeStyle = webkitTextStrokeColor;
|
|
3366
|
+
contextToDraw.lineWidth = strokeWidth;
|
|
3367
|
+
}
|
|
3348
3368
|
const isRTL = direction === "rtl";
|
|
3349
3369
|
contextToDraw.textAlign = isRTL ? "right" : "left";
|
|
3350
3370
|
contextToDraw.textBaseline = "alphabetic";
|
|
@@ -3353,6 +3373,7 @@ var drawText = ({
|
|
|
3353
3373
|
span.textContent = transformedText;
|
|
3354
3374
|
const tokens = findWords(span);
|
|
3355
3375
|
const textShadows = parseTextShadow(textShadowValue);
|
|
3376
|
+
const { strokeFirst } = parsePaintOrder(paintOrder);
|
|
3356
3377
|
for (const token of tokens) {
|
|
3357
3378
|
const measurements = contextToDraw.measureText(originalText);
|
|
3358
3379
|
const { fontBoundingBoxDescent, fontBoundingBoxAscent } = measurements;
|
|
@@ -3373,7 +3394,19 @@ var drawText = ({
|
|
|
3373
3394
|
contextToDraw.shadowBlur = 0;
|
|
3374
3395
|
contextToDraw.shadowOffsetX = 0;
|
|
3375
3396
|
contextToDraw.shadowOffsetY = 0;
|
|
3376
|
-
contextToDraw.fillText(token.text, x, y);
|
|
3397
|
+
const drawFill = () => contextToDraw.fillText(token.text, x, y);
|
|
3398
|
+
const drawStroke = () => {
|
|
3399
|
+
if (hasStroke) {
|
|
3400
|
+
contextToDraw.strokeText(token.text, x, y);
|
|
3401
|
+
}
|
|
3402
|
+
};
|
|
3403
|
+
if (strokeFirst) {
|
|
3404
|
+
drawStroke();
|
|
3405
|
+
drawFill();
|
|
3406
|
+
} else {
|
|
3407
|
+
drawFill();
|
|
3408
|
+
drawStroke();
|
|
3409
|
+
}
|
|
3377
3410
|
}
|
|
3378
3411
|
span.textContent = originalText;
|
|
3379
3412
|
contextToDraw.restore();
|
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { canEncodeAudio, QUALITY_MEDIUM, } from 'mediabunny';
|
|
2
|
+
export const getDefaultAudioEncodingConfig = async () => {
|
|
3
|
+
const preferredDefaultAudioEncodingConfig = {
|
|
4
|
+
codec: 'aac',
|
|
5
|
+
bitrate: QUALITY_MEDIUM,
|
|
6
|
+
};
|
|
7
|
+
if (await canEncodeAudio(preferredDefaultAudioEncodingConfig.codec, preferredDefaultAudioEncodingConfig)) {
|
|
8
|
+
return preferredDefaultAudioEncodingConfig;
|
|
9
|
+
}
|
|
10
|
+
const backupDefaultAudioEncodingConfig = {
|
|
11
|
+
codec: 'opus',
|
|
12
|
+
bitrate: QUALITY_MEDIUM,
|
|
13
|
+
};
|
|
14
|
+
if (await canEncodeAudio(backupDefaultAudioEncodingConfig.codec, backupDefaultAudioEncodingConfig)) {
|
|
15
|
+
return backupDefaultAudioEncodingConfig;
|
|
16
|
+
}
|
|
17
|
+
return null;
|
|
18
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { parseBoxShadow } from './drawing/draw-box-shadow';
|
|
2
|
+
import { parseOutlineOffset, parseOutlineWidth } from './drawing/draw-outline';
|
|
3
|
+
import { skipToNextNonDescendant } from './walk-tree';
|
|
4
|
+
export const getBiggestBoundingClientRect = (element) => {
|
|
5
|
+
const treeWalker = document.createTreeWalker(element, NodeFilter.SHOW_ELEMENT);
|
|
6
|
+
let mostLeft = Infinity;
|
|
7
|
+
let mostTop = Infinity;
|
|
8
|
+
let mostRight = -Infinity;
|
|
9
|
+
let mostBottom = -Infinity;
|
|
10
|
+
while (true) {
|
|
11
|
+
const computedStyle = getComputedStyle(treeWalker.currentNode);
|
|
12
|
+
const outlineWidth = parseOutlineWidth(computedStyle.outlineWidth);
|
|
13
|
+
const outlineOffset = parseOutlineOffset(computedStyle.outlineOffset);
|
|
14
|
+
const rect = treeWalker.currentNode.getBoundingClientRect();
|
|
15
|
+
// Calculate box shadow extensions
|
|
16
|
+
const shadows = parseBoxShadow(computedStyle.boxShadow);
|
|
17
|
+
let shadowLeft = 0;
|
|
18
|
+
let shadowRight = 0;
|
|
19
|
+
let shadowTop = 0;
|
|
20
|
+
let shadowBottom = 0;
|
|
21
|
+
for (const shadow of shadows) {
|
|
22
|
+
if (!shadow.inset) {
|
|
23
|
+
shadowLeft = Math.max(shadowLeft, Math.abs(Math.min(shadow.offsetX, 0)) + shadow.blurRadius);
|
|
24
|
+
shadowRight = Math.max(shadowRight, Math.max(shadow.offsetX, 0) + shadow.blurRadius);
|
|
25
|
+
shadowTop = Math.max(shadowTop, Math.abs(Math.min(shadow.offsetY, 0)) + shadow.blurRadius);
|
|
26
|
+
shadowBottom = Math.max(shadowBottom, Math.max(shadow.offsetY, 0) + shadow.blurRadius);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
mostLeft = Math.min(mostLeft, rect.left - outlineOffset - outlineWidth - shadowLeft);
|
|
30
|
+
mostTop = Math.min(mostTop, rect.top - outlineOffset - outlineWidth - shadowTop);
|
|
31
|
+
mostRight = Math.max(mostRight, rect.right + outlineOffset + outlineWidth + shadowRight);
|
|
32
|
+
mostBottom = Math.max(mostBottom, rect.bottom + outlineOffset + outlineWidth + shadowBottom);
|
|
33
|
+
if (computedStyle.overflow === 'hidden') {
|
|
34
|
+
if (!skipToNextNonDescendant(treeWalker)) {
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (!treeWalker.nextNode()) {
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return new DOMRect(mostLeft, mostTop, mostRight - mostLeft, mostBottom - mostTop);
|
|
43
|
+
};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export const makeInternalState = () => {
|
|
2
|
+
let drawnPrecomposedPixels = 0;
|
|
3
|
+
let precomposedTextures = 0;
|
|
4
|
+
let waitForReadyTime = 0;
|
|
5
|
+
let addSampleTime = 0;
|
|
6
|
+
let createFrameTime = 0;
|
|
7
|
+
const helperCanvasState = {
|
|
8
|
+
current: null,
|
|
9
|
+
};
|
|
10
|
+
return {
|
|
11
|
+
getDrawn3dPixels: () => drawnPrecomposedPixels,
|
|
12
|
+
getPrecomposedTiles: () => precomposedTextures,
|
|
13
|
+
addPrecompose: ({ canvasWidth, canvasHeight, }) => {
|
|
14
|
+
drawnPrecomposedPixels += canvasWidth * canvasHeight;
|
|
15
|
+
precomposedTextures++;
|
|
16
|
+
},
|
|
17
|
+
helperCanvasState,
|
|
18
|
+
cleanup: () => {
|
|
19
|
+
if (helperCanvasState.current) {
|
|
20
|
+
helperCanvasState.current.cleanup();
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
getWaitForReadyTime: () => waitForReadyTime,
|
|
24
|
+
addWaitForReadyTime: (time) => {
|
|
25
|
+
waitForReadyTime += time;
|
|
26
|
+
},
|
|
27
|
+
getAddSampleTime: () => addSampleTime,
|
|
28
|
+
addAddSampleTime: (time) => {
|
|
29
|
+
addSampleTime += time;
|
|
30
|
+
},
|
|
31
|
+
getCreateFrameTime: () => createFrameTime,
|
|
32
|
+
addCreateFrameTime: (time) => {
|
|
33
|
+
createFrameTime += time;
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
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
|
+
};
|
|
54
|
+
export const getMimeType = (container) => {
|
|
55
|
+
switch (container) {
|
|
56
|
+
case 'mp4':
|
|
57
|
+
return 'video/mp4';
|
|
58
|
+
case 'webm':
|
|
59
|
+
return 'video/webm';
|
|
60
|
+
default:
|
|
61
|
+
throw new Error(`Unsupported container: ${container}`);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import { AudioSampleSource, BufferTarget, Output, StreamTarget, VideoSampleSource, } from 'mediabunny';
|
|
2
|
+
import { Internals } from 'remotion';
|
|
3
|
+
import { addAudioSample, addVideoSampleAndCloseFrame } from './add-sample';
|
|
4
|
+
import { handleArtifacts } from './artifact';
|
|
5
|
+
import { onlyInlineAudio } from './audio';
|
|
6
|
+
import { canUseWebFsWriter } from './can-use-webfs-target';
|
|
7
|
+
import { createScaffold } from './create-scaffold';
|
|
8
|
+
import { getRealFrameRange } from './frame-range';
|
|
9
|
+
import { getDefaultAudioEncodingConfig } from './get-audio-encoding-config';
|
|
10
|
+
import { makeInternalState } from './internal-state';
|
|
11
|
+
import { codecToMediabunnyCodec, containerToMediabunnyContainer, getDefaultVideoCodecForContainer, getMimeType, getQualityForWebRendererQuality, } from './mediabunny-mappings';
|
|
12
|
+
import { onlyOneRenderAtATimeQueue } from './render-operations-queue';
|
|
13
|
+
import { sendUsageEvent } from './send-telemetry-event';
|
|
14
|
+
import { createFrame } from './take-screenshot';
|
|
15
|
+
import { createThrottledProgressCallback } from './throttle-progress';
|
|
16
|
+
import { validateVideoFrame } from './validate-video-frame';
|
|
17
|
+
import { waitForReady } from './wait-for-ready';
|
|
18
|
+
import { cleanupStaleOpfsFiles, createWebFsTarget } from './web-fs-target';
|
|
19
|
+
// TODO: More containers
|
|
20
|
+
// TODO: Audio
|
|
21
|
+
// TODO: Metadata
|
|
22
|
+
// TODO: Validating inputs
|
|
23
|
+
// TODO: Apply defaultCodec
|
|
24
|
+
const internalRenderMediaOnWeb = async ({ composition, inputProps, delayRenderTimeoutInMilliseconds, logLevel, mediaCacheSizeInBytes, schema, videoCodec: codec, container, signal, onProgress, hardwareAcceleration, keyframeIntervalInSeconds, videoBitrate, frameRange, transparent, onArtifact, onFrame, outputTarget: userDesiredOutputTarget, licenseKey, muted, }) => {
|
|
25
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
26
|
+
const outputTarget = userDesiredOutputTarget === null
|
|
27
|
+
? (await canUseWebFsWriter())
|
|
28
|
+
? 'web-fs'
|
|
29
|
+
: 'arraybuffer'
|
|
30
|
+
: userDesiredOutputTarget;
|
|
31
|
+
if (outputTarget === 'web-fs') {
|
|
32
|
+
await cleanupStaleOpfsFiles();
|
|
33
|
+
}
|
|
34
|
+
const cleanupFns = [];
|
|
35
|
+
const format = containerToMediabunnyContainer(container);
|
|
36
|
+
if (codec &&
|
|
37
|
+
!format.getSupportedCodecs().includes(codecToMediabunnyCodec(codec))) {
|
|
38
|
+
return Promise.reject(new Error(`Codec ${codec} is not supported for container ${container}`));
|
|
39
|
+
}
|
|
40
|
+
const resolved = await Internals.resolveVideoConfig({
|
|
41
|
+
calculateMetadata: (_a = composition.calculateMetadata) !== null && _a !== void 0 ? _a : null,
|
|
42
|
+
signal: signal !== null && signal !== void 0 ? signal : new AbortController().signal,
|
|
43
|
+
defaultProps: (_b = composition.defaultProps) !== null && _b !== void 0 ? _b : {},
|
|
44
|
+
inputProps: inputProps !== null && inputProps !== void 0 ? inputProps : {},
|
|
45
|
+
compositionId: composition.id,
|
|
46
|
+
compositionDurationInFrames: (_c = composition.durationInFrames) !== null && _c !== void 0 ? _c : null,
|
|
47
|
+
compositionFps: (_d = composition.fps) !== null && _d !== void 0 ? _d : null,
|
|
48
|
+
compositionHeight: (_e = composition.height) !== null && _e !== void 0 ? _e : null,
|
|
49
|
+
compositionWidth: (_f = composition.width) !== null && _f !== void 0 ? _f : null,
|
|
50
|
+
});
|
|
51
|
+
const realFrameRange = getRealFrameRange(resolved.durationInFrames, frameRange);
|
|
52
|
+
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
53
|
+
return Promise.reject(new Error('renderMediaOnWeb() was cancelled'));
|
|
54
|
+
}
|
|
55
|
+
const { delayRenderScope, div, cleanupScaffold, timeUpdater, collectAssets } = await createScaffold({
|
|
56
|
+
width: resolved.width,
|
|
57
|
+
height: resolved.height,
|
|
58
|
+
fps: resolved.fps,
|
|
59
|
+
durationInFrames: resolved.durationInFrames,
|
|
60
|
+
Component: composition.component,
|
|
61
|
+
resolvedProps: resolved.props,
|
|
62
|
+
id: resolved.id,
|
|
63
|
+
delayRenderTimeoutInMilliseconds,
|
|
64
|
+
logLevel,
|
|
65
|
+
mediaCacheSizeInBytes,
|
|
66
|
+
schema: schema !== null && schema !== void 0 ? schema : null,
|
|
67
|
+
audioEnabled: !muted,
|
|
68
|
+
videoEnabled: true,
|
|
69
|
+
initialFrame: 0,
|
|
70
|
+
defaultCodec: resolved.defaultCodec,
|
|
71
|
+
defaultOutName: resolved.defaultOutName,
|
|
72
|
+
});
|
|
73
|
+
const internalState = makeInternalState();
|
|
74
|
+
const artifactsHandler = handleArtifacts();
|
|
75
|
+
cleanupFns.push(() => {
|
|
76
|
+
cleanupScaffold();
|
|
77
|
+
});
|
|
78
|
+
const webFsTarget = outputTarget === 'web-fs' ? await createWebFsTarget() : null;
|
|
79
|
+
const target = webFsTarget
|
|
80
|
+
? new StreamTarget(webFsTarget.stream)
|
|
81
|
+
: new BufferTarget();
|
|
82
|
+
const output = new Output({
|
|
83
|
+
format,
|
|
84
|
+
target,
|
|
85
|
+
});
|
|
86
|
+
try {
|
|
87
|
+
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
88
|
+
throw new Error('renderMediaOnWeb() was cancelled');
|
|
89
|
+
}
|
|
90
|
+
await waitForReady({
|
|
91
|
+
timeoutInMilliseconds: delayRenderTimeoutInMilliseconds,
|
|
92
|
+
scope: delayRenderScope,
|
|
93
|
+
signal,
|
|
94
|
+
apiName: 'renderMediaOnWeb',
|
|
95
|
+
internalState,
|
|
96
|
+
});
|
|
97
|
+
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
98
|
+
throw new Error('renderMediaOnWeb() was cancelled');
|
|
99
|
+
}
|
|
100
|
+
cleanupFns.push(() => {
|
|
101
|
+
if (output.state === 'finalized' || output.state === 'canceled') {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
output.cancel();
|
|
105
|
+
});
|
|
106
|
+
const videoSampleSource = new VideoSampleSource({
|
|
107
|
+
codec: codecToMediabunnyCodec(codec),
|
|
108
|
+
bitrate: typeof videoBitrate === 'number'
|
|
109
|
+
? videoBitrate
|
|
110
|
+
: getQualityForWebRendererQuality(videoBitrate),
|
|
111
|
+
sizeChangeBehavior: 'deny',
|
|
112
|
+
hardwareAcceleration,
|
|
113
|
+
latencyMode: 'quality',
|
|
114
|
+
keyFrameInterval: keyframeIntervalInSeconds,
|
|
115
|
+
alpha: transparent ? 'keep' : 'discard',
|
|
116
|
+
});
|
|
117
|
+
cleanupFns.push(() => {
|
|
118
|
+
videoSampleSource.close();
|
|
119
|
+
});
|
|
120
|
+
output.addVideoTrack(videoSampleSource);
|
|
121
|
+
// TODO: Should be able to customize
|
|
122
|
+
let audioSampleSource = null;
|
|
123
|
+
if (!muted) {
|
|
124
|
+
const defaultAudioEncodingConfig = await getDefaultAudioEncodingConfig();
|
|
125
|
+
if (!defaultAudioEncodingConfig) {
|
|
126
|
+
return Promise.reject(new Error('No default audio encoding config found'));
|
|
127
|
+
}
|
|
128
|
+
audioSampleSource = new AudioSampleSource(defaultAudioEncodingConfig);
|
|
129
|
+
cleanupFns.push(() => {
|
|
130
|
+
audioSampleSource === null || audioSampleSource === void 0 ? void 0 : audioSampleSource.close();
|
|
131
|
+
});
|
|
132
|
+
output.addAudioTrack(audioSampleSource);
|
|
133
|
+
}
|
|
134
|
+
await output.start();
|
|
135
|
+
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
136
|
+
throw new Error('renderMediaOnWeb() was cancelled');
|
|
137
|
+
}
|
|
138
|
+
const progress = {
|
|
139
|
+
renderedFrames: 0,
|
|
140
|
+
encodedFrames: 0,
|
|
141
|
+
};
|
|
142
|
+
const throttledOnProgress = createThrottledProgressCallback(onProgress);
|
|
143
|
+
for (let frame = realFrameRange[0]; frame <= realFrameRange[1]; frame++) {
|
|
144
|
+
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
145
|
+
throw new Error('renderMediaOnWeb() was cancelled');
|
|
146
|
+
}
|
|
147
|
+
(_g = timeUpdater.current) === null || _g === void 0 ? void 0 : _g.update(frame);
|
|
148
|
+
await waitForReady({
|
|
149
|
+
timeoutInMilliseconds: delayRenderTimeoutInMilliseconds,
|
|
150
|
+
scope: delayRenderScope,
|
|
151
|
+
signal,
|
|
152
|
+
apiName: 'renderMediaOnWeb',
|
|
153
|
+
internalState,
|
|
154
|
+
});
|
|
155
|
+
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
156
|
+
throw new Error('renderMediaOnWeb() was cancelled');
|
|
157
|
+
}
|
|
158
|
+
const createFrameStart = performance.now();
|
|
159
|
+
const imageData = await createFrame({
|
|
160
|
+
div,
|
|
161
|
+
width: resolved.width,
|
|
162
|
+
height: resolved.height,
|
|
163
|
+
logLevel,
|
|
164
|
+
internalState,
|
|
165
|
+
});
|
|
166
|
+
internalState.addCreateFrameTime(performance.now() - createFrameStart);
|
|
167
|
+
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
168
|
+
throw new Error('renderMediaOnWeb() was cancelled');
|
|
169
|
+
}
|
|
170
|
+
const assets = collectAssets.current.collectAssets();
|
|
171
|
+
if (onArtifact) {
|
|
172
|
+
await artifactsHandler.handle({
|
|
173
|
+
imageData,
|
|
174
|
+
frame,
|
|
175
|
+
assets,
|
|
176
|
+
onArtifact,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
180
|
+
throw new Error('renderMediaOnWeb() was cancelled');
|
|
181
|
+
}
|
|
182
|
+
const audio = muted
|
|
183
|
+
? null
|
|
184
|
+
: onlyInlineAudio({ assets, fps: resolved.fps, frame });
|
|
185
|
+
const timestamp = Math.round(((frame - realFrameRange[0]) / resolved.fps) * 1000000);
|
|
186
|
+
const videoFrame = new VideoFrame(imageData, {
|
|
187
|
+
timestamp,
|
|
188
|
+
});
|
|
189
|
+
progress.renderedFrames++;
|
|
190
|
+
throttledOnProgress === null || throttledOnProgress === void 0 ? void 0 : throttledOnProgress({ ...progress });
|
|
191
|
+
// Process frame through onFrame callback if provided
|
|
192
|
+
let frameToEncode = videoFrame;
|
|
193
|
+
if (onFrame) {
|
|
194
|
+
const returnedFrame = await onFrame(videoFrame);
|
|
195
|
+
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
196
|
+
throw new Error('renderMediaOnWeb() was cancelled');
|
|
197
|
+
}
|
|
198
|
+
frameToEncode = validateVideoFrame({
|
|
199
|
+
originalFrame: videoFrame,
|
|
200
|
+
returnedFrame,
|
|
201
|
+
expectedWidth: resolved.width,
|
|
202
|
+
expectedHeight: resolved.height,
|
|
203
|
+
expectedTimestamp: timestamp,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
const addSampleStart = performance.now();
|
|
207
|
+
await Promise.all([
|
|
208
|
+
addVideoSampleAndCloseFrame(frameToEncode, videoSampleSource),
|
|
209
|
+
audio && audioSampleSource
|
|
210
|
+
? addAudioSample(audio, audioSampleSource)
|
|
211
|
+
: Promise.resolve(),
|
|
212
|
+
]);
|
|
213
|
+
internalState.addAddSampleTime(performance.now() - addSampleStart);
|
|
214
|
+
progress.encodedFrames++;
|
|
215
|
+
throttledOnProgress === null || throttledOnProgress === void 0 ? void 0 : throttledOnProgress({ ...progress });
|
|
216
|
+
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
217
|
+
throw new Error('renderMediaOnWeb() was cancelled');
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
// Call progress one final time to ensure final state is reported
|
|
221
|
+
onProgress === null || onProgress === void 0 ? void 0 : onProgress({ ...progress });
|
|
222
|
+
videoSampleSource.close();
|
|
223
|
+
audioSampleSource === null || audioSampleSource === void 0 ? void 0 : audioSampleSource.close();
|
|
224
|
+
await output.finalize();
|
|
225
|
+
Internals.Log.verbose({ logLevel, tag: 'web-renderer' }, `Render timings: waitForReady=${internalState.getWaitForReadyTime().toFixed(2)}ms, createFrame=${internalState.getCreateFrameTime().toFixed(2)}ms, addSample=${internalState.getAddSampleTime().toFixed(2)}ms`);
|
|
226
|
+
const mimeType = getMimeType(container);
|
|
227
|
+
if (webFsTarget) {
|
|
228
|
+
sendUsageEvent({
|
|
229
|
+
licenseKey: licenseKey !== null && licenseKey !== void 0 ? licenseKey : null,
|
|
230
|
+
succeeded: true,
|
|
231
|
+
apiName: 'renderMediaOnWeb',
|
|
232
|
+
});
|
|
233
|
+
await webFsTarget.close();
|
|
234
|
+
return {
|
|
235
|
+
getBlob: () => {
|
|
236
|
+
return webFsTarget.getBlob();
|
|
237
|
+
},
|
|
238
|
+
internalState,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
if (!(target instanceof BufferTarget)) {
|
|
242
|
+
throw new Error('Expected target to be a BufferTarget');
|
|
243
|
+
}
|
|
244
|
+
sendUsageEvent({
|
|
245
|
+
licenseKey: licenseKey !== null && licenseKey !== void 0 ? licenseKey : null,
|
|
246
|
+
succeeded: true,
|
|
247
|
+
apiName: 'renderMediaOnWeb',
|
|
248
|
+
});
|
|
249
|
+
return {
|
|
250
|
+
getBlob: () => {
|
|
251
|
+
if (!target.buffer) {
|
|
252
|
+
throw new Error('The resulting buffer is empty');
|
|
253
|
+
}
|
|
254
|
+
return Promise.resolve(new Blob([target.buffer], { type: mimeType }));
|
|
255
|
+
},
|
|
256
|
+
internalState,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
catch (err) {
|
|
260
|
+
sendUsageEvent({
|
|
261
|
+
succeeded: false,
|
|
262
|
+
licenseKey: licenseKey !== null && licenseKey !== void 0 ? licenseKey : null,
|
|
263
|
+
apiName: 'renderMediaOnWeb',
|
|
264
|
+
}).catch((err2) => {
|
|
265
|
+
Internals.Log.error({ logLevel: 'error', tag: 'web-renderer' }, 'Failed to send usage event', err2);
|
|
266
|
+
});
|
|
267
|
+
throw err;
|
|
268
|
+
}
|
|
269
|
+
finally {
|
|
270
|
+
cleanupFns.forEach((fn) => fn());
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
export const renderMediaOnWeb = (options) => {
|
|
274
|
+
var _a, _b;
|
|
275
|
+
const container = (_a = options.container) !== null && _a !== void 0 ? _a : 'mp4';
|
|
276
|
+
const codec = (_b = options.videoCodec) !== null && _b !== void 0 ? _b : getDefaultVideoCodecForContainer(container);
|
|
277
|
+
onlyOneRenderAtATimeQueue.ref = onlyOneRenderAtATimeQueue.ref
|
|
278
|
+
.catch(() => Promise.resolve())
|
|
279
|
+
.then(() => {
|
|
280
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s;
|
|
281
|
+
return internalRenderMediaOnWeb({
|
|
282
|
+
...options,
|
|
283
|
+
delayRenderTimeoutInMilliseconds: (_a = options.delayRenderTimeoutInMilliseconds) !== null && _a !== void 0 ? _a : 30000,
|
|
284
|
+
logLevel: (_c = (_b = options.logLevel) !== null && _b !== void 0 ? _b : window.remotion_logLevel) !== null && _c !== void 0 ? _c : 'info',
|
|
285
|
+
schema: (_d = options.schema) !== null && _d !== void 0 ? _d : undefined,
|
|
286
|
+
mediaCacheSizeInBytes: (_e = options.mediaCacheSizeInBytes) !== null && _e !== void 0 ? _e : null,
|
|
287
|
+
videoCodec: codec,
|
|
288
|
+
container,
|
|
289
|
+
signal: (_f = options.signal) !== null && _f !== void 0 ? _f : null,
|
|
290
|
+
onProgress: (_g = options.onProgress) !== null && _g !== void 0 ? _g : null,
|
|
291
|
+
hardwareAcceleration: (_h = options.hardwareAcceleration) !== null && _h !== void 0 ? _h : 'no-preference',
|
|
292
|
+
keyframeIntervalInSeconds: (_j = options.keyframeIntervalInSeconds) !== null && _j !== void 0 ? _j : 5,
|
|
293
|
+
videoBitrate: (_k = options.videoBitrate) !== null && _k !== void 0 ? _k : 'medium',
|
|
294
|
+
frameRange: (_l = options.frameRange) !== null && _l !== void 0 ? _l : null,
|
|
295
|
+
transparent: (_m = options.transparent) !== null && _m !== void 0 ? _m : false,
|
|
296
|
+
onArtifact: (_o = options.onArtifact) !== null && _o !== void 0 ? _o : null,
|
|
297
|
+
onFrame: (_p = options.onFrame) !== null && _p !== void 0 ? _p : null,
|
|
298
|
+
outputTarget: (_q = options.outputTarget) !== null && _q !== void 0 ? _q : null,
|
|
299
|
+
licenseKey: (_r = options.licenseKey) !== null && _r !== void 0 ? _r : undefined,
|
|
300
|
+
muted: (_s = options.muted) !== null && _s !== void 0 ? _s : false,
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
return onlyOneRenderAtATimeQueue.ref;
|
|
304
|
+
};
|