@remotion/webcodecs 4.0.309 → 4.0.311
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/can-copy-video-track.js +2 -1
- package/dist/can-reencode-video-track.js +1 -1
- package/dist/create-frames.d.ts +8 -0
- package/dist/create-frames.js +69 -0
- package/dist/create-video-decoder.d.ts +1 -1
- package/dist/esm/index.mjs +129 -15
- package/dist/extract-frames.d.ts +13 -0
- package/dist/extract-frames.js +114 -0
- package/dist/index.d.ts +6 -4
- package/dist/index.js +3 -1
- package/dist/io-manager/io-synchronizer.js +3 -1
- package/dist/on-frame.js +1 -1
- package/dist/reencode-audio-track.js +2 -1
- package/dist/reencode-video-track.js +3 -3
- package/dist/resizing/calculate-new-size.d.ts +3 -4
- package/dist/resizing/calculate-new-size.js +1 -2
- package/dist/rotate-and-resize-video-frame.d.ts +2 -3
- package/dist/rotate-and-resize-video-frame.js +2 -2
- package/dist/rotation.d.ts +3 -4
- package/dist/rotation.js +2 -2
- package/package.json +6 -5
|
@@ -17,12 +17,13 @@ const canCopyVideoTrack = ({ outputContainer, rotationToApply, inputContainer, r
|
|
|
17
17
|
return false;
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
|
+
const needsToBeMultipleOfTwo = inputTrack.codecEnum === 'h264';
|
|
20
21
|
const newDimensions = (0, rotation_1.calculateNewDimensionsFromRotateAndScale)({
|
|
21
22
|
height: inputTrack.height,
|
|
22
23
|
resizeOperation,
|
|
23
24
|
rotation: rotationToApply,
|
|
24
|
-
videoCodec: inputTrack.codecEnum,
|
|
25
25
|
width: inputTrack.width,
|
|
26
|
+
needsToBeMultipleOfTwo,
|
|
26
27
|
});
|
|
27
28
|
if (newDimensions.height !== inputTrack.height ||
|
|
28
29
|
newDimensions.width !== inputTrack.width) {
|
|
@@ -9,7 +9,7 @@ const canReencodeVideoTrack = async ({ videoCodec, track, resizeOperation, rotat
|
|
|
9
9
|
height: track.displayAspectHeight,
|
|
10
10
|
resizeOperation,
|
|
11
11
|
rotation: rotate ?? 0,
|
|
12
|
-
videoCodec,
|
|
12
|
+
needsToBeMultipleOfTwo: videoCodec === 'h264',
|
|
13
13
|
width: track.displayAspectWidth,
|
|
14
14
|
});
|
|
15
15
|
const videoEncoderConfig = await (0, video_encoder_config_1.getVideoEncoderConfig)({
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.extractFrames = void 0;
|
|
4
|
+
const media_parser_1 = require("@remotion/media-parser");
|
|
5
|
+
const worker_1 = require("@remotion/media-parser/worker");
|
|
6
|
+
const create_video_decoder_1 = require("./create-video-decoder");
|
|
7
|
+
const extractFrames = async ({ fromSeconds, toSeconds, width, height, src, onFrame, }) => {
|
|
8
|
+
const controller = (0, media_parser_1.mediaParserController)();
|
|
9
|
+
controller.seek(fromSeconds);
|
|
10
|
+
const segmentDuration = toSeconds - fromSeconds;
|
|
11
|
+
const expectedFrames = [];
|
|
12
|
+
try {
|
|
13
|
+
await (0, worker_1.parseMediaOnWebWorker)({
|
|
14
|
+
src: new URL(src, window.location.href).toString(),
|
|
15
|
+
acknowledgeRemotionLicense: true,
|
|
16
|
+
controller,
|
|
17
|
+
onVideoTrack: ({ track }) => {
|
|
18
|
+
const aspectRatio = track.width / track.height;
|
|
19
|
+
const framesFitInWidth = Math.ceil(width / (height * aspectRatio));
|
|
20
|
+
const timestampTargets = [];
|
|
21
|
+
for (let i = 0; i < framesFitInWidth; i++) {
|
|
22
|
+
timestampTargets.push(fromSeconds +
|
|
23
|
+
((segmentDuration * media_parser_1.WEBCODECS_TIMESCALE) / framesFitInWidth) *
|
|
24
|
+
(i + 0.5));
|
|
25
|
+
}
|
|
26
|
+
const decoder = (0, create_video_decoder_1.createVideoDecoder)({
|
|
27
|
+
onFrame: (frame) => {
|
|
28
|
+
if (frame.timestamp >= expectedFrames[0] - 1) {
|
|
29
|
+
expectedFrames.shift();
|
|
30
|
+
onFrame(frame);
|
|
31
|
+
}
|
|
32
|
+
frame.close();
|
|
33
|
+
},
|
|
34
|
+
onError: console.error,
|
|
35
|
+
track,
|
|
36
|
+
});
|
|
37
|
+
const queued = [];
|
|
38
|
+
return async (sample) => {
|
|
39
|
+
const nextTimestampWeWant = timestampTargets[0];
|
|
40
|
+
if (nextTimestampWeWant === undefined) {
|
|
41
|
+
throw new Error('this should not happen');
|
|
42
|
+
}
|
|
43
|
+
if (sample.type === 'key') {
|
|
44
|
+
queued.length = 0;
|
|
45
|
+
}
|
|
46
|
+
queued.push(sample);
|
|
47
|
+
if (sample.timestamp > nextTimestampWeWant) {
|
|
48
|
+
expectedFrames.push(timestampTargets.shift());
|
|
49
|
+
while (queued.length > 0) {
|
|
50
|
+
const sam = queued.shift();
|
|
51
|
+
await decoder.waitForQueueToBeLessThan(10);
|
|
52
|
+
await decoder.decode(sam);
|
|
53
|
+
}
|
|
54
|
+
if (timestampTargets.length === 0) {
|
|
55
|
+
await decoder.flush();
|
|
56
|
+
controller.abort();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
catch (e) {
|
|
64
|
+
if (!(0, media_parser_1.hasBeenAborted)(e)) {
|
|
65
|
+
throw e;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
exports.extractFrames = extractFrames;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { MediaParserLogLevel } from '@remotion/media-parser';
|
|
2
2
|
import type { WebCodecsController } from './webcodecs-controller';
|
|
3
3
|
export type WebCodecsVideoDecoder = {
|
|
4
|
-
decode: (videoSample: EncodedVideoChunkInit | EncodedVideoChunk) => void
|
|
4
|
+
decode: (videoSample: EncodedVideoChunkInit | EncodedVideoChunk) => Promise<void>;
|
|
5
5
|
close: () => void;
|
|
6
6
|
flush: () => Promise<void>;
|
|
7
7
|
waitForQueueToBeLessThan: (items: number) => Promise<void>;
|
package/dist/esm/index.mjs
CHANGED
|
@@ -244,9 +244,8 @@ var ensureMultipleOfTwo = ({
|
|
|
244
244
|
var calculateNewSizeAfterResizing = ({
|
|
245
245
|
dimensions,
|
|
246
246
|
resizeOperation,
|
|
247
|
-
|
|
247
|
+
needsToBeMultipleOfTwo
|
|
248
248
|
}) => {
|
|
249
|
-
const needsToBeMultipleOfTwo = videoCodec === "h264";
|
|
250
249
|
if (resizeOperation === null) {
|
|
251
250
|
return ensureMultipleOfTwo({
|
|
252
251
|
dimensions,
|
|
@@ -342,7 +341,7 @@ var calculateNewDimensionsFromRotateAndScale = ({
|
|
|
342
341
|
height,
|
|
343
342
|
rotation,
|
|
344
343
|
resizeOperation,
|
|
345
|
-
|
|
344
|
+
needsToBeMultipleOfTwo
|
|
346
345
|
}) => {
|
|
347
346
|
const { height: newHeight, width: newWidth } = calculateNewDimensionsFromRotate({
|
|
348
347
|
height,
|
|
@@ -352,7 +351,7 @@ var calculateNewDimensionsFromRotateAndScale = ({
|
|
|
352
351
|
return calculateNewSizeAfterResizing({
|
|
353
352
|
dimensions: { height: newHeight, width: newWidth },
|
|
354
353
|
resizeOperation,
|
|
355
|
-
|
|
354
|
+
needsToBeMultipleOfTwo
|
|
356
355
|
});
|
|
357
356
|
};
|
|
358
357
|
|
|
@@ -363,7 +362,7 @@ var normalizeVideoRotation = (rotation) => {
|
|
|
363
362
|
var rotateAndResizeVideoFrame = ({
|
|
364
363
|
frame,
|
|
365
364
|
rotation,
|
|
366
|
-
|
|
365
|
+
needsToBeMultipleOfTwo,
|
|
367
366
|
resizeOperation
|
|
368
367
|
}) => {
|
|
369
368
|
const normalized = (rotation % 360 + 360) % 360;
|
|
@@ -377,7 +376,7 @@ var rotateAndResizeVideoFrame = ({
|
|
|
377
376
|
height: frame.displayHeight,
|
|
378
377
|
width: frame.displayWidth,
|
|
379
378
|
rotation,
|
|
380
|
-
|
|
379
|
+
needsToBeMultipleOfTwo,
|
|
381
380
|
resizeOperation
|
|
382
381
|
});
|
|
383
382
|
if (normalized === 0 && height === frame.displayHeight && width === frame.displayWidth) {
|
|
@@ -540,7 +539,7 @@ var makeIoSynchronizer = ({
|
|
|
540
539
|
let inputsSinceLastOutput = 0;
|
|
541
540
|
let inputs = [];
|
|
542
541
|
const getQueuedItems = () => {
|
|
543
|
-
inputs = inputs.filter((input) => Math.floor(input) > Math.floor(lastOutput));
|
|
542
|
+
inputs = inputs.filter((input) => Math.floor(input) > Math.floor(lastOutput) + 1);
|
|
544
543
|
return inputs.length;
|
|
545
544
|
};
|
|
546
545
|
const printState = (prefix) => {
|
|
@@ -930,12 +929,13 @@ var canCopyVideoTrack = ({
|
|
|
930
929
|
return false;
|
|
931
930
|
}
|
|
932
931
|
}
|
|
932
|
+
const needsToBeMultipleOfTwo = inputTrack.codecEnum === "h264";
|
|
933
933
|
const newDimensions = calculateNewDimensionsFromRotateAndScale({
|
|
934
934
|
height: inputTrack.height,
|
|
935
935
|
resizeOperation,
|
|
936
936
|
rotation: rotationToApply,
|
|
937
|
-
|
|
938
|
-
|
|
937
|
+
width: inputTrack.width,
|
|
938
|
+
needsToBeMultipleOfTwo
|
|
939
939
|
});
|
|
940
940
|
if (newDimensions.height !== inputTrack.height || newDimensions.width !== inputTrack.width) {
|
|
941
941
|
return false;
|
|
@@ -1416,7 +1416,7 @@ var canReencodeVideoTrack = async ({
|
|
|
1416
1416
|
height: track.displayAspectHeight,
|
|
1417
1417
|
resizeOperation,
|
|
1418
1418
|
rotation: rotate ?? 0,
|
|
1419
|
-
videoCodec,
|
|
1419
|
+
needsToBeMultipleOfTwo: videoCodec === "h264",
|
|
1420
1420
|
width: track.displayAspectWidth
|
|
1421
1421
|
});
|
|
1422
1422
|
const videoEncoderConfig = await getVideoEncoderConfig({
|
|
@@ -4498,7 +4498,8 @@ var reencodeAudioTrack = async ({
|
|
|
4498
4498
|
logLevel
|
|
4499
4499
|
});
|
|
4500
4500
|
state.addWaitForFinishPromise(async () => {
|
|
4501
|
-
|
|
4501
|
+
Log.verbose(logLevel, "Waiting for audio decoder to finish");
|
|
4502
|
+
await audioDecoder.flush();
|
|
4502
4503
|
Log.verbose(logLevel, "Audio decoder finished");
|
|
4503
4504
|
audioDecoder.close();
|
|
4504
4505
|
await audioProcessingQueue.ioSynchronizer.waitForQueueSize(0);
|
|
@@ -4801,7 +4802,7 @@ var onFrame = async ({
|
|
|
4801
4802
|
rotation,
|
|
4802
4803
|
frame: unrotatedFrame,
|
|
4803
4804
|
resizeOperation,
|
|
4804
|
-
|
|
4805
|
+
needsToBeMultipleOfTwo: outputCodec === "h264"
|
|
4805
4806
|
});
|
|
4806
4807
|
if (unrotatedFrame !== rotated) {
|
|
4807
4808
|
unrotatedFrame.close();
|
|
@@ -4989,7 +4990,7 @@ var reencodeVideoTrack = async ({
|
|
|
4989
4990
|
width: track.codedWidth,
|
|
4990
4991
|
height: track.codedHeight,
|
|
4991
4992
|
rotation,
|
|
4992
|
-
|
|
4993
|
+
needsToBeMultipleOfTwo: videoOperation.videoCodec === "h264",
|
|
4993
4994
|
resizeOperation: videoOperation.resize ?? null
|
|
4994
4995
|
});
|
|
4995
4996
|
const videoEncoderConfig = await getVideoEncoderConfig({
|
|
@@ -5095,7 +5096,7 @@ var reencodeVideoTrack = async ({
|
|
|
5095
5096
|
});
|
|
5096
5097
|
state.addWaitForFinishPromise(async () => {
|
|
5097
5098
|
Log.verbose(logLevel, "Waiting for video decoder to finish");
|
|
5098
|
-
await videoDecoder.
|
|
5099
|
+
await videoDecoder.flush();
|
|
5099
5100
|
videoDecoder.close();
|
|
5100
5101
|
Log.verbose(logLevel, "Video decoder finished. Waiting for encoder to finish");
|
|
5101
5102
|
await frameSorter.flush();
|
|
@@ -5109,7 +5110,7 @@ var reencodeVideoTrack = async ({
|
|
|
5109
5110
|
return async (chunk) => {
|
|
5110
5111
|
progressTracker.setPossibleLowestTimestamp(Math.min(chunk.timestamp, chunk.decodingTimestamp ?? Infinity));
|
|
5111
5112
|
await controller._internals._mediaParserController._internals.checkForAbortAndPause();
|
|
5112
|
-
await videoDecoder.waitForQueueToBeLessThan(
|
|
5113
|
+
await videoDecoder.waitForQueueToBeLessThan(15);
|
|
5113
5114
|
if (chunk.type === "key") {
|
|
5114
5115
|
await videoDecoder.flush();
|
|
5115
5116
|
}
|
|
@@ -5499,6 +5500,118 @@ var convertMedia = async function({
|
|
|
5499
5500
|
controller._internals._mediaParserController._internals.signal.removeEventListener("abort", onUserAbort);
|
|
5500
5501
|
});
|
|
5501
5502
|
};
|
|
5503
|
+
// src/extract-frames.ts
|
|
5504
|
+
import {
|
|
5505
|
+
hasBeenAborted,
|
|
5506
|
+
MediaParserAbortError as MediaParserAbortError4,
|
|
5507
|
+
mediaParserController as mediaParserController2,
|
|
5508
|
+
WEBCODECS_TIMESCALE
|
|
5509
|
+
} from "@remotion/media-parser";
|
|
5510
|
+
import { parseMediaOnWebWorker } from "@remotion/media-parser/worker";
|
|
5511
|
+
var internalExtractFrames = ({
|
|
5512
|
+
src,
|
|
5513
|
+
onFrame: onFrame2,
|
|
5514
|
+
signal,
|
|
5515
|
+
timestampsInSeconds,
|
|
5516
|
+
acknowledgeRemotionLicense,
|
|
5517
|
+
logLevel
|
|
5518
|
+
}) => {
|
|
5519
|
+
const controller = mediaParserController2();
|
|
5520
|
+
const expectedFrames = [];
|
|
5521
|
+
const resolvers = withResolvers();
|
|
5522
|
+
const abortListener = () => {
|
|
5523
|
+
controller.abort();
|
|
5524
|
+
resolvers.reject(new MediaParserAbortError4("Aborted by user"));
|
|
5525
|
+
};
|
|
5526
|
+
signal?.addEventListener("abort", abortListener, { once: true });
|
|
5527
|
+
let dur = null;
|
|
5528
|
+
parseMediaOnWebWorker({
|
|
5529
|
+
src: new URL(src, window.location.href),
|
|
5530
|
+
acknowledgeRemotionLicense,
|
|
5531
|
+
controller,
|
|
5532
|
+
logLevel,
|
|
5533
|
+
onDurationInSeconds(durationInSeconds) {
|
|
5534
|
+
dur = durationInSeconds;
|
|
5535
|
+
},
|
|
5536
|
+
onVideoTrack: async ({ track }) => {
|
|
5537
|
+
const timestampTargetsUnsorted = typeof timestampsInSeconds === "function" ? await timestampsInSeconds({
|
|
5538
|
+
track,
|
|
5539
|
+
durationInSeconds: dur
|
|
5540
|
+
}) : timestampsInSeconds;
|
|
5541
|
+
const timestampTargets = timestampTargetsUnsorted.sort((a, b) => a - b);
|
|
5542
|
+
controller.seek(timestampTargets[0]);
|
|
5543
|
+
const decoder = createVideoDecoder({
|
|
5544
|
+
onFrame: (frame) => {
|
|
5545
|
+
if (frame.timestamp >= expectedFrames[0] - 1) {
|
|
5546
|
+
expectedFrames.shift();
|
|
5547
|
+
onFrame2(frame);
|
|
5548
|
+
} else {
|
|
5549
|
+
frame.close();
|
|
5550
|
+
}
|
|
5551
|
+
},
|
|
5552
|
+
onError: (e) => {
|
|
5553
|
+
controller.abort();
|
|
5554
|
+
try {
|
|
5555
|
+
decoder.close();
|
|
5556
|
+
} catch {}
|
|
5557
|
+
resolvers.reject(e);
|
|
5558
|
+
},
|
|
5559
|
+
track
|
|
5560
|
+
});
|
|
5561
|
+
const queued = [];
|
|
5562
|
+
const doProcess = async () => {
|
|
5563
|
+
expectedFrames.push(timestampTargets.shift() * WEBCODECS_TIMESCALE);
|
|
5564
|
+
while (queued.length > 0) {
|
|
5565
|
+
const sam = queued.shift();
|
|
5566
|
+
await decoder.waitForQueueToBeLessThan(10);
|
|
5567
|
+
await decoder.decode(sam);
|
|
5568
|
+
}
|
|
5569
|
+
};
|
|
5570
|
+
return async (sample) => {
|
|
5571
|
+
const nextTimestampWeWant = timestampTargets[0];
|
|
5572
|
+
if (sample.type === "key") {
|
|
5573
|
+
queued.length = 0;
|
|
5574
|
+
}
|
|
5575
|
+
queued.push(sample);
|
|
5576
|
+
if (sample.timestamp >= timestampTargets[timestampTargets.length - 1] * WEBCODECS_TIMESCALE) {
|
|
5577
|
+
await doProcess();
|
|
5578
|
+
await decoder.flush();
|
|
5579
|
+
controller.abort();
|
|
5580
|
+
return;
|
|
5581
|
+
}
|
|
5582
|
+
if (nextTimestampWeWant === undefined) {
|
|
5583
|
+
throw new Error("this should not happen");
|
|
5584
|
+
}
|
|
5585
|
+
if (sample.timestamp >= nextTimestampWeWant * WEBCODECS_TIMESCALE) {
|
|
5586
|
+
await doProcess();
|
|
5587
|
+
if (timestampTargets.length === 0) {
|
|
5588
|
+
await decoder.flush();
|
|
5589
|
+
controller.abort();
|
|
5590
|
+
}
|
|
5591
|
+
}
|
|
5592
|
+
};
|
|
5593
|
+
}
|
|
5594
|
+
}).then(() => {
|
|
5595
|
+
resolvers.resolve();
|
|
5596
|
+
}).catch((e) => {
|
|
5597
|
+
if (!hasBeenAborted(e)) {
|
|
5598
|
+
resolvers.reject(e);
|
|
5599
|
+
} else {
|
|
5600
|
+
resolvers.resolve();
|
|
5601
|
+
}
|
|
5602
|
+
}).finally(() => {
|
|
5603
|
+
signal?.removeEventListener("abort", abortListener);
|
|
5604
|
+
});
|
|
5605
|
+
return resolvers.promise;
|
|
5606
|
+
};
|
|
5607
|
+
var extractFrames = (options) => {
|
|
5608
|
+
return internalExtractFrames({
|
|
5609
|
+
...options,
|
|
5610
|
+
signal: options.signal ?? null,
|
|
5611
|
+
acknowledgeRemotionLicense: options.acknowledgeRemotionLicense ?? false,
|
|
5612
|
+
logLevel: options.logLevel ?? "info"
|
|
5613
|
+
});
|
|
5614
|
+
};
|
|
5502
5615
|
// src/get-available-audio-codecs.ts
|
|
5503
5616
|
var getAvailableAudioCodecs = ({
|
|
5504
5617
|
container
|
|
@@ -5529,6 +5642,7 @@ export {
|
|
|
5529
5642
|
getAvailableVideoCodecs,
|
|
5530
5643
|
getAvailableContainers,
|
|
5531
5644
|
getAvailableAudioCodecs,
|
|
5645
|
+
extractFrames,
|
|
5532
5646
|
defaultOnVideoTrackHandler,
|
|
5533
5647
|
defaultOnAudioTrackHandler,
|
|
5534
5648
|
createVideoEncoder,
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { MediaParserLogLevel, MediaParserVideoTrack } from '@remotion/media-parser';
|
|
2
|
+
export type ExtractFramesTimestampsInSecondsFn = (options: {
|
|
3
|
+
track: MediaParserVideoTrack;
|
|
4
|
+
durationInSeconds: number | null;
|
|
5
|
+
}) => Promise<number[]> | number[];
|
|
6
|
+
export declare const extractFrames: (options: {
|
|
7
|
+
src: string;
|
|
8
|
+
timestampsInSeconds: number[] | ExtractFramesTimestampsInSecondsFn;
|
|
9
|
+
onFrame: (frame: VideoFrame) => void;
|
|
10
|
+
signal?: AbortSignal;
|
|
11
|
+
acknowledgeRemotionLicense?: boolean;
|
|
12
|
+
logLevel?: MediaParserLogLevel;
|
|
13
|
+
}) => Promise<void>;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.extractFrames = void 0;
|
|
4
|
+
const media_parser_1 = require("@remotion/media-parser");
|
|
5
|
+
const worker_1 = require("@remotion/media-parser/worker");
|
|
6
|
+
const create_video_decoder_1 = require("./create-video-decoder");
|
|
7
|
+
const with_resolvers_1 = require("./create/with-resolvers");
|
|
8
|
+
const internalExtractFrames = ({ src, onFrame, signal, timestampsInSeconds, acknowledgeRemotionLicense, logLevel, }) => {
|
|
9
|
+
const controller = (0, media_parser_1.mediaParserController)();
|
|
10
|
+
const expectedFrames = [];
|
|
11
|
+
const resolvers = (0, with_resolvers_1.withResolvers)();
|
|
12
|
+
const abortListener = () => {
|
|
13
|
+
controller.abort();
|
|
14
|
+
resolvers.reject(new media_parser_1.MediaParserAbortError('Aborted by user'));
|
|
15
|
+
};
|
|
16
|
+
signal?.addEventListener('abort', abortListener, { once: true });
|
|
17
|
+
let dur = null;
|
|
18
|
+
(0, worker_1.parseMediaOnWebWorker)({
|
|
19
|
+
src: new URL(src, window.location.href),
|
|
20
|
+
acknowledgeRemotionLicense,
|
|
21
|
+
controller,
|
|
22
|
+
logLevel,
|
|
23
|
+
onDurationInSeconds(durationInSeconds) {
|
|
24
|
+
dur = durationInSeconds;
|
|
25
|
+
},
|
|
26
|
+
onVideoTrack: async ({ track }) => {
|
|
27
|
+
const timestampTargetsUnsorted = typeof timestampsInSeconds === 'function'
|
|
28
|
+
? await timestampsInSeconds({
|
|
29
|
+
track,
|
|
30
|
+
durationInSeconds: dur,
|
|
31
|
+
})
|
|
32
|
+
: timestampsInSeconds;
|
|
33
|
+
const timestampTargets = timestampTargetsUnsorted.sort((a, b) => a - b);
|
|
34
|
+
controller.seek(timestampTargets[0]);
|
|
35
|
+
const decoder = (0, create_video_decoder_1.createVideoDecoder)({
|
|
36
|
+
onFrame: (frame) => {
|
|
37
|
+
if (frame.timestamp >= expectedFrames[0] - 1) {
|
|
38
|
+
expectedFrames.shift();
|
|
39
|
+
onFrame(frame);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
frame.close();
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
onError: (e) => {
|
|
46
|
+
controller.abort();
|
|
47
|
+
try {
|
|
48
|
+
decoder.close();
|
|
49
|
+
}
|
|
50
|
+
catch { }
|
|
51
|
+
resolvers.reject(e);
|
|
52
|
+
},
|
|
53
|
+
track,
|
|
54
|
+
});
|
|
55
|
+
const queued = [];
|
|
56
|
+
const doProcess = async () => {
|
|
57
|
+
expectedFrames.push(timestampTargets.shift() * media_parser_1.WEBCODECS_TIMESCALE);
|
|
58
|
+
while (queued.length > 0) {
|
|
59
|
+
const sam = queued.shift();
|
|
60
|
+
await decoder.waitForQueueToBeLessThan(10);
|
|
61
|
+
await decoder.decode(sam);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
return async (sample) => {
|
|
65
|
+
const nextTimestampWeWant = timestampTargets[0];
|
|
66
|
+
if (sample.type === 'key') {
|
|
67
|
+
queued.length = 0;
|
|
68
|
+
}
|
|
69
|
+
queued.push(sample);
|
|
70
|
+
if (sample.timestamp >=
|
|
71
|
+
timestampTargets[timestampTargets.length - 1] * media_parser_1.WEBCODECS_TIMESCALE) {
|
|
72
|
+
await doProcess();
|
|
73
|
+
await decoder.flush();
|
|
74
|
+
controller.abort();
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (nextTimestampWeWant === undefined) {
|
|
78
|
+
throw new Error('this should not happen');
|
|
79
|
+
}
|
|
80
|
+
if (sample.timestamp >= nextTimestampWeWant * media_parser_1.WEBCODECS_TIMESCALE) {
|
|
81
|
+
await doProcess();
|
|
82
|
+
if (timestampTargets.length === 0) {
|
|
83
|
+
await decoder.flush();
|
|
84
|
+
controller.abort();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
},
|
|
89
|
+
})
|
|
90
|
+
.then(() => {
|
|
91
|
+
resolvers.resolve();
|
|
92
|
+
})
|
|
93
|
+
.catch((e) => {
|
|
94
|
+
if (!(0, media_parser_1.hasBeenAborted)(e)) {
|
|
95
|
+
resolvers.reject(e);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
resolvers.resolve();
|
|
99
|
+
}
|
|
100
|
+
})
|
|
101
|
+
.finally(() => {
|
|
102
|
+
signal?.removeEventListener('abort', abortListener);
|
|
103
|
+
});
|
|
104
|
+
return resolvers.promise;
|
|
105
|
+
};
|
|
106
|
+
const extractFrames = (options) => {
|
|
107
|
+
return internalExtractFrames({
|
|
108
|
+
...options,
|
|
109
|
+
signal: options.signal ?? null,
|
|
110
|
+
acknowledgeRemotionLicense: options.acknowledgeRemotionLicense ?? false,
|
|
111
|
+
logLevel: options.logLevel ?? 'info',
|
|
112
|
+
});
|
|
113
|
+
};
|
|
114
|
+
exports.extractFrames = extractFrames;
|
package/dist/index.d.ts
CHANGED
|
@@ -13,6 +13,8 @@ export { createVideoDecoder } from './create-video-decoder';
|
|
|
13
13
|
export type { WebCodecsVideoDecoder } from './create-video-decoder';
|
|
14
14
|
export { defaultOnAudioTrackHandler } from './default-on-audio-track-handler';
|
|
15
15
|
export { defaultOnVideoTrackHandler } from './default-on-video-track-handler';
|
|
16
|
+
export { extractFrames } from './extract-frames';
|
|
17
|
+
export type { ExtractFramesTimestampsInSecondsFn } from './extract-frames';
|
|
16
18
|
export { getAvailableAudioCodecs } from './get-available-audio-codecs';
|
|
17
19
|
export type { ConvertMediaAudioCodec } from './get-available-audio-codecs';
|
|
18
20
|
export { getAvailableContainers } from './get-available-containers';
|
|
@@ -29,18 +31,18 @@ export type { WebCodecsVideoEncoder } from './video-encoder';
|
|
|
29
31
|
export { webcodecsController } from './webcodecs-controller';
|
|
30
32
|
export type { WebCodecsController } from './webcodecs-controller';
|
|
31
33
|
export declare const WebCodecsInternals: {
|
|
32
|
-
rotateAndResizeVideoFrame: ({ frame, rotation,
|
|
34
|
+
rotateAndResizeVideoFrame: ({ frame, rotation, needsToBeMultipleOfTwo, resizeOperation, }: {
|
|
33
35
|
frame: VideoFrame;
|
|
34
36
|
rotation: number;
|
|
35
|
-
|
|
37
|
+
needsToBeMultipleOfTwo: boolean;
|
|
36
38
|
resizeOperation: import("./resizing/mode").ResizeOperation | null;
|
|
37
39
|
}) => VideoFrame;
|
|
38
40
|
normalizeVideoRotation: (rotation: number) => number;
|
|
39
|
-
calculateNewDimensionsFromDimensions: ({ width, height, rotation, resizeOperation,
|
|
41
|
+
calculateNewDimensionsFromDimensions: ({ width, height, rotation, resizeOperation, needsToBeMultipleOfTwo, }: {
|
|
40
42
|
width: number;
|
|
41
43
|
height: number;
|
|
42
44
|
rotation: number;
|
|
43
45
|
resizeOperation: import("./resizing/mode").ResizeOperation | null;
|
|
44
|
-
|
|
46
|
+
needsToBeMultipleOfTwo: boolean;
|
|
45
47
|
}) => import("@remotion/media-parser").MediaParserDimensions;
|
|
46
48
|
};
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.WebCodecsInternals = exports.webcodecsController = exports.createVideoEncoder = exports.getDefaultVideoCodec = exports.getDefaultAudioCodec = exports.getAvailableVideoCodecs = exports.getAvailableContainers = exports.getAvailableAudioCodecs = exports.defaultOnVideoTrackHandler = exports.defaultOnAudioTrackHandler = exports.createVideoDecoder = exports.createAudioDecoder = exports.convertMedia = exports.convertAudioData = exports.canReencodeVideoTrack = exports.canReencodeAudioTrack = exports.canCopyVideoTrack = exports.canCopyAudioTrack = exports.createAudioEncoder = void 0;
|
|
3
|
+
exports.WebCodecsInternals = exports.webcodecsController = exports.createVideoEncoder = exports.getDefaultVideoCodec = exports.getDefaultAudioCodec = exports.getAvailableVideoCodecs = exports.getAvailableContainers = exports.getAvailableAudioCodecs = exports.extractFrames = exports.defaultOnVideoTrackHandler = exports.defaultOnAudioTrackHandler = exports.createVideoDecoder = exports.createAudioDecoder = exports.convertMedia = exports.convertAudioData = exports.canReencodeVideoTrack = exports.canReencodeAudioTrack = exports.canCopyVideoTrack = exports.canCopyAudioTrack = exports.createAudioEncoder = void 0;
|
|
4
4
|
const rotate_and_resize_video_frame_1 = require("./rotate-and-resize-video-frame");
|
|
5
5
|
const rotation_1 = require("./rotation");
|
|
6
6
|
const set_remotion_imported_1 = require("./set-remotion-imported");
|
|
@@ -26,6 +26,8 @@ var default_on_audio_track_handler_1 = require("./default-on-audio-track-handler
|
|
|
26
26
|
Object.defineProperty(exports, "defaultOnAudioTrackHandler", { enumerable: true, get: function () { return default_on_audio_track_handler_1.defaultOnAudioTrackHandler; } });
|
|
27
27
|
var default_on_video_track_handler_1 = require("./default-on-video-track-handler");
|
|
28
28
|
Object.defineProperty(exports, "defaultOnVideoTrackHandler", { enumerable: true, get: function () { return default_on_video_track_handler_1.defaultOnVideoTrackHandler; } });
|
|
29
|
+
var extract_frames_1 = require("./extract-frames");
|
|
30
|
+
Object.defineProperty(exports, "extractFrames", { enumerable: true, get: function () { return extract_frames_1.extractFrames; } });
|
|
29
31
|
var get_available_audio_codecs_1 = require("./get-available-audio-codecs");
|
|
30
32
|
Object.defineProperty(exports, "getAvailableAudioCodecs", { enumerable: true, get: function () { return get_available_audio_codecs_1.getAvailableAudioCodecs; } });
|
|
31
33
|
var get_available_containers_1 = require("./get-available-containers");
|
|
@@ -12,7 +12,9 @@ const makeIoSynchronizer = ({ logLevel, label, controller, }) => {
|
|
|
12
12
|
let inputsSinceLastOutput = 0;
|
|
13
13
|
let inputs = [];
|
|
14
14
|
const getQueuedItems = () => {
|
|
15
|
-
inputs = inputs.filter(
|
|
15
|
+
inputs = inputs.filter(
|
|
16
|
+
// In chrome, the last output sometimes shifts the timestamp by 1 macrosecond - allowing this to happen
|
|
17
|
+
(input) => Math.floor(input) > Math.floor(lastOutput) + 1);
|
|
16
18
|
return inputs.length;
|
|
17
19
|
};
|
|
18
20
|
const printState = (prefix) => {
|
package/dist/on-frame.js
CHANGED
|
@@ -9,7 +9,7 @@ const onFrame = async ({ frame: unrotatedFrame, onVideoFrame, track, outputCodec
|
|
|
9
9
|
rotation,
|
|
10
10
|
frame: unrotatedFrame,
|
|
11
11
|
resizeOperation,
|
|
12
|
-
|
|
12
|
+
needsToBeMultipleOfTwo: outputCodec === 'h264',
|
|
13
13
|
});
|
|
14
14
|
if (unrotatedFrame !== rotated) {
|
|
15
15
|
unrotatedFrame.close();
|
|
@@ -145,7 +145,8 @@ const reencodeAudioTrack = async ({ audioOperation, track, logLevel, abortConver
|
|
|
145
145
|
logLevel,
|
|
146
146
|
});
|
|
147
147
|
state.addWaitForFinishPromise(async () => {
|
|
148
|
-
|
|
148
|
+
log_1.Log.verbose(logLevel, 'Waiting for audio decoder to finish');
|
|
149
|
+
await audioDecoder.flush();
|
|
149
150
|
log_1.Log.verbose(logLevel, 'Audio decoder finished');
|
|
150
151
|
audioDecoder.close();
|
|
151
152
|
await audioProcessingQueue.ioSynchronizer.waitForQueueSize(0);
|
|
@@ -21,7 +21,7 @@ const reencodeVideoTrack = async ({ videoOperation, rotate, track, logLevel, abo
|
|
|
21
21
|
width: track.codedWidth,
|
|
22
22
|
height: track.codedHeight,
|
|
23
23
|
rotation,
|
|
24
|
-
|
|
24
|
+
needsToBeMultipleOfTwo: videoOperation.videoCodec === 'h264',
|
|
25
25
|
resizeOperation: videoOperation.resize ?? null,
|
|
26
26
|
});
|
|
27
27
|
const videoEncoderConfig = await (0, video_encoder_config_1.getVideoEncoderConfig)({
|
|
@@ -127,7 +127,7 @@ const reencodeVideoTrack = async ({ videoOperation, rotate, track, logLevel, abo
|
|
|
127
127
|
});
|
|
128
128
|
state.addWaitForFinishPromise(async () => {
|
|
129
129
|
log_1.Log.verbose(logLevel, 'Waiting for video decoder to finish');
|
|
130
|
-
await videoDecoder.
|
|
130
|
+
await videoDecoder.flush();
|
|
131
131
|
videoDecoder.close();
|
|
132
132
|
log_1.Log.verbose(logLevel, 'Video decoder finished. Waiting for encoder to finish');
|
|
133
133
|
await frameSorter.flush();
|
|
@@ -141,7 +141,7 @@ const reencodeVideoTrack = async ({ videoOperation, rotate, track, logLevel, abo
|
|
|
141
141
|
return async (chunk) => {
|
|
142
142
|
progressTracker.setPossibleLowestTimestamp(Math.min(chunk.timestamp, chunk.decodingTimestamp ?? Infinity));
|
|
143
143
|
await controller._internals._mediaParserController._internals.checkForAbortAndPause();
|
|
144
|
-
await videoDecoder.waitForQueueToBeLessThan(
|
|
144
|
+
await videoDecoder.waitForQueueToBeLessThan(15);
|
|
145
145
|
if (chunk.type === 'key') {
|
|
146
146
|
await videoDecoder.flush();
|
|
147
147
|
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import type { MediaParserDimensions
|
|
2
|
-
import type { ConvertMediaVideoCodec } from '../get-available-video-codecs';
|
|
1
|
+
import type { MediaParserDimensions } from '@remotion/media-parser';
|
|
3
2
|
import type { ResizeOperation } from './mode';
|
|
4
|
-
export declare const calculateNewSizeAfterResizing: ({ dimensions, resizeOperation,
|
|
3
|
+
export declare const calculateNewSizeAfterResizing: ({ dimensions, resizeOperation, needsToBeMultipleOfTwo, }: {
|
|
5
4
|
dimensions: MediaParserDimensions;
|
|
6
5
|
resizeOperation: ResizeOperation | null;
|
|
7
|
-
|
|
6
|
+
needsToBeMultipleOfTwo: boolean;
|
|
8
7
|
}) => MediaParserDimensions;
|
|
@@ -10,8 +10,7 @@ const ensureMultipleOfTwo = ({ dimensions, needsToBeMultipleOfTwo, }) => {
|
|
|
10
10
|
height: Math.floor(dimensions.height / 2) * 2,
|
|
11
11
|
};
|
|
12
12
|
};
|
|
13
|
-
const calculateNewSizeAfterResizing = ({ dimensions, resizeOperation,
|
|
14
|
-
const needsToBeMultipleOfTwo = videoCodec === 'h264';
|
|
13
|
+
const calculateNewSizeAfterResizing = ({ dimensions, resizeOperation, needsToBeMultipleOfTwo, }) => {
|
|
15
14
|
if (resizeOperation === null) {
|
|
16
15
|
return ensureMultipleOfTwo({
|
|
17
16
|
dimensions,
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import type { ConvertMediaVideoCodec } from './get-available-video-codecs';
|
|
2
1
|
import type { ResizeOperation } from './resizing/mode';
|
|
3
2
|
export declare const normalizeVideoRotation: (rotation: number) => number;
|
|
4
|
-
export declare const rotateAndResizeVideoFrame: ({ frame, rotation,
|
|
3
|
+
export declare const rotateAndResizeVideoFrame: ({ frame, rotation, needsToBeMultipleOfTwo, resizeOperation, }: {
|
|
5
4
|
frame: VideoFrame;
|
|
6
5
|
rotation: number;
|
|
7
|
-
|
|
6
|
+
needsToBeMultipleOfTwo: boolean;
|
|
8
7
|
resizeOperation: ResizeOperation | null;
|
|
9
8
|
}) => VideoFrame;
|
|
@@ -6,7 +6,7 @@ const normalizeVideoRotation = (rotation) => {
|
|
|
6
6
|
return ((rotation % 360) + 360) % 360;
|
|
7
7
|
};
|
|
8
8
|
exports.normalizeVideoRotation = normalizeVideoRotation;
|
|
9
|
-
const rotateAndResizeVideoFrame = ({ frame, rotation,
|
|
9
|
+
const rotateAndResizeVideoFrame = ({ frame, rotation, needsToBeMultipleOfTwo, resizeOperation, }) => {
|
|
10
10
|
const normalized = ((rotation % 360) + 360) % 360;
|
|
11
11
|
// No resize, no rotation
|
|
12
12
|
if (normalized === 0 && resizeOperation === null) {
|
|
@@ -19,7 +19,7 @@ const rotateAndResizeVideoFrame = ({ frame, rotation, videoCodec, resizeOperatio
|
|
|
19
19
|
height: frame.displayHeight,
|
|
20
20
|
width: frame.displayWidth,
|
|
21
21
|
rotation,
|
|
22
|
-
|
|
22
|
+
needsToBeMultipleOfTwo,
|
|
23
23
|
resizeOperation,
|
|
24
24
|
});
|
|
25
25
|
// No rotation, and resize turned out to be same dimensions
|
package/dist/rotation.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import type { MediaParserDimensions
|
|
2
|
-
import type { ConvertMediaVideoCodec } from './get-available-video-codecs';
|
|
1
|
+
import type { MediaParserDimensions } from '@remotion/media-parser';
|
|
3
2
|
import type { ResizeOperation } from './resizing/mode';
|
|
4
3
|
export declare const calculateNewDimensionsFromRotate: ({ height, width, rotation, }: MediaParserDimensions & {
|
|
5
4
|
rotation: number;
|
|
@@ -7,10 +6,10 @@ export declare const calculateNewDimensionsFromRotate: ({ height, width, rotatio
|
|
|
7
6
|
height: number;
|
|
8
7
|
width: number;
|
|
9
8
|
};
|
|
10
|
-
export declare const calculateNewDimensionsFromRotateAndScale: ({ width, height, rotation, resizeOperation,
|
|
9
|
+
export declare const calculateNewDimensionsFromRotateAndScale: ({ width, height, rotation, resizeOperation, needsToBeMultipleOfTwo, }: {
|
|
11
10
|
width: number;
|
|
12
11
|
height: number;
|
|
13
12
|
rotation: number;
|
|
14
13
|
resizeOperation: ResizeOperation | null;
|
|
15
|
-
|
|
14
|
+
needsToBeMultipleOfTwo: boolean;
|
|
16
15
|
}) => MediaParserDimensions;
|
package/dist/rotation.js
CHANGED
|
@@ -14,7 +14,7 @@ const calculateNewDimensionsFromRotate = ({ height, width, rotation, }) => {
|
|
|
14
14
|
};
|
|
15
15
|
};
|
|
16
16
|
exports.calculateNewDimensionsFromRotate = calculateNewDimensionsFromRotate;
|
|
17
|
-
const calculateNewDimensionsFromRotateAndScale = ({ width, height, rotation, resizeOperation,
|
|
17
|
+
const calculateNewDimensionsFromRotateAndScale = ({ width, height, rotation, resizeOperation, needsToBeMultipleOfTwo, }) => {
|
|
18
18
|
const { height: newHeight, width: newWidth } = (0, exports.calculateNewDimensionsFromRotate)({
|
|
19
19
|
height,
|
|
20
20
|
rotation,
|
|
@@ -23,7 +23,7 @@ const calculateNewDimensionsFromRotateAndScale = ({ width, height, rotation, res
|
|
|
23
23
|
return (0, calculate_new_size_1.calculateNewSizeAfterResizing)({
|
|
24
24
|
dimensions: { height: newHeight, width: newWidth },
|
|
25
25
|
resizeOperation,
|
|
26
|
-
|
|
26
|
+
needsToBeMultipleOfTwo,
|
|
27
27
|
});
|
|
28
28
|
};
|
|
29
29
|
exports.calculateNewDimensionsFromRotateAndScale = calculateNewDimensionsFromRotateAndScale;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remotion/webcodecs",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.311",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"module": "dist/esm/index.mjs",
|
|
@@ -19,17 +19,18 @@
|
|
|
19
19
|
"author": "Jonny Burger <jonny@remotion.dev>",
|
|
20
20
|
"license": "Remotion License (See https://remotion.dev/docs/webcodecs#license)",
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@remotion/media-parser": "4.0.
|
|
23
|
-
"@remotion/licensing": "4.0.
|
|
22
|
+
"@remotion/media-parser": "4.0.311",
|
|
23
|
+
"@remotion/licensing": "4.0.311"
|
|
24
24
|
},
|
|
25
25
|
"peerDependencies": {},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@types/dom-webcodecs": "0.1.11",
|
|
28
28
|
"playwright": "1.51.1",
|
|
29
|
+
"vite": "5.4.19",
|
|
29
30
|
"@playwright/test": "1.51.1",
|
|
30
31
|
"eslint": "9.19.0",
|
|
31
|
-
"@remotion/example-videos": "4.0.
|
|
32
|
-
"@remotion/eslint-config-internal": "4.0.
|
|
32
|
+
"@remotion/example-videos": "4.0.311",
|
|
33
|
+
"@remotion/eslint-config-internal": "4.0.311"
|
|
33
34
|
},
|
|
34
35
|
"keywords": [],
|
|
35
36
|
"publishConfig": {
|