@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.
@@ -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,8 @@
1
+ export declare const extractFrames: ({ fromSeconds, toSeconds, width, height, src, onFrame, }: {
2
+ fromSeconds: number;
3
+ toSeconds: number;
4
+ width: number;
5
+ height: number;
6
+ src: string;
7
+ onFrame: (frame: VideoFrame) => void;
8
+ }) => Promise<void>;
@@ -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>;
@@ -244,9 +244,8 @@ var ensureMultipleOfTwo = ({
244
244
  var calculateNewSizeAfterResizing = ({
245
245
  dimensions,
246
246
  resizeOperation,
247
- videoCodec
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
- videoCodec
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
- videoCodec
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
- videoCodec,
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
- videoCodec,
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
- videoCodec: inputTrack.codecEnum,
938
- width: inputTrack.width
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
- await audioDecoder.waitForQueueToBeLessThan(0);
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
- videoCodec: outputCodec
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
- videoCodec: videoOperation.videoCodec,
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.waitForQueueToBeLessThan(0);
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(10);
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, videoCodec, resizeOperation, }: {
34
+ rotateAndResizeVideoFrame: ({ frame, rotation, needsToBeMultipleOfTwo, resizeOperation, }: {
33
35
  frame: VideoFrame;
34
36
  rotation: number;
35
- videoCodec: import("./get-available-video-codecs").ConvertMediaVideoCodec;
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, videoCodec, }: {
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
- videoCodec: import("./get-available-video-codecs").ConvertMediaVideoCodec | import("@remotion/media-parser").MediaParserVideoCodec;
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((input) => Math.floor(input) > Math.floor(lastOutput));
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
- videoCodec: outputCodec,
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
- await audioDecoder.waitForQueueToBeLessThan(0);
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
- videoCodec: videoOperation.videoCodec,
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.waitForQueueToBeLessThan(0);
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(10);
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, MediaParserVideoCodec } from '@remotion/media-parser';
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, videoCodec, }: {
3
+ export declare const calculateNewSizeAfterResizing: ({ dimensions, resizeOperation, needsToBeMultipleOfTwo, }: {
5
4
  dimensions: MediaParserDimensions;
6
5
  resizeOperation: ResizeOperation | null;
7
- videoCodec: ConvertMediaVideoCodec | MediaParserVideoCodec;
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, videoCodec, }) => {
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, videoCodec, resizeOperation, }: {
3
+ export declare const rotateAndResizeVideoFrame: ({ frame, rotation, needsToBeMultipleOfTwo, resizeOperation, }: {
5
4
  frame: VideoFrame;
6
5
  rotation: number;
7
- videoCodec: ConvertMediaVideoCodec;
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, videoCodec, resizeOperation, }) => {
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
- videoCodec,
22
+ needsToBeMultipleOfTwo,
23
23
  resizeOperation,
24
24
  });
25
25
  // No rotation, and resize turned out to be same dimensions
@@ -1,5 +1,4 @@
1
- import type { MediaParserDimensions, MediaParserVideoCodec } from '@remotion/media-parser';
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, videoCodec, }: {
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
- videoCodec: ConvertMediaVideoCodec | MediaParserVideoCodec;
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, videoCodec, }) => {
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
- videoCodec,
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.309",
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.309",
23
- "@remotion/licensing": "4.0.309"
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.309",
32
- "@remotion/eslint-config-internal": "4.0.309"
32
+ "@remotion/example-videos": "4.0.311",
33
+ "@remotion/eslint-config-internal": "4.0.311"
33
34
  },
34
35
  "keywords": [],
35
36
  "publishConfig": {