@remotion/webcodecs 4.0.330 → 4.0.331

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.
@@ -5593,20 +5593,23 @@ var convertMedia = async function({
5593
5593
  });
5594
5594
  };
5595
5595
  // src/extract-frames.ts
5596
+ import { parseMedia } from "@remotion/media-parser";
5597
+
5598
+ // src/internal-extract-frames.ts
5596
5599
  import {
5597
- hasBeenAborted,
5598
5600
  MediaParserAbortError as MediaParserAbortError4,
5599
- mediaParserController as mediaParserController2,
5600
- WEBCODECS_TIMESCALE
5601
+ WEBCODECS_TIMESCALE,
5602
+ hasBeenAborted,
5603
+ mediaParserController as mediaParserController2
5601
5604
  } from "@remotion/media-parser";
5602
- import { parseMediaOnWebWorker } from "@remotion/media-parser/worker";
5603
5605
  var internalExtractFrames = ({
5604
5606
  src,
5605
5607
  onFrame,
5606
5608
  signal,
5607
5609
  timestampsInSeconds,
5608
5610
  acknowledgeRemotionLicense,
5609
- logLevel
5611
+ logLevel,
5612
+ parseMediaImplementation
5610
5613
  }) => {
5611
5614
  const controller = mediaParserController2();
5612
5615
  const expectedFrames = [];
@@ -5619,7 +5622,7 @@ var internalExtractFrames = ({
5619
5622
  let dur = null;
5620
5623
  let lastFrame;
5621
5624
  let lastFrameEmitted;
5622
- parseMediaOnWebWorker({
5625
+ parseMediaImplementation({
5623
5626
  src: new URL(src, window.location.href),
5624
5627
  acknowledgeRemotionLicense,
5625
5628
  controller,
@@ -5656,11 +5659,17 @@ var internalExtractFrames = ({
5656
5659
  onFrame(lastFrame);
5657
5660
  lastFrameEmitted = lastFrame;
5658
5661
  expectedFrames.shift();
5662
+ if (lastFrame) {
5663
+ lastFrame.close();
5664
+ }
5659
5665
  lastFrame = frame;
5660
5666
  return;
5661
5667
  }
5662
5668
  expectedFrames.shift();
5663
5669
  onFrame(frame);
5670
+ if (lastFrame && lastFrame !== lastFrameEmitted) {
5671
+ lastFrame.close();
5672
+ }
5664
5673
  lastFrameEmitted = frame;
5665
5674
  lastFrame = frame;
5666
5675
  },
@@ -5681,14 +5690,14 @@ var internalExtractFrames = ({
5681
5690
  if (!sam) {
5682
5691
  throw new Error("Sample is undefined");
5683
5692
  }
5684
- await decoder.waitForQueueToBeLessThan(10);
5693
+ await decoder.waitForQueueToBeLessThan(20);
5685
5694
  Log.trace(logLevel, "Decoding sample", sam.timestamp);
5686
5695
  await decoder.decode(sam);
5687
5696
  }
5688
5697
  };
5689
5698
  return async (sample) => {
5690
5699
  const nextTimestampWeWant = timestampTargets[0];
5691
- Log.trace(logLevel, "Received sample with dts", sample.decodingTimestamp, "and cts", sample.timestamp);
5700
+ Log.trace(logLevel, `Received ${sample.type} sample with dts`, sample.decodingTimestamp, "and cts", sample.timestamp);
5692
5701
  if (sample.type === "key") {
5693
5702
  await decoder.flush();
5694
5703
  queued.length = 0;
@@ -5735,12 +5744,15 @@ var internalExtractFrames = ({
5735
5744
  });
5736
5745
  return resolvers.promise;
5737
5746
  };
5747
+
5748
+ // src/extract-frames.ts
5738
5749
  var extractFrames = (options) => {
5739
5750
  return internalExtractFrames({
5740
5751
  ...options,
5741
5752
  signal: options.signal ?? null,
5742
5753
  acknowledgeRemotionLicense: options.acknowledgeRemotionLicense ?? false,
5743
- logLevel: options.logLevel ?? "info"
5754
+ logLevel: options.logLevel ?? "info",
5755
+ parseMediaImplementation: parseMedia
5744
5756
  });
5745
5757
  };
5746
5758
  // src/get-available-audio-codecs.ts
@@ -5762,7 +5774,7 @@ var getAvailableAudioCodecs = ({
5762
5774
  import {
5763
5775
  hasBeenAborted as hasBeenAborted2,
5764
5776
  mediaParserController as mediaParserController3,
5765
- parseMedia
5777
+ parseMedia as parseMedia2
5766
5778
  } from "@remotion/media-parser";
5767
5779
  var extractOverlappingAudioSamples = ({
5768
5780
  sample,
@@ -5820,7 +5832,7 @@ var getPartialAudioData = async ({
5820
5832
  if (fromSeconds > 0) {
5821
5833
  controller.seek(fromSeconds);
5822
5834
  }
5823
- await parseMedia({
5835
+ await parseMedia2({
5824
5836
  acknowledgeRemotionLicense: true,
5825
5837
  src,
5826
5838
  controller,
@@ -0,0 +1,506 @@
1
+ var __create = Object.create;
2
+ var __getProtoOf = Object.getPrototypeOf;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __toESM = (mod, isNodeMode, target) => {
7
+ target = mod != null ? __create(__getProtoOf(mod)) : {};
8
+ const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
9
+ for (let key of __getOwnPropNames(mod))
10
+ if (!__hasOwnProp.call(to, key))
11
+ __defProp(to, key, {
12
+ get: () => mod[key],
13
+ enumerable: true
14
+ });
15
+ return to;
16
+ };
17
+ var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
18
+
19
+ // src/extract-frames-on-web-worker.ts
20
+ import { parseMediaOnWebWorker } from "@remotion/media-parser/worker";
21
+
22
+ // src/internal-extract-frames.ts
23
+ import {
24
+ MediaParserAbortError,
25
+ WEBCODECS_TIMESCALE,
26
+ hasBeenAborted,
27
+ mediaParserController
28
+ } from "@remotion/media-parser";
29
+
30
+ // src/create/with-resolvers.ts
31
+ var withResolvers = function() {
32
+ let resolve;
33
+ let reject;
34
+ const promise = new Promise((res, rej) => {
35
+ resolve = res;
36
+ reject = rej;
37
+ });
38
+ return { promise, resolve, reject };
39
+ };
40
+ var withResolversAndWaitForReturn = () => {
41
+ const { promise, reject, resolve } = withResolvers();
42
+ const { promise: returnPromise, resolve: resolveReturn } = withResolvers();
43
+ return {
44
+ getPromiseToImmediatelyReturn: () => {
45
+ resolveReturn(undefined);
46
+ return promise;
47
+ },
48
+ reject: (reason) => {
49
+ returnPromise.then(() => reject(reason));
50
+ },
51
+ resolve
52
+ };
53
+ };
54
+
55
+ // src/flush-pending.ts
56
+ var makeFlushPending = () => {
57
+ const { promise, resolve, reject } = withResolvers();
58
+ return {
59
+ promise,
60
+ resolve,
61
+ reject
62
+ };
63
+ };
64
+
65
+ // src/create/event-emitter.ts
66
+ class IoEventEmitter {
67
+ listeners = {
68
+ input: [],
69
+ output: [],
70
+ processed: [],
71
+ progress: []
72
+ };
73
+ addEventListener(name, callback) {
74
+ this.listeners[name].push(callback);
75
+ }
76
+ removeEventListener(name, callback) {
77
+ this.listeners[name] = this.listeners[name].filter((l) => l !== callback);
78
+ }
79
+ dispatchEvent(dispatchName, context) {
80
+ this.listeners[dispatchName].forEach((callback) => {
81
+ callback({ detail: context });
82
+ });
83
+ }
84
+ }
85
+
86
+ // src/log.ts
87
+ import { MediaParserInternals } from "@remotion/media-parser";
88
+ var { Log } = MediaParserInternals;
89
+
90
+ // src/io-manager/make-timeout-promise.ts
91
+ var makeTimeoutPromise = ({
92
+ label,
93
+ ms,
94
+ controller
95
+ }) => {
96
+ const { promise, reject, resolve } = withResolvers();
97
+ let timeout = null;
98
+ const set = () => {
99
+ timeout = setTimeout(() => {
100
+ reject(new Error(`${label()} (timed out after ${ms}ms)`));
101
+ }, ms);
102
+ };
103
+ set();
104
+ const onPause = () => {
105
+ if (timeout) {
106
+ clearTimeout(timeout);
107
+ }
108
+ };
109
+ const onResume = () => {
110
+ set();
111
+ };
112
+ if (controller) {
113
+ controller.addEventListener("pause", onPause);
114
+ controller.addEventListener("resume", onResume);
115
+ }
116
+ return {
117
+ timeoutPromise: promise,
118
+ clear: () => {
119
+ if (timeout) {
120
+ clearTimeout(timeout);
121
+ }
122
+ resolve();
123
+ if (controller) {
124
+ controller.removeEventListener("pause", onPause);
125
+ controller.removeEventListener("resume", onResume);
126
+ }
127
+ }
128
+ };
129
+ };
130
+
131
+ // src/io-manager/io-synchronizer.ts
132
+ var makeIoSynchronizer = ({
133
+ logLevel,
134
+ label,
135
+ controller
136
+ }) => {
137
+ const eventEmitter = new IoEventEmitter;
138
+ let lastInput = 0;
139
+ let lastOutput = 0;
140
+ let inputsSinceLastOutput = 0;
141
+ let inputs = [];
142
+ let resolvers = [];
143
+ const getQueuedItems = () => {
144
+ inputs = inputs.filter((input) => Math.floor(input) > Math.floor(lastOutput) + 1);
145
+ return inputs.length;
146
+ };
147
+ const printState = (prefix) => {
148
+ Log.trace(logLevel, `[${label}] ${prefix}, state: Last input = ${lastInput} Last output = ${lastOutput} Inputs since last output = ${inputsSinceLastOutput}, Queue = ${getQueuedItems()}`);
149
+ };
150
+ const inputItem = (timestamp) => {
151
+ lastInput = timestamp;
152
+ inputsSinceLastOutput++;
153
+ inputs.push(timestamp);
154
+ eventEmitter.dispatchEvent("input", {
155
+ timestamp
156
+ });
157
+ printState("Input item");
158
+ };
159
+ const onOutput = (timestamp) => {
160
+ lastOutput = timestamp;
161
+ inputsSinceLastOutput = 0;
162
+ eventEmitter.dispatchEvent("output", {
163
+ timestamp
164
+ });
165
+ printState("Got output");
166
+ };
167
+ const waitForOutput = () => {
168
+ const { promise, resolve } = withResolvers();
169
+ const on = () => {
170
+ eventEmitter.removeEventListener("output", on);
171
+ resolve();
172
+ resolvers = resolvers.filter((resolver) => resolver !== resolve);
173
+ };
174
+ eventEmitter.addEventListener("output", on);
175
+ resolvers.push(resolve);
176
+ return promise;
177
+ };
178
+ const makeErrorBanner = () => {
179
+ return [
180
+ `Waited too long for ${label} to finish:`,
181
+ `${getQueuedItems()} queued items`,
182
+ `inputs: ${JSON.stringify(inputs)}`,
183
+ `last output: ${lastOutput}`
184
+ ];
185
+ };
186
+ const waitForQueueSize = async (queueSize) => {
187
+ if (getQueuedItems() <= queueSize) {
188
+ return Promise.resolve();
189
+ }
190
+ const { timeoutPromise, clear } = makeTimeoutPromise({
191
+ label: () => [
192
+ ...makeErrorBanner(),
193
+ `wanted: <${queueSize} queued items`,
194
+ `Report this at https://remotion.dev/report`
195
+ ].join(`
196
+ `),
197
+ ms: 1e4,
198
+ controller
199
+ });
200
+ if (controller) {
201
+ controller._internals._mediaParserController._internals.signal.addEventListener("abort", clear);
202
+ }
203
+ await Promise.race([
204
+ timeoutPromise,
205
+ (async () => {
206
+ while (getQueuedItems() > queueSize) {
207
+ await waitForOutput();
208
+ }
209
+ })()
210
+ ]).finally(() => clear());
211
+ if (controller) {
212
+ controller._internals._mediaParserController._internals.signal.removeEventListener("abort", clear);
213
+ }
214
+ };
215
+ const clearQueue = () => {
216
+ inputs.length = 0;
217
+ lastInput = 0;
218
+ lastOutput = 0;
219
+ inputsSinceLastOutput = 0;
220
+ resolvers.forEach((resolver) => {
221
+ return resolver();
222
+ });
223
+ resolvers.length = 0;
224
+ inputs.length = 0;
225
+ };
226
+ return {
227
+ inputItem,
228
+ onOutput,
229
+ waitForQueueSize,
230
+ clearQueue
231
+ };
232
+ };
233
+
234
+ // src/create-video-decoder.ts
235
+ var internalCreateVideoDecoder = ({
236
+ onFrame,
237
+ onError,
238
+ controller,
239
+ config,
240
+ logLevel
241
+ }) => {
242
+ if (controller && controller._internals._mediaParserController._internals.signal.aborted) {
243
+ throw new Error("Not creating audio decoder, already aborted");
244
+ }
245
+ const ioSynchronizer = makeIoSynchronizer({
246
+ logLevel,
247
+ label: "Video decoder",
248
+ controller
249
+ });
250
+ let mostRecentSampleReceived = null;
251
+ const videoDecoder = new VideoDecoder({
252
+ async output(frame) {
253
+ try {
254
+ await onFrame(frame);
255
+ } catch (err) {
256
+ onError(err);
257
+ frame.close();
258
+ }
259
+ ioSynchronizer.onOutput(frame.timestamp);
260
+ },
261
+ error(error) {
262
+ onError(error);
263
+ }
264
+ });
265
+ const close = () => {
266
+ if (controller) {
267
+ controller._internals._mediaParserController._internals.signal.removeEventListener("abort", onAbort);
268
+ }
269
+ if (videoDecoder.state === "closed") {
270
+ return;
271
+ }
272
+ videoDecoder.close();
273
+ };
274
+ const onAbort = () => {
275
+ close();
276
+ };
277
+ if (controller) {
278
+ controller._internals._mediaParserController._internals.signal.addEventListener("abort", onAbort);
279
+ }
280
+ videoDecoder.configure(config);
281
+ const decode = async (sample) => {
282
+ if (videoDecoder.state === "closed") {
283
+ return;
284
+ }
285
+ try {
286
+ await controller?._internals._mediaParserController._internals.checkForAbortAndPause();
287
+ } catch (err) {
288
+ onError(err);
289
+ return;
290
+ }
291
+ mostRecentSampleReceived = sample.timestamp;
292
+ const encodedChunk = sample instanceof EncodedVideoChunk ? sample : new EncodedVideoChunk(sample);
293
+ videoDecoder.decode(encodedChunk);
294
+ ioSynchronizer.inputItem(sample.timestamp);
295
+ };
296
+ let flushPending = null;
297
+ let lastReset = null;
298
+ return {
299
+ decode,
300
+ close,
301
+ flush: () => {
302
+ if (flushPending) {
303
+ throw new Error("Flush already pending");
304
+ }
305
+ const pendingFlush = makeFlushPending();
306
+ flushPending = pendingFlush;
307
+ Promise.resolve().then(() => {
308
+ return videoDecoder.flush();
309
+ }).catch(() => {}).finally(() => {
310
+ pendingFlush.resolve();
311
+ flushPending = null;
312
+ });
313
+ return pendingFlush.promise;
314
+ },
315
+ waitForQueueToBeLessThan: ioSynchronizer.waitForQueueSize,
316
+ reset: () => {
317
+ lastReset = Date.now();
318
+ flushPending?.resolve();
319
+ ioSynchronizer.clearQueue();
320
+ videoDecoder.reset();
321
+ videoDecoder.configure(config);
322
+ },
323
+ checkReset: () => {
324
+ const initTime = Date.now();
325
+ return {
326
+ wasReset: () => lastReset !== null && lastReset > initTime
327
+ };
328
+ },
329
+ getMostRecentSampleInput() {
330
+ return mostRecentSampleReceived;
331
+ }
332
+ };
333
+ };
334
+ var createVideoDecoder = ({
335
+ onFrame,
336
+ onError,
337
+ controller,
338
+ track,
339
+ logLevel
340
+ }) => {
341
+ return internalCreateVideoDecoder({
342
+ onFrame,
343
+ onError,
344
+ controller: controller ?? null,
345
+ config: track,
346
+ logLevel: logLevel ?? "info"
347
+ });
348
+ };
349
+
350
+ // src/internal-extract-frames.ts
351
+ var internalExtractFrames = ({
352
+ src,
353
+ onFrame,
354
+ signal,
355
+ timestampsInSeconds,
356
+ acknowledgeRemotionLicense,
357
+ logLevel,
358
+ parseMediaImplementation
359
+ }) => {
360
+ const controller = mediaParserController();
361
+ const expectedFrames = [];
362
+ const resolvers = withResolvers();
363
+ const abortListener = () => {
364
+ controller.abort();
365
+ resolvers.reject(new MediaParserAbortError("Aborted by user"));
366
+ };
367
+ signal?.addEventListener("abort", abortListener, { once: true });
368
+ let dur = null;
369
+ let lastFrame;
370
+ let lastFrameEmitted;
371
+ parseMediaImplementation({
372
+ src: new URL(src, window.location.href),
373
+ acknowledgeRemotionLicense,
374
+ controller,
375
+ logLevel,
376
+ onDurationInSeconds(durationInSeconds) {
377
+ dur = durationInSeconds;
378
+ },
379
+ onVideoTrack: async ({ track, container }) => {
380
+ const timestampTargetsUnsorted = typeof timestampsInSeconds === "function" ? await timestampsInSeconds({
381
+ track,
382
+ container,
383
+ durationInSeconds: dur
384
+ }) : timestampsInSeconds;
385
+ const timestampTargets = timestampTargetsUnsorted.sort((a, b) => a - b);
386
+ if (timestampTargets.length === 0) {
387
+ throw new Error("expected at least one timestamp to extract but found zero");
388
+ }
389
+ controller.seek(timestampTargets[0]);
390
+ const decoder = createVideoDecoder({
391
+ onFrame: (frame) => {
392
+ Log.trace(logLevel, "Received frame with timestamp", frame.timestamp);
393
+ if (expectedFrames.length === 0) {
394
+ frame.close();
395
+ return;
396
+ }
397
+ if (frame.timestamp < expectedFrames[0] - 1) {
398
+ if (lastFrame) {
399
+ lastFrame.close();
400
+ }
401
+ lastFrame = frame;
402
+ return;
403
+ }
404
+ if (expectedFrames[0] + 6667 < frame.timestamp && lastFrame) {
405
+ onFrame(lastFrame);
406
+ lastFrameEmitted = lastFrame;
407
+ expectedFrames.shift();
408
+ if (lastFrame) {
409
+ lastFrame.close();
410
+ }
411
+ lastFrame = frame;
412
+ return;
413
+ }
414
+ expectedFrames.shift();
415
+ onFrame(frame);
416
+ if (lastFrame && lastFrame !== lastFrameEmitted) {
417
+ lastFrame.close();
418
+ }
419
+ lastFrameEmitted = frame;
420
+ lastFrame = frame;
421
+ },
422
+ onError: (e) => {
423
+ controller.abort();
424
+ try {
425
+ decoder.close();
426
+ } catch {}
427
+ resolvers.reject(e);
428
+ },
429
+ track
430
+ });
431
+ const queued = [];
432
+ const doProcess = async () => {
433
+ expectedFrames.push(timestampTargets.shift() * WEBCODECS_TIMESCALE);
434
+ while (queued.length > 0) {
435
+ const sam = queued.shift();
436
+ if (!sam) {
437
+ throw new Error("Sample is undefined");
438
+ }
439
+ await decoder.waitForQueueToBeLessThan(20);
440
+ Log.trace(logLevel, "Decoding sample", sam.timestamp);
441
+ await decoder.decode(sam);
442
+ }
443
+ };
444
+ return async (sample) => {
445
+ const nextTimestampWeWant = timestampTargets[0];
446
+ Log.trace(logLevel, `Received ${sample.type} sample with dts`, sample.decodingTimestamp, "and cts", sample.timestamp);
447
+ if (sample.type === "key") {
448
+ await decoder.flush();
449
+ queued.length = 0;
450
+ }
451
+ queued.push(sample);
452
+ if (sample.decodingTimestamp >= timestampTargets[timestampTargets.length - 1] * WEBCODECS_TIMESCALE) {
453
+ await doProcess();
454
+ await decoder.flush();
455
+ controller.abort();
456
+ return;
457
+ }
458
+ if (nextTimestampWeWant === undefined) {
459
+ throw new Error("this should not happen");
460
+ }
461
+ if (sample.decodingTimestamp >= nextTimestampWeWant * WEBCODECS_TIMESCALE) {
462
+ await doProcess();
463
+ if (timestampTargets.length === 0) {
464
+ await decoder.flush();
465
+ controller.abort();
466
+ }
467
+ }
468
+ return async () => {
469
+ await doProcess();
470
+ await decoder.flush();
471
+ if (lastFrame && lastFrameEmitted !== lastFrame) {
472
+ lastFrame.close();
473
+ }
474
+ };
475
+ };
476
+ }
477
+ }).then(() => {
478
+ resolvers.resolve();
479
+ }).catch((e) => {
480
+ if (!hasBeenAborted(e)) {
481
+ resolvers.reject(e);
482
+ } else {
483
+ resolvers.resolve();
484
+ }
485
+ }).finally(() => {
486
+ if (lastFrame && lastFrameEmitted !== lastFrame) {
487
+ lastFrame.close();
488
+ }
489
+ signal?.removeEventListener("abort", abortListener);
490
+ });
491
+ return resolvers.promise;
492
+ };
493
+
494
+ // src/extract-frames-on-web-worker.ts
495
+ var extractFramesOnWebWorker = (options) => {
496
+ return internalExtractFrames({
497
+ ...options,
498
+ signal: options.signal ?? null,
499
+ acknowledgeRemotionLicense: options.acknowledgeRemotionLicense ?? false,
500
+ logLevel: options.logLevel ?? "info",
501
+ parseMediaImplementation: parseMediaOnWebWorker
502
+ });
503
+ };
504
+ export {
505
+ extractFramesOnWebWorker
506
+ };
@@ -0,0 +1,12 @@
1
+ import { type MediaParserLogLevel } from '@remotion/media-parser';
2
+ import { type ExtractFramesTimestampsInSecondsFn } from './internal-extract-frames';
3
+ export type ExtractFramesOnWebWorkerProps = {
4
+ src: string;
5
+ timestampsInSeconds: number[] | ExtractFramesTimestampsInSecondsFn;
6
+ onFrame: (frame: VideoFrame) => void;
7
+ signal?: AbortSignal;
8
+ acknowledgeRemotionLicense?: boolean;
9
+ logLevel?: MediaParserLogLevel;
10
+ };
11
+ export type ExtractFramesOnWebWorker = (options: ExtractFramesOnWebWorkerProps) => Promise<void>;
12
+ export declare const extractFramesOnWebWorker: ExtractFramesOnWebWorker;
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.extractFramesOnWebWorker = void 0;
4
+ const worker_1 = require("@remotion/media-parser/worker");
5
+ const internal_extract_frames_1 = require("./internal-extract-frames");
6
+ const extractFramesOnWebWorker = (options) => {
7
+ return (0, internal_extract_frames_1.internalExtractFrames)({
8
+ ...options,
9
+ signal: options.signal ?? null,
10
+ acknowledgeRemotionLicense: options.acknowledgeRemotionLicense ?? false,
11
+ logLevel: options.logLevel ?? 'info',
12
+ parseMediaImplementation: worker_1.parseMediaOnWebWorker,
13
+ });
14
+ };
15
+ exports.extractFramesOnWebWorker = extractFramesOnWebWorker;
File without changes
@@ -0,0 +1 @@
1
+ "use strict";
@@ -1,14 +1,12 @@
1
- import type { MediaParserContainer, MediaParserLogLevel, MediaParserVideoTrack } from '@remotion/media-parser';
2
- export type ExtractFramesTimestampsInSecondsFn = (options: {
3
- track: MediaParserVideoTrack;
4
- container: MediaParserContainer;
5
- durationInSeconds: number | null;
6
- }) => Promise<number[]> | number[];
7
- export declare const extractFrames: (options: {
1
+ import { type MediaParserLogLevel } from '@remotion/media-parser';
2
+ import { type ExtractFramesTimestampsInSecondsFn } from './internal-extract-frames';
3
+ export type ExtractFramesProps = {
8
4
  src: string;
9
5
  timestampsInSeconds: number[] | ExtractFramesTimestampsInSecondsFn;
10
6
  onFrame: (frame: VideoFrame) => void;
11
7
  signal?: AbortSignal;
12
8
  acknowledgeRemotionLicense?: boolean;
13
9
  logLevel?: MediaParserLogLevel;
14
- }) => Promise<void>;
10
+ };
11
+ export type ExtractFrames = (options: ExtractFramesProps) => Promise<void>;
12
+ export declare const extractFrames: ExtractFrames;
@@ -2,156 +2,14 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.extractFrames = void 0;
4
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 log_1 = require("./log");
9
- const internalExtractFrames = ({ src, onFrame, signal, timestampsInSeconds, acknowledgeRemotionLicense, logLevel, }) => {
10
- const controller = (0, media_parser_1.mediaParserController)();
11
- const expectedFrames = [];
12
- const resolvers = (0, with_resolvers_1.withResolvers)();
13
- const abortListener = () => {
14
- controller.abort();
15
- resolvers.reject(new media_parser_1.MediaParserAbortError('Aborted by user'));
16
- };
17
- signal?.addEventListener('abort', abortListener, { once: true });
18
- let dur = null;
19
- let lastFrame;
20
- let lastFrameEmitted;
21
- (0, worker_1.parseMediaOnWebWorker)({
22
- src: new URL(src, window.location.href),
23
- acknowledgeRemotionLicense,
24
- controller,
25
- logLevel,
26
- onDurationInSeconds(durationInSeconds) {
27
- dur = durationInSeconds;
28
- },
29
- onVideoTrack: async ({ track, container }) => {
30
- const timestampTargetsUnsorted = typeof timestampsInSeconds === 'function'
31
- ? await timestampsInSeconds({
32
- track,
33
- container,
34
- durationInSeconds: dur,
35
- })
36
- : timestampsInSeconds;
37
- const timestampTargets = timestampTargetsUnsorted.sort((a, b) => a - b);
38
- if (timestampTargets.length === 0) {
39
- throw new Error('expected at least one timestamp to extract but found zero');
40
- }
41
- controller.seek(timestampTargets[0]);
42
- const decoder = (0, create_video_decoder_1.createVideoDecoder)({
43
- onFrame: (frame) => {
44
- log_1.Log.trace(logLevel, 'Received frame with timestamp', frame.timestamp);
45
- if (expectedFrames.length === 0) {
46
- frame.close();
47
- return;
48
- }
49
- if (frame.timestamp < expectedFrames[0] - 1) {
50
- if (lastFrame) {
51
- lastFrame.close();
52
- }
53
- lastFrame = frame;
54
- return;
55
- }
56
- // A WebM might have a timestamp of 67000 but we request 66666
57
- // See a test with this problem in it-tests/rendering/frame-accuracy.test.ts
58
- // Solution: We allow a 10.000ms - 3.333ms = 6.667ms difference between the requested timestamp and the actual timestamp
59
- if (expectedFrames[0] + 6667 < frame.timestamp && lastFrame) {
60
- onFrame(lastFrame);
61
- lastFrameEmitted = lastFrame;
62
- expectedFrames.shift();
63
- lastFrame = frame;
64
- return;
65
- }
66
- expectedFrames.shift();
67
- onFrame(frame);
68
- lastFrameEmitted = frame;
69
- lastFrame = frame;
70
- },
71
- onError: (e) => {
72
- controller.abort();
73
- try {
74
- decoder.close();
75
- }
76
- catch { }
77
- resolvers.reject(e);
78
- },
79
- track,
80
- });
81
- const queued = [];
82
- const doProcess = async () => {
83
- expectedFrames.push(timestampTargets.shift() * media_parser_1.WEBCODECS_TIMESCALE);
84
- while (queued.length > 0) {
85
- const sam = queued.shift();
86
- if (!sam) {
87
- throw new Error('Sample is undefined');
88
- }
89
- await decoder.waitForQueueToBeLessThan(10);
90
- log_1.Log.trace(logLevel, 'Decoding sample', sam.timestamp);
91
- await decoder.decode(sam);
92
- }
93
- };
94
- return async (sample) => {
95
- const nextTimestampWeWant = timestampTargets[0];
96
- log_1.Log.trace(logLevel, 'Received sample with dts', sample.decodingTimestamp, 'and cts', sample.timestamp);
97
- if (sample.type === 'key') {
98
- await decoder.flush();
99
- queued.length = 0;
100
- }
101
- queued.push(sample);
102
- if (sample.decodingTimestamp >=
103
- timestampTargets[timestampTargets.length - 1] * media_parser_1.WEBCODECS_TIMESCALE) {
104
- await doProcess();
105
- await decoder.flush();
106
- controller.abort();
107
- return;
108
- }
109
- if (nextTimestampWeWant === undefined) {
110
- throw new Error('this should not happen');
111
- }
112
- if (sample.decodingTimestamp >=
113
- nextTimestampWeWant * media_parser_1.WEBCODECS_TIMESCALE) {
114
- await doProcess();
115
- if (timestampTargets.length === 0) {
116
- await decoder.flush();
117
- controller.abort();
118
- }
119
- }
120
- return async () => {
121
- await doProcess();
122
- await decoder.flush();
123
- if (lastFrame && lastFrameEmitted !== lastFrame) {
124
- lastFrame.close();
125
- }
126
- };
127
- };
128
- },
129
- })
130
- .then(() => {
131
- resolvers.resolve();
132
- })
133
- .catch((e) => {
134
- if (!(0, media_parser_1.hasBeenAborted)(e)) {
135
- resolvers.reject(e);
136
- }
137
- else {
138
- resolvers.resolve();
139
- }
140
- })
141
- .finally(() => {
142
- if (lastFrame && lastFrameEmitted !== lastFrame) {
143
- lastFrame.close();
144
- }
145
- signal?.removeEventListener('abort', abortListener);
146
- });
147
- return resolvers.promise;
148
- };
5
+ const internal_extract_frames_1 = require("./internal-extract-frames");
149
6
  const extractFrames = (options) => {
150
- return internalExtractFrames({
7
+ return (0, internal_extract_frames_1.internalExtractFrames)({
151
8
  ...options,
152
9
  signal: options.signal ?? null,
153
10
  acknowledgeRemotionLicense: options.acknowledgeRemotionLicense ?? false,
154
11
  logLevel: options.logLevel ?? 'info',
12
+ parseMediaImplementation: media_parser_1.parseMedia,
155
13
  });
156
14
  };
157
15
  exports.extractFrames = extractFrames;
package/dist/index.d.ts CHANGED
@@ -13,8 +13,7 @@ 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
+ export { extractFrames, ExtractFrames, ExtractFramesProps, } from './extract-frames';
18
17
  export { getAvailableAudioCodecs } from './get-available-audio-codecs';
19
18
  export type { ConvertMediaAudioCodec } from './get-available-audio-codecs';
20
19
  export { getAvailableContainers } from './get-available-containers';
@@ -24,6 +23,7 @@ export type { ConvertMediaVideoCodec } from './get-available-video-codecs';
24
23
  export { getDefaultAudioCodec } from './get-default-audio-codec';
25
24
  export { getDefaultVideoCodec } from './get-default-video-codec';
26
25
  export { getPartialAudioData, GetPartialAudioDataProps, } from './get-partial-audio-data';
26
+ export type { ExtractFramesTimestampsInSecondsFn } from './internal-extract-frames';
27
27
  export type { AudioOperation, ConvertMediaOnAudioTrackHandler, } from './on-audio-track-handler';
28
28
  export type { ConvertMediaOnVideoTrackHandler, VideoOperation, } from './on-video-track-handler';
29
29
  export type { ResizeOperation } from './resizing/mode';
@@ -0,0 +1,16 @@
1
+ import type { MediaParserContainer, MediaParserLogLevel, MediaParserVideoTrack, ParseMedia } from '@remotion/media-parser';
2
+ import type { ParseMediaOnWorker } from '@remotion/media-parser/worker';
3
+ export type ExtractFramesTimestampsInSecondsFn = (options: {
4
+ track: MediaParserVideoTrack;
5
+ container: MediaParserContainer;
6
+ durationInSeconds: number | null;
7
+ }) => Promise<number[]> | number[];
8
+ export declare const internalExtractFrames: ({ src, onFrame, signal, timestampsInSeconds, acknowledgeRemotionLicense, logLevel, parseMediaImplementation, }: {
9
+ timestampsInSeconds: number[] | ExtractFramesTimestampsInSecondsFn;
10
+ src: string;
11
+ onFrame: (frame: VideoFrame) => void;
12
+ signal: AbortSignal | null;
13
+ acknowledgeRemotionLicense: boolean;
14
+ logLevel: MediaParserLogLevel;
15
+ parseMediaImplementation: ParseMediaOnWorker | ParseMedia;
16
+ }) => Promise<void>;
@@ -0,0 +1,156 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.internalExtractFrames = void 0;
4
+ const media_parser_1 = require("@remotion/media-parser");
5
+ const create_video_decoder_1 = require("./create-video-decoder");
6
+ const with_resolvers_1 = require("./create/with-resolvers");
7
+ const log_1 = require("./log");
8
+ const internalExtractFrames = ({ src, onFrame, signal, timestampsInSeconds, acknowledgeRemotionLicense, logLevel, parseMediaImplementation, }) => {
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
+ let lastFrame;
19
+ let lastFrameEmitted;
20
+ parseMediaImplementation({
21
+ src: new URL(src, window.location.href),
22
+ acknowledgeRemotionLicense,
23
+ controller,
24
+ logLevel,
25
+ onDurationInSeconds(durationInSeconds) {
26
+ dur = durationInSeconds;
27
+ },
28
+ onVideoTrack: async ({ track, container }) => {
29
+ const timestampTargetsUnsorted = typeof timestampsInSeconds === 'function'
30
+ ? await timestampsInSeconds({
31
+ track,
32
+ container,
33
+ durationInSeconds: dur,
34
+ })
35
+ : timestampsInSeconds;
36
+ const timestampTargets = timestampTargetsUnsorted.sort((a, b) => a - b);
37
+ if (timestampTargets.length === 0) {
38
+ throw new Error('expected at least one timestamp to extract but found zero');
39
+ }
40
+ controller.seek(timestampTargets[0]);
41
+ const decoder = (0, create_video_decoder_1.createVideoDecoder)({
42
+ onFrame: (frame) => {
43
+ log_1.Log.trace(logLevel, 'Received frame with timestamp', frame.timestamp);
44
+ if (expectedFrames.length === 0) {
45
+ frame.close();
46
+ return;
47
+ }
48
+ if (frame.timestamp < expectedFrames[0] - 1) {
49
+ if (lastFrame) {
50
+ lastFrame.close();
51
+ }
52
+ lastFrame = frame;
53
+ return;
54
+ }
55
+ // A WebM might have a timestamp of 67000 but we request 66666
56
+ // See a test with this problem in it-tests/rendering/frame-accuracy.test.ts
57
+ // Solution: We allow a 10.000ms - 3.333ms = 6.667ms difference between the requested timestamp and the actual timestamp
58
+ if (expectedFrames[0] + 6667 < frame.timestamp && lastFrame) {
59
+ onFrame(lastFrame);
60
+ lastFrameEmitted = lastFrame;
61
+ expectedFrames.shift();
62
+ if (lastFrame) {
63
+ lastFrame.close();
64
+ }
65
+ lastFrame = frame;
66
+ return;
67
+ }
68
+ expectedFrames.shift();
69
+ onFrame(frame);
70
+ if (lastFrame && lastFrame !== lastFrameEmitted) {
71
+ lastFrame.close();
72
+ }
73
+ lastFrameEmitted = frame;
74
+ lastFrame = frame;
75
+ },
76
+ onError: (e) => {
77
+ controller.abort();
78
+ try {
79
+ decoder.close();
80
+ }
81
+ catch {
82
+ // Ignore
83
+ }
84
+ resolvers.reject(e);
85
+ },
86
+ track,
87
+ });
88
+ const queued = [];
89
+ const doProcess = async () => {
90
+ expectedFrames.push(timestampTargets.shift() * media_parser_1.WEBCODECS_TIMESCALE);
91
+ while (queued.length > 0) {
92
+ const sam = queued.shift();
93
+ if (!sam) {
94
+ throw new Error('Sample is undefined');
95
+ }
96
+ await decoder.waitForQueueToBeLessThan(20);
97
+ log_1.Log.trace(logLevel, 'Decoding sample', sam.timestamp);
98
+ await decoder.decode(sam);
99
+ }
100
+ };
101
+ return async (sample) => {
102
+ const nextTimestampWeWant = timestampTargets[0];
103
+ log_1.Log.trace(logLevel, `Received ${sample.type} sample with dts`, sample.decodingTimestamp, 'and cts', sample.timestamp);
104
+ if (sample.type === 'key') {
105
+ await decoder.flush();
106
+ queued.length = 0;
107
+ }
108
+ queued.push(sample);
109
+ if (sample.decodingTimestamp >=
110
+ timestampTargets[timestampTargets.length - 1] * media_parser_1.WEBCODECS_TIMESCALE) {
111
+ await doProcess();
112
+ await decoder.flush();
113
+ controller.abort();
114
+ return;
115
+ }
116
+ if (nextTimestampWeWant === undefined) {
117
+ throw new Error('this should not happen');
118
+ }
119
+ if (sample.decodingTimestamp >=
120
+ nextTimestampWeWant * media_parser_1.WEBCODECS_TIMESCALE) {
121
+ await doProcess();
122
+ if (timestampTargets.length === 0) {
123
+ await decoder.flush();
124
+ controller.abort();
125
+ }
126
+ }
127
+ return async () => {
128
+ await doProcess();
129
+ await decoder.flush();
130
+ if (lastFrame && lastFrameEmitted !== lastFrame) {
131
+ lastFrame.close();
132
+ }
133
+ };
134
+ };
135
+ },
136
+ })
137
+ .then(() => {
138
+ resolvers.resolve();
139
+ })
140
+ .catch((e) => {
141
+ if (!(0, media_parser_1.hasBeenAborted)(e)) {
142
+ resolvers.reject(e);
143
+ }
144
+ else {
145
+ resolvers.resolve();
146
+ }
147
+ })
148
+ .finally(() => {
149
+ if (lastFrame && lastFrameEmitted !== lastFrame) {
150
+ lastFrame.close();
151
+ }
152
+ signal?.removeEventListener('abort', abortListener);
153
+ });
154
+ return resolvers.promise;
155
+ };
156
+ exports.internalExtractFrames = internalExtractFrames;
@@ -0,0 +1 @@
1
+ export { ExtractFramesOnWebWorker, ExtractFramesOnWebWorkerProps, extractFramesOnWebWorker, } from './extract-frames-on-web-worker';
package/dist/worker.js ADDED
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.extractFramesOnWebWorker = void 0;
4
+ var extract_frames_on_web_worker_1 = require("./extract-frames-on-web-worker");
5
+ Object.defineProperty(exports, "extractFramesOnWebWorker", { enumerable: true, get: function () { return extract_frames_on_web_worker_1.extractFramesOnWebWorker; } });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remotion/webcodecs",
3
- "version": "4.0.330",
3
+ "version": "4.0.331",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "module": "dist/esm/index.mjs",
@@ -19,8 +19,8 @@
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.330",
23
- "@remotion/licensing": "4.0.330"
22
+ "@remotion/media-parser": "4.0.331",
23
+ "@remotion/licensing": "4.0.331"
24
24
  },
25
25
  "peerDependencies": {},
26
26
  "devDependencies": {
@@ -29,8 +29,8 @@
29
29
  "vite": "5.4.19",
30
30
  "@playwright/test": "1.51.1",
31
31
  "eslint": "9.19.0",
32
- "@remotion/eslint-config-internal": "4.0.330",
33
- "@remotion/example-videos": "4.0.330"
32
+ "@remotion/example-videos": "4.0.331",
33
+ "@remotion/eslint-config-internal": "4.0.331"
34
34
  },
35
35
  "keywords": [],
36
36
  "publishConfig": {
@@ -55,6 +55,12 @@
55
55
  "module": "./dist/esm/buffer.mjs",
56
56
  "import": "./dist/esm/buffer.mjs"
57
57
  },
58
+ "./worker": {
59
+ "types": "./dist/worker.d.ts",
60
+ "require": "./dist/worker.js",
61
+ "module": "./dist/esm/worker.mjs",
62
+ "import": "./dist/esm/worker.mjs"
63
+ },
58
64
  "./package.json": "./package.json"
59
65
  },
60
66
  "typesVersions": {
@@ -62,6 +68,9 @@
62
68
  "web-fs": [
63
69
  "dist/writers/web-fs.d.ts"
64
70
  ],
71
+ "worker": [
72
+ "dist/worker.d.ts"
73
+ ],
65
74
  "buffer": [
66
75
  "dist/writers/buffer.d.ts"
67
76
  ]