@remotion/webcodecs 4.0.236 → 4.0.239
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/audio-decoder.js +11 -7
- package/dist/audio-encoder.js +4 -2
- package/dist/auto-select-writer.d.ts +1 -1
- package/dist/auto-select-writer.js +22 -5
- package/dist/can-copy-audio-track.d.ts +4 -3
- package/dist/can-copy-audio-track.js +7 -6
- package/dist/can-copy-video-track.d.ts +4 -3
- package/dist/can-copy-video-track.js +7 -6
- package/dist/convert-media.js +2 -2
- package/dist/default-on-audio-track-handler.js +9 -11
- package/dist/default-on-video-track-handler.js +6 -15
- package/dist/esm/index.mjs +188 -140
- package/dist/io-manager/io-synchronizer.d.ts +4 -3
- package/dist/io-manager/io-synchronizer.js +25 -10
- package/dist/on-audio-track-handler.d.ts +4 -2
- package/dist/on-audio-track.d.ts +2 -2
- package/dist/on-audio-track.js +13 -6
- package/dist/on-frame.js +4 -1
- package/dist/on-video-track-handler.d.ts +4 -2
- package/dist/on-video-track.d.ts +2 -2
- package/dist/on-video-track.js +15 -6
- package/dist/video-decoder.js +5 -2
- package/dist/video-encoder.js +4 -2
- package/package.json +4 -4
- package/dist/codec-id.d.ts +0 -10
- package/dist/codec-id.js +0 -38
- package/dist/create-aac-codecprivate.d.ts +0 -14
- package/dist/create-aac-codecprivate.js +0 -72
- package/dist/io-manager/event-emitter.d.ts +0 -31
- package/dist/io-manager/event-emitter.js +0 -25
- package/dist/rotate-video.d.ts +0 -4
- package/dist/rotate-video.js +0 -43
- package/dist/test/aac-codecprivate.test.js +0 -12
- package/dist/with-resolvers.d.ts +0 -10
- package/dist/with-resolvers.js +0 -28
- /package/dist/test/{aac-codecprivate.test.d.ts → avi-to-mp4.test.d.ts} +0 -0
package/dist/esm/index.mjs
CHANGED
|
@@ -134,8 +134,8 @@ var makeIoSynchronizer = ({
|
|
|
134
134
|
let inputsSinceLastOutput = 0;
|
|
135
135
|
let inputs = [];
|
|
136
136
|
let keyframes = [];
|
|
137
|
-
let
|
|
138
|
-
const getUnprocessed = () =>
|
|
137
|
+
let _unprocessed = 0;
|
|
138
|
+
const getUnprocessed = () => _unprocessed;
|
|
139
139
|
const getUnemittedItems = () => {
|
|
140
140
|
inputs = inputs.filter((input) => Math.floor(input) > Math.floor(lastOutput));
|
|
141
141
|
return inputs.length;
|
|
@@ -167,7 +167,7 @@ var makeIoSynchronizer = ({
|
|
|
167
167
|
eventEmitter.dispatchEvent("output", {
|
|
168
168
|
timestamp
|
|
169
169
|
});
|
|
170
|
-
|
|
170
|
+
_unprocessed++;
|
|
171
171
|
printState("Got output");
|
|
172
172
|
};
|
|
173
173
|
const waitForOutput = () => {
|
|
@@ -189,11 +189,22 @@ var makeIoSynchronizer = ({
|
|
|
189
189
|
return promise;
|
|
190
190
|
};
|
|
191
191
|
const waitFor = async ({
|
|
192
|
-
|
|
192
|
+
unprocessed,
|
|
193
193
|
unemitted,
|
|
194
|
-
minimumProgress
|
|
194
|
+
minimumProgress,
|
|
195
|
+
signal
|
|
195
196
|
}) => {
|
|
196
|
-
const { timeoutPromise, clear } = makeTimeoutPromise(
|
|
197
|
+
const { timeoutPromise, clear } = makeTimeoutPromise([
|
|
198
|
+
`Waited too long for ${label}:`,
|
|
199
|
+
`${getUnemittedItems()} unemitted items`,
|
|
200
|
+
`${getUnprocessed()} unprocessed items`,
|
|
201
|
+
`minimum progress ${minimumProgress}`,
|
|
202
|
+
`smallest progress: ${progress.getSmallestProgress()}`,
|
|
203
|
+
`inputs: ${JSON.stringify(inputs)}`,
|
|
204
|
+
`last output: ${lastOutput}`
|
|
205
|
+
].join(`
|
|
206
|
+
`), 1e4);
|
|
207
|
+
signal.addEventListener("abort", clear);
|
|
197
208
|
await Promise.race([
|
|
198
209
|
timeoutPromise,
|
|
199
210
|
Promise.all([
|
|
@@ -203,24 +214,30 @@ var makeIoSynchronizer = ({
|
|
|
203
214
|
}
|
|
204
215
|
})(),
|
|
205
216
|
(async () => {
|
|
206
|
-
while (getUnprocessed() >
|
|
217
|
+
while (getUnprocessed() > unprocessed) {
|
|
207
218
|
await waitForProcessed();
|
|
208
219
|
}
|
|
209
220
|
})(),
|
|
210
|
-
minimumProgress === null ? Promise.resolve() : (async () => {
|
|
221
|
+
minimumProgress === null || progress.getSmallestProgress() === null ? Promise.resolve() : (async () => {
|
|
211
222
|
while (progress.getSmallestProgress() < minimumProgress) {
|
|
212
223
|
await progress.waitForProgress();
|
|
213
224
|
}
|
|
214
225
|
})()
|
|
215
226
|
])
|
|
216
227
|
]).finally(() => clear());
|
|
228
|
+
signal.removeEventListener("abort", clear);
|
|
217
229
|
};
|
|
218
|
-
const waitForFinish = async () => {
|
|
219
|
-
await waitFor({
|
|
230
|
+
const waitForFinish = async (signal) => {
|
|
231
|
+
await waitFor({
|
|
232
|
+
unprocessed: 0,
|
|
233
|
+
unemitted: 0,
|
|
234
|
+
minimumProgress: null,
|
|
235
|
+
signal
|
|
236
|
+
});
|
|
220
237
|
};
|
|
221
238
|
const onProcessed = () => {
|
|
222
239
|
eventEmitter.dispatchEvent("processed", {});
|
|
223
|
-
|
|
240
|
+
_unprocessed--;
|
|
224
241
|
};
|
|
225
242
|
return {
|
|
226
243
|
inputItem,
|
|
@@ -255,23 +272,23 @@ var createAudioDecoder = ({
|
|
|
255
272
|
});
|
|
256
273
|
let outputQueue = Promise.resolve();
|
|
257
274
|
const audioDecoder = new AudioDecoder({
|
|
258
|
-
output(
|
|
259
|
-
ioSynchronizer.onOutput(
|
|
275
|
+
output(frame) {
|
|
276
|
+
ioSynchronizer.onOutput(frame.timestamp + (frame.duration ?? 0));
|
|
260
277
|
const abortHandler = () => {
|
|
261
|
-
|
|
278
|
+
frame.close();
|
|
262
279
|
};
|
|
263
280
|
signal.addEventListener("abort", abortHandler, { once: true });
|
|
264
281
|
outputQueue = outputQueue.then(() => {
|
|
265
282
|
if (signal.aborted) {
|
|
266
283
|
return;
|
|
267
284
|
}
|
|
268
|
-
return onFrame(
|
|
285
|
+
return onFrame(frame);
|
|
269
286
|
}).then(() => {
|
|
270
287
|
ioSynchronizer.onProcessed();
|
|
271
288
|
signal.removeEventListener("abort", abortHandler);
|
|
272
289
|
return Promise.resolve();
|
|
273
290
|
}).catch((err) => {
|
|
274
|
-
|
|
291
|
+
frame.close();
|
|
275
292
|
onError(err);
|
|
276
293
|
});
|
|
277
294
|
},
|
|
@@ -295,10 +312,12 @@ var createAudioDecoder = ({
|
|
|
295
312
|
if (audioDecoder.state === "closed") {
|
|
296
313
|
return;
|
|
297
314
|
}
|
|
315
|
+
progressTracker.setPossibleLowestTimestamp(Math.min(audioSample.timestamp, audioSample.dts ?? Infinity, audioSample.cts ?? Infinity));
|
|
298
316
|
await ioSynchronizer.waitFor({
|
|
299
317
|
unemitted: 20,
|
|
300
|
-
|
|
301
|
-
minimumProgress: audioSample.timestamp - 1e7
|
|
318
|
+
unprocessed: 20,
|
|
319
|
+
minimumProgress: audioSample.timestamp - 1e7,
|
|
320
|
+
signal
|
|
302
321
|
});
|
|
303
322
|
const chunk = new EncodedAudioChunk(audioSample);
|
|
304
323
|
audioDecoder.decode(chunk);
|
|
@@ -319,7 +338,7 @@ var createAudioDecoder = ({
|
|
|
319
338
|
} catch {
|
|
320
339
|
}
|
|
321
340
|
await queue;
|
|
322
|
-
await ioSynchronizer.waitForFinish();
|
|
341
|
+
await ioSynchronizer.waitForFinish(signal);
|
|
323
342
|
await outputQueue;
|
|
324
343
|
},
|
|
325
344
|
close,
|
|
@@ -416,10 +435,12 @@ var createAudioEncoder = ({
|
|
|
416
435
|
if (encoder.state === "closed") {
|
|
417
436
|
return;
|
|
418
437
|
}
|
|
438
|
+
progressTracker.setPossibleLowestTimestamp(audioData.timestamp);
|
|
419
439
|
await ioSynchronizer.waitFor({
|
|
420
440
|
unemitted: 20,
|
|
421
|
-
|
|
422
|
-
minimumProgress: audioData.timestamp - 1e7
|
|
441
|
+
unprocessed: 20,
|
|
442
|
+
minimumProgress: audioData.timestamp - 1e7,
|
|
443
|
+
signal
|
|
423
444
|
});
|
|
424
445
|
if (encoder.state === "closed") {
|
|
425
446
|
return;
|
|
@@ -446,7 +467,7 @@ var createAudioEncoder = ({
|
|
|
446
467
|
},
|
|
447
468
|
waitForFinish: async () => {
|
|
448
469
|
await encoder.flush();
|
|
449
|
-
await ioSynchronizer.waitForFinish();
|
|
470
|
+
await ioSynchronizer.waitForFinish(signal);
|
|
450
471
|
await prom;
|
|
451
472
|
},
|
|
452
473
|
close,
|
|
@@ -458,39 +479,41 @@ var createAudioEncoder = ({
|
|
|
458
479
|
// src/can-copy-audio-track.ts
|
|
459
480
|
var canCopyAudioTrack = ({
|
|
460
481
|
inputCodec,
|
|
461
|
-
|
|
482
|
+
outputContainer,
|
|
483
|
+
inputContainer
|
|
462
484
|
}) => {
|
|
463
|
-
if (
|
|
485
|
+
if (outputContainer === "webm") {
|
|
464
486
|
return inputCodec === "opus";
|
|
465
487
|
}
|
|
466
|
-
if (
|
|
467
|
-
return inputCodec === "aac";
|
|
488
|
+
if (outputContainer === "mp4") {
|
|
489
|
+
return inputCodec === "aac" && (inputContainer === "mp4" || inputContainer === "avi");
|
|
468
490
|
}
|
|
469
|
-
if (
|
|
491
|
+
if (outputContainer === "wav") {
|
|
470
492
|
return false;
|
|
471
493
|
}
|
|
472
|
-
throw new Error(`Unhandled
|
|
494
|
+
throw new Error(`Unhandled container: ${outputContainer}`);
|
|
473
495
|
};
|
|
474
496
|
// src/can-copy-video-track.ts
|
|
475
497
|
var canCopyVideoTrack = ({
|
|
476
498
|
inputCodec,
|
|
477
|
-
|
|
499
|
+
outputContainer,
|
|
478
500
|
inputRotation,
|
|
479
|
-
rotationToApply
|
|
501
|
+
rotationToApply,
|
|
502
|
+
inputContainer
|
|
480
503
|
}) => {
|
|
481
504
|
if (normalizeVideoRotation(inputRotation) !== normalizeVideoRotation(rotationToApply)) {
|
|
482
505
|
return false;
|
|
483
506
|
}
|
|
484
|
-
if (
|
|
507
|
+
if (outputContainer === "webm") {
|
|
485
508
|
return inputCodec === "vp8" || inputCodec === "vp9";
|
|
486
509
|
}
|
|
487
|
-
if (
|
|
488
|
-
return inputCodec === "h264";
|
|
510
|
+
if (outputContainer === "mp4") {
|
|
511
|
+
return inputCodec === "h264" && (inputContainer === "mp4" || inputContainer === "avi");
|
|
489
512
|
}
|
|
490
|
-
if (
|
|
513
|
+
if (outputContainer === "wav") {
|
|
491
514
|
return false;
|
|
492
515
|
}
|
|
493
|
-
throw new Error(`Unhandled codec: ${
|
|
516
|
+
throw new Error(`Unhandled codec: ${outputContainer}`);
|
|
494
517
|
};
|
|
495
518
|
// src/audio-decoder-config.ts
|
|
496
519
|
var getAudioDecoderConfig = async (config) => {
|
|
@@ -671,24 +694,45 @@ var canReencodeVideoTrack = async ({
|
|
|
671
694
|
};
|
|
672
695
|
// src/convert-media.ts
|
|
673
696
|
import {
|
|
674
|
-
MediaParserInternals as
|
|
697
|
+
MediaParserInternals as MediaParserInternals9,
|
|
675
698
|
parseMedia
|
|
676
699
|
} from "@remotion/media-parser";
|
|
677
700
|
|
|
678
701
|
// src/auto-select-writer.ts
|
|
679
|
-
import { bufferWriter } from "@remotion/media-parser/buffer";
|
|
680
702
|
import { canUseWebFsWriter, webFsWriter } from "@remotion/media-parser/web-fs";
|
|
703
|
+
import {
|
|
704
|
+
MediaParserInternals as MediaParserInternals4
|
|
705
|
+
} from "@remotion/media-parser";
|
|
706
|
+
import { bufferWriter } from "@remotion/media-parser/buffer";
|
|
681
707
|
var autoSelectWriter = async (writer, logLevel) => {
|
|
682
708
|
if (writer) {
|
|
683
709
|
Log.verbose(logLevel, "Using writer provided by user");
|
|
684
710
|
return writer;
|
|
685
711
|
}
|
|
686
712
|
Log.verbose(logLevel, "Determining best writer");
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
713
|
+
const isOffline = !navigator.onLine;
|
|
714
|
+
if (isOffline) {
|
|
715
|
+
Log.verbose(logLevel, "Offline mode detected, using buffer writer");
|
|
716
|
+
return bufferWriter;
|
|
717
|
+
}
|
|
718
|
+
try {
|
|
719
|
+
const {
|
|
720
|
+
promise: timeout,
|
|
721
|
+
reject,
|
|
722
|
+
resolve
|
|
723
|
+
} = MediaParserInternals4.withResolvers();
|
|
724
|
+
const time = setTimeout(() => reject(new Error("WebFS check timeout")), 2000);
|
|
725
|
+
const webFsSupported = await Promise.race([canUseWebFsWriter(), timeout]);
|
|
726
|
+
resolve();
|
|
727
|
+
clearTimeout(time);
|
|
728
|
+
if (webFsSupported) {
|
|
729
|
+
Log.verbose(logLevel, "Using WebFS writer because it is supported");
|
|
730
|
+
return webFsWriter;
|
|
731
|
+
}
|
|
732
|
+
} catch (err) {
|
|
733
|
+
Log.verbose(logLevel, `WebFS check failed: ${err}. Falling back to buffer writer`);
|
|
690
734
|
}
|
|
691
|
-
Log.verbose(logLevel, "Using buffer writer because WebFS writer is not supported");
|
|
735
|
+
Log.verbose(logLevel, "Using buffer writer because WebFS writer is not supported or unavailable");
|
|
692
736
|
return bufferWriter;
|
|
693
737
|
};
|
|
694
738
|
|
|
@@ -716,7 +760,7 @@ var generateOutputFilename = (source, container) => {
|
|
|
716
760
|
|
|
717
761
|
// src/on-audio-track.ts
|
|
718
762
|
import {
|
|
719
|
-
MediaParserInternals as
|
|
763
|
+
MediaParserInternals as MediaParserInternals6
|
|
720
764
|
} from "@remotion/media-parser";
|
|
721
765
|
|
|
722
766
|
// src/convert-encoded-chunk.ts
|
|
@@ -735,59 +779,56 @@ var convertEncodedChunk = (chunk, trackId) => {
|
|
|
735
779
|
};
|
|
736
780
|
|
|
737
781
|
// src/default-on-audio-track-handler.ts
|
|
738
|
-
import { MediaParserInternals as
|
|
739
|
-
|
|
740
|
-
// src/get-default-audio-codec.ts
|
|
741
|
-
var getDefaultAudioCodec = ({
|
|
742
|
-
container
|
|
743
|
-
}) => {
|
|
744
|
-
if (container === "webm") {
|
|
745
|
-
return "opus";
|
|
746
|
-
}
|
|
747
|
-
if (container === "mp4") {
|
|
748
|
-
return "aac";
|
|
749
|
-
}
|
|
750
|
-
if (container === "wav") {
|
|
751
|
-
return "wav";
|
|
752
|
-
}
|
|
753
|
-
throw new Error(`Unhandled container: ${container}`);
|
|
754
|
-
};
|
|
755
|
-
|
|
756
|
-
// src/default-on-audio-track-handler.ts
|
|
782
|
+
import { MediaParserInternals as MediaParserInternals5 } from "@remotion/media-parser";
|
|
757
783
|
var DEFAULT_BITRATE = 128000;
|
|
758
784
|
var defaultOnAudioTrackHandler = async ({
|
|
759
785
|
track,
|
|
760
786
|
defaultAudioCodec,
|
|
761
787
|
logLevel,
|
|
762
|
-
|
|
788
|
+
canCopyTrack
|
|
763
789
|
}) => {
|
|
764
790
|
const bitrate = DEFAULT_BITRATE;
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
container
|
|
768
|
-
});
|
|
769
|
-
if (canCopy) {
|
|
770
|
-
MediaParserInternals4.Log.verbose(logLevel, `Track ${track.trackId} (audio): Can copy track, therefore copying`);
|
|
791
|
+
if (canCopyTrack) {
|
|
792
|
+
MediaParserInternals5.Log.verbose(logLevel, `Track ${track.trackId} (audio): Can copy track, therefore copying`);
|
|
771
793
|
return Promise.resolve({ type: "copy" });
|
|
772
794
|
}
|
|
773
|
-
|
|
795
|
+
if (defaultAudioCodec === null) {
|
|
796
|
+
MediaParserInternals5.Log.verbose(logLevel, `Track ${track.trackId} (audio): Container does not support audio, dropping audio`);
|
|
797
|
+
return Promise.resolve({ type: "drop" });
|
|
798
|
+
}
|
|
774
799
|
const canReencode = await canReencodeAudioTrack({
|
|
775
|
-
audioCodec,
|
|
800
|
+
audioCodec: defaultAudioCodec,
|
|
776
801
|
track,
|
|
777
802
|
bitrate
|
|
778
803
|
});
|
|
779
804
|
if (canReencode) {
|
|
780
|
-
|
|
805
|
+
MediaParserInternals5.Log.verbose(logLevel, `Track ${track.trackId} (audio): Cannot copy, but re-encode, therefore re-encoding`);
|
|
781
806
|
return Promise.resolve({
|
|
782
807
|
type: "reencode",
|
|
783
808
|
bitrate,
|
|
784
|
-
audioCodec
|
|
809
|
+
audioCodec: defaultAudioCodec
|
|
785
810
|
});
|
|
786
811
|
}
|
|
787
|
-
|
|
812
|
+
MediaParserInternals5.Log.verbose(logLevel, `Track ${track.trackId} (audio): Can neither re-encode nor copy, failing render`);
|
|
788
813
|
return Promise.resolve({ type: "fail" });
|
|
789
814
|
};
|
|
790
815
|
|
|
816
|
+
// src/get-default-audio-codec.ts
|
|
817
|
+
var getDefaultAudioCodec = ({
|
|
818
|
+
container
|
|
819
|
+
}) => {
|
|
820
|
+
if (container === "webm") {
|
|
821
|
+
return "opus";
|
|
822
|
+
}
|
|
823
|
+
if (container === "mp4") {
|
|
824
|
+
return "aac";
|
|
825
|
+
}
|
|
826
|
+
if (container === "wav") {
|
|
827
|
+
return "wav";
|
|
828
|
+
}
|
|
829
|
+
throw new Error(`Unhandled container: ${container}`);
|
|
830
|
+
};
|
|
831
|
+
|
|
791
832
|
// src/on-audio-track.ts
|
|
792
833
|
var makeAudioTrackHandler = ({
|
|
793
834
|
state,
|
|
@@ -797,20 +838,27 @@ var makeAudioTrackHandler = ({
|
|
|
797
838
|
onMediaStateUpdate,
|
|
798
839
|
onAudioTrack,
|
|
799
840
|
logLevel,
|
|
800
|
-
|
|
841
|
+
outputContainer,
|
|
801
842
|
progressTracker
|
|
802
|
-
}) => async (track) => {
|
|
843
|
+
}) => async ({ track, container: inputContainer }) => {
|
|
844
|
+
const canCopyTrack = canCopyAudioTrack({
|
|
845
|
+
inputCodec: track.codecWithoutConfig,
|
|
846
|
+
outputContainer,
|
|
847
|
+
inputContainer
|
|
848
|
+
});
|
|
803
849
|
const audioOperation = await (onAudioTrack ?? defaultOnAudioTrackHandler)({
|
|
804
|
-
defaultAudioCodec: audioCodec,
|
|
850
|
+
defaultAudioCodec: audioCodec ?? getDefaultAudioCodec({ container: outputContainer }),
|
|
805
851
|
track,
|
|
806
852
|
logLevel,
|
|
807
|
-
|
|
853
|
+
outputContainer,
|
|
854
|
+
inputContainer,
|
|
855
|
+
canCopyTrack
|
|
808
856
|
});
|
|
809
857
|
if (audioOperation.type === "drop") {
|
|
810
858
|
return null;
|
|
811
859
|
}
|
|
812
860
|
if (audioOperation.type === "fail") {
|
|
813
|
-
throw new error_cause_default(`Audio track with ID ${track.trackId}
|
|
861
|
+
throw new error_cause_default(`Audio track with ID ${track.trackId} resolved with {"type": "fail"}. This could mean that this audio track could neither be copied to the output container or re-encoded. You have the option to drop the track instead of failing it: https://remotion.dev/docs/webcodecs/track-transformation`);
|
|
814
862
|
}
|
|
815
863
|
if (audioOperation.type === "copy") {
|
|
816
864
|
const addedTrack = await state.addTrack({
|
|
@@ -827,7 +875,6 @@ var makeAudioTrackHandler = ({
|
|
|
827
875
|
chunk: audioSample,
|
|
828
876
|
trackNumber: addedTrack.trackNumber,
|
|
829
877
|
isVideo: false,
|
|
830
|
-
timescale: track.timescale,
|
|
831
878
|
codecPrivate: track.codecPrivate
|
|
832
879
|
});
|
|
833
880
|
onMediaStateUpdate?.((prevState) => {
|
|
@@ -858,7 +905,7 @@ var makeAudioTrackHandler = ({
|
|
|
858
905
|
abortConversion(new error_cause_default(`Could not configure audio decoder of track ${track.trackId}`));
|
|
859
906
|
return null;
|
|
860
907
|
}
|
|
861
|
-
const codecPrivate = audioOperation.audioCodec === "aac" ?
|
|
908
|
+
const codecPrivate = audioOperation.audioCodec === "aac" ? MediaParserInternals6.createAacCodecPrivate({
|
|
862
909
|
audioObjectType: 2,
|
|
863
910
|
sampleRate: track.sampleRate,
|
|
864
911
|
channelConfiguration: track.numberOfChannels
|
|
@@ -880,7 +927,6 @@ var makeAudioTrackHandler = ({
|
|
|
880
927
|
chunk: convertEncodedChunk(chunk, trackNumber),
|
|
881
928
|
trackNumber,
|
|
882
929
|
isVideo: false,
|
|
883
|
-
timescale: track.timescale,
|
|
884
930
|
codecPrivate
|
|
885
931
|
});
|
|
886
932
|
onMediaStateUpdate?.((prevState) => {
|
|
@@ -940,63 +986,54 @@ var arrayBufferToUint8Array = (buffer) => {
|
|
|
940
986
|
};
|
|
941
987
|
|
|
942
988
|
// src/default-on-video-track-handler.ts
|
|
943
|
-
import { MediaParserInternals as
|
|
944
|
-
|
|
945
|
-
// src/get-default-video-codec.ts
|
|
946
|
-
var getDefaultVideoCodec = ({
|
|
947
|
-
container
|
|
948
|
-
}) => {
|
|
949
|
-
if (container === "webm") {
|
|
950
|
-
return "vp8";
|
|
951
|
-
}
|
|
952
|
-
if (container === "mp4") {
|
|
953
|
-
return "h264";
|
|
954
|
-
}
|
|
955
|
-
if (container === "wav") {
|
|
956
|
-
return null;
|
|
957
|
-
}
|
|
958
|
-
throw new Error(`Unhandled container: ${container}`);
|
|
959
|
-
};
|
|
960
|
-
|
|
961
|
-
// src/default-on-video-track-handler.ts
|
|
989
|
+
import { MediaParserInternals as MediaParserInternals7 } from "@remotion/media-parser";
|
|
962
990
|
var defaultOnVideoTrackHandler = async ({
|
|
963
991
|
track,
|
|
964
992
|
defaultVideoCodec,
|
|
965
993
|
logLevel,
|
|
966
|
-
|
|
967
|
-
|
|
994
|
+
rotate,
|
|
995
|
+
canCopyTrack
|
|
968
996
|
}) => {
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
container,
|
|
972
|
-
inputRotation: track.rotation,
|
|
973
|
-
rotationToApply: rotate
|
|
974
|
-
});
|
|
975
|
-
if (canCopy) {
|
|
976
|
-
MediaParserInternals6.Log.verbose(logLevel, `Track ${track.trackId} (video): Can copy, therefore copying`);
|
|
997
|
+
if (canCopyTrack) {
|
|
998
|
+
MediaParserInternals7.Log.verbose(logLevel, `Track ${track.trackId} (video): Can copy, therefore copying`);
|
|
977
999
|
return Promise.resolve({ type: "copy" });
|
|
978
1000
|
}
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
MediaParserInternals6.Log.verbose(logLevel, `Track ${track.trackId} (video): No default video codec, therefore dropping`);
|
|
1001
|
+
if (defaultVideoCodec === null) {
|
|
1002
|
+
MediaParserInternals7.Log.verbose(logLevel, `Track ${track.trackId} (video): Is audio container, therefore dropping video`);
|
|
982
1003
|
return Promise.resolve({ type: "drop" });
|
|
983
1004
|
}
|
|
984
1005
|
const canReencode = await canReencodeVideoTrack({
|
|
985
|
-
videoCodec,
|
|
1006
|
+
videoCodec: defaultVideoCodec,
|
|
986
1007
|
track
|
|
987
1008
|
});
|
|
988
1009
|
if (canReencode) {
|
|
989
|
-
|
|
1010
|
+
MediaParserInternals7.Log.verbose(logLevel, `Track ${track.trackId} (video): Cannot copy, but re-enconde, therefore re-encoding`);
|
|
990
1011
|
return Promise.resolve({
|
|
991
1012
|
type: "reencode",
|
|
992
|
-
videoCodec,
|
|
1013
|
+
videoCodec: defaultVideoCodec,
|
|
993
1014
|
rotation: rotate - track.rotation
|
|
994
1015
|
});
|
|
995
1016
|
}
|
|
996
|
-
|
|
1017
|
+
MediaParserInternals7.Log.verbose(logLevel, `Track ${track.trackId} (video): Can neither copy nor re-encode, therefore failing`);
|
|
997
1018
|
return Promise.resolve({ type: "fail" });
|
|
998
1019
|
};
|
|
999
1020
|
|
|
1021
|
+
// src/get-default-video-codec.ts
|
|
1022
|
+
var getDefaultVideoCodec = ({
|
|
1023
|
+
container
|
|
1024
|
+
}) => {
|
|
1025
|
+
if (container === "webm") {
|
|
1026
|
+
return "vp8";
|
|
1027
|
+
}
|
|
1028
|
+
if (container === "mp4") {
|
|
1029
|
+
return "h264";
|
|
1030
|
+
}
|
|
1031
|
+
if (container === "wav") {
|
|
1032
|
+
return null;
|
|
1033
|
+
}
|
|
1034
|
+
throw new Error(`Unhandled container: ${container}`);
|
|
1035
|
+
};
|
|
1036
|
+
|
|
1000
1037
|
// src/convert-to-correct-videoframe.ts
|
|
1001
1038
|
var needsToCorrectVideoFrame = ({
|
|
1002
1039
|
videoFrame,
|
|
@@ -1056,7 +1093,7 @@ var onFrame = async ({
|
|
|
1056
1093
|
if (userProcessedFrame.displayHeight !== rotated.displayHeight) {
|
|
1057
1094
|
throw new Error(`Returned VideoFrame of track ${track.trackId} has different displayHeight (${userProcessedFrame.displayHeight}) than the input frame (${userProcessedFrame.displayHeight})`);
|
|
1058
1095
|
}
|
|
1059
|
-
if (userProcessedFrame.timestamp !== rotated.timestamp) {
|
|
1096
|
+
if (userProcessedFrame.timestamp !== rotated.timestamp && !isSafari()) {
|
|
1060
1097
|
throw new Error(`Returned VideoFrame of track ${track.trackId} has different timestamp (${userProcessedFrame.timestamp}) than the input frame (${rotated.timestamp}). When calling new VideoFrame(), pass {timestamp: frame.timestamp} as second argument`);
|
|
1061
1098
|
}
|
|
1062
1099
|
if ((userProcessedFrame.duration ?? 0) !== (rotated.duration ?? 0)) {
|
|
@@ -1135,10 +1172,12 @@ var createVideoDecoder = ({
|
|
|
1135
1172
|
if (videoDecoder.state === "closed") {
|
|
1136
1173
|
return;
|
|
1137
1174
|
}
|
|
1175
|
+
progress.setPossibleLowestTimestamp(Math.min(sample.timestamp, sample.dts ?? Infinity, sample.cts ?? Infinity));
|
|
1138
1176
|
await ioSynchronizer.waitFor({
|
|
1139
1177
|
unemitted: 20,
|
|
1140
|
-
|
|
1141
|
-
minimumProgress: sample.timestamp - 5000000
|
|
1178
|
+
unprocessed: 2,
|
|
1179
|
+
minimumProgress: sample.timestamp - 5000000,
|
|
1180
|
+
signal
|
|
1142
1181
|
});
|
|
1143
1182
|
if (sample.type === "key") {
|
|
1144
1183
|
await videoDecoder.flush();
|
|
@@ -1155,7 +1194,7 @@ var createVideoDecoder = ({
|
|
|
1155
1194
|
waitForFinish: async () => {
|
|
1156
1195
|
await videoDecoder.flush();
|
|
1157
1196
|
Log.verbose(logLevel, "Flushed video decoder");
|
|
1158
|
-
await ioSynchronizer.waitForFinish();
|
|
1197
|
+
await ioSynchronizer.waitForFinish(signal);
|
|
1159
1198
|
Log.verbose(logLevel, "IO synchro finished");
|
|
1160
1199
|
await outputQueue;
|
|
1161
1200
|
Log.verbose(logLevel, "Output queue finished");
|
|
@@ -1226,10 +1265,12 @@ var createVideoEncoder = ({
|
|
|
1226
1265
|
if (encoder.state === "closed") {
|
|
1227
1266
|
return;
|
|
1228
1267
|
}
|
|
1268
|
+
progress.setPossibleLowestTimestamp(frame.timestamp);
|
|
1229
1269
|
await ioSynchronizer.waitFor({
|
|
1230
1270
|
unemitted: 10,
|
|
1231
|
-
|
|
1232
|
-
minimumProgress: frame.timestamp - 5000000
|
|
1271
|
+
unprocessed: 10,
|
|
1272
|
+
minimumProgress: frame.timestamp - 5000000,
|
|
1273
|
+
signal
|
|
1233
1274
|
});
|
|
1234
1275
|
if (encoder.state === "closed") {
|
|
1235
1276
|
return;
|
|
@@ -1250,7 +1291,7 @@ var createVideoEncoder = ({
|
|
|
1250
1291
|
waitForFinish: async () => {
|
|
1251
1292
|
await encoder.flush();
|
|
1252
1293
|
await outputQueue;
|
|
1253
|
-
await ioSynchronizer.waitForFinish();
|
|
1294
|
+
await ioSynchronizer.waitForFinish(signal);
|
|
1254
1295
|
},
|
|
1255
1296
|
close,
|
|
1256
1297
|
flush: async () => {
|
|
@@ -1269,25 +1310,34 @@ var makeVideoTrackHandler = ({
|
|
|
1269
1310
|
defaultVideoCodec,
|
|
1270
1311
|
onVideoTrack,
|
|
1271
1312
|
logLevel,
|
|
1272
|
-
|
|
1313
|
+
outputContainer,
|
|
1273
1314
|
rotate,
|
|
1274
1315
|
progress
|
|
1275
|
-
}) => async (track) => {
|
|
1316
|
+
}) => async ({ track, container: inputContainer }) => {
|
|
1276
1317
|
if (controller.signal.aborted) {
|
|
1277
1318
|
throw new error_cause_default("Aborted");
|
|
1278
1319
|
}
|
|
1320
|
+
const canCopyTrack = canCopyVideoTrack({
|
|
1321
|
+
inputCodec: track.codecWithoutConfig,
|
|
1322
|
+
inputContainer,
|
|
1323
|
+
inputRotation: track.rotation,
|
|
1324
|
+
outputContainer,
|
|
1325
|
+
rotationToApply: rotate
|
|
1326
|
+
});
|
|
1279
1327
|
const videoOperation = await (onVideoTrack ?? defaultOnVideoTrackHandler)({
|
|
1280
1328
|
track,
|
|
1281
|
-
defaultVideoCodec,
|
|
1329
|
+
defaultVideoCodec: defaultVideoCodec ?? getDefaultVideoCodec({ container: outputContainer }),
|
|
1282
1330
|
logLevel,
|
|
1283
|
-
|
|
1284
|
-
rotate
|
|
1331
|
+
outputContainer,
|
|
1332
|
+
rotate,
|
|
1333
|
+
inputContainer,
|
|
1334
|
+
canCopyTrack
|
|
1285
1335
|
});
|
|
1286
1336
|
if (videoOperation.type === "drop") {
|
|
1287
1337
|
return null;
|
|
1288
1338
|
}
|
|
1289
1339
|
if (videoOperation.type === "fail") {
|
|
1290
|
-
throw new error_cause_default(`Video track with ID ${track.trackId} could
|
|
1340
|
+
throw new error_cause_default(`Video track with ID ${track.trackId} could with {"type": "fail"}. This could mean that this video track could neither be copied to the output container or re-encoded. You have the option to drop the track instead of failing it: https://remotion.dev/docs/webcodecs/track-transformation`);
|
|
1291
1341
|
}
|
|
1292
1342
|
if (videoOperation.type === "copy") {
|
|
1293
1343
|
Log.verbose(logLevel, `Copying video track with codec ${track.codec} and timescale ${track.timescale}`);
|
|
@@ -1305,7 +1355,6 @@ var makeVideoTrackHandler = ({
|
|
|
1305
1355
|
chunk: sample,
|
|
1306
1356
|
trackNumber: videoTrack.trackNumber,
|
|
1307
1357
|
isVideo: true,
|
|
1308
|
-
timescale: track.timescale,
|
|
1309
1358
|
codecPrivate: track.codecPrivate
|
|
1310
1359
|
});
|
|
1311
1360
|
onMediaStateUpdate?.((prevState) => {
|
|
@@ -1356,7 +1405,6 @@ var makeVideoTrackHandler = ({
|
|
|
1356
1405
|
chunk: convertEncodedChunk(chunk, trackNumber),
|
|
1357
1406
|
trackNumber,
|
|
1358
1407
|
isVideo: true,
|
|
1359
|
-
timescale: track.timescale,
|
|
1360
1408
|
codecPrivate: arrayBufferToUint8Array(metadata?.decoderConfig?.description ?? null)
|
|
1361
1409
|
});
|
|
1362
1410
|
onMediaStateUpdate?.((prevState) => {
|
|
@@ -1413,16 +1461,16 @@ var makeVideoTrackHandler = ({
|
|
|
1413
1461
|
};
|
|
1414
1462
|
|
|
1415
1463
|
// src/select-container-creator.ts
|
|
1416
|
-
import { MediaParserInternals as
|
|
1464
|
+
import { MediaParserInternals as MediaParserInternals8 } from "@remotion/media-parser";
|
|
1417
1465
|
var selectContainerCreator = (container) => {
|
|
1418
1466
|
if (container === "mp4") {
|
|
1419
|
-
return
|
|
1467
|
+
return MediaParserInternals8.createIsoBaseMedia;
|
|
1420
1468
|
}
|
|
1421
1469
|
if (container === "wav") {
|
|
1422
|
-
return
|
|
1470
|
+
return MediaParserInternals8.createWav;
|
|
1423
1471
|
}
|
|
1424
1472
|
if (container === "webm") {
|
|
1425
|
-
return
|
|
1473
|
+
return MediaParserInternals8.createMatroskaMedia;
|
|
1426
1474
|
}
|
|
1427
1475
|
throw new Error(`Unsupported container: ${container}`);
|
|
1428
1476
|
};
|
|
@@ -1507,7 +1555,7 @@ var convertMedia = async function({
|
|
|
1507
1555
|
if (videoCodec && videoCodec !== "vp8" && videoCodec !== "vp9") {
|
|
1508
1556
|
return Promise.reject(new TypeError('Only `videoCodec: "vp8"` and `videoCodec: "vp9"` are supported currently'));
|
|
1509
1557
|
}
|
|
1510
|
-
const { resolve, reject, getPromiseToImmediatelyReturn } =
|
|
1558
|
+
const { resolve, reject, getPromiseToImmediatelyReturn } = MediaParserInternals9.withResolversAndWaitForReturn();
|
|
1511
1559
|
const controller = new AbortController;
|
|
1512
1560
|
const abortConversion = (errCause) => {
|
|
1513
1561
|
reject(errCause);
|
|
@@ -1525,7 +1573,7 @@ var convertMedia = async function({
|
|
|
1525
1573
|
everyMilliseconds: progressIntervalInMs ?? 100,
|
|
1526
1574
|
signal: controller.signal
|
|
1527
1575
|
});
|
|
1528
|
-
const progressTracker =
|
|
1576
|
+
const progressTracker = MediaParserInternals9.makeProgressTracker();
|
|
1529
1577
|
const state = await creator({
|
|
1530
1578
|
filename: generateOutputFilename(src, container),
|
|
1531
1579
|
writer: await autoSelectWriter(writer, logLevel),
|
|
@@ -1564,7 +1612,7 @@ var convertMedia = async function({
|
|
|
1564
1612
|
defaultVideoCodec: videoCodec ?? null,
|
|
1565
1613
|
onVideoTrack: userVideoResolver ?? null,
|
|
1566
1614
|
logLevel,
|
|
1567
|
-
container,
|
|
1615
|
+
outputContainer: container,
|
|
1568
1616
|
rotate: rotate ?? 0,
|
|
1569
1617
|
progress: progressTracker
|
|
1570
1618
|
});
|
|
@@ -1576,7 +1624,7 @@ var convertMedia = async function({
|
|
|
1576
1624
|
state,
|
|
1577
1625
|
onAudioTrack: userAudioResolver ?? null,
|
|
1578
1626
|
logLevel,
|
|
1579
|
-
container,
|
|
1627
|
+
outputContainer: container,
|
|
1580
1628
|
progressTracker
|
|
1581
1629
|
});
|
|
1582
1630
|
parseMedia({
|
|
@@ -7,12 +7,13 @@ export declare const makeIoSynchronizer: ({ logLevel, label, progress, }: {
|
|
|
7
7
|
}) => {
|
|
8
8
|
inputItem: (timestamp: number, keyFrame: boolean) => void;
|
|
9
9
|
onOutput: (timestamp: number) => void;
|
|
10
|
-
waitFor: ({
|
|
10
|
+
waitFor: ({ unprocessed, unemitted, minimumProgress, signal, }: {
|
|
11
11
|
unemitted: number;
|
|
12
|
-
|
|
12
|
+
unprocessed: number;
|
|
13
13
|
minimumProgress: number | null;
|
|
14
|
+
signal: AbortSignal;
|
|
14
15
|
}) => Promise<void>;
|
|
15
|
-
waitForFinish: () => Promise<void>;
|
|
16
|
+
waitForFinish: (signal: AbortSignal) => Promise<void>;
|
|
16
17
|
onProcessed: () => void;
|
|
17
18
|
getUnprocessed: () => number;
|
|
18
19
|
};
|