@remotion/webcodecs 4.0.251 → 4.0.253
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.d.ts +3 -2
- package/dist/audio-decoder.js +11 -9
- package/dist/audio-encoder.d.ts +4 -3
- package/dist/audio-encoder.js +10 -9
- package/dist/auto-select-writer.d.ts +1 -1
- package/dist/can-copy-video-track.js +2 -1
- package/dist/choose-correct-hevc-profile.d.ts +5 -0
- package/dist/choose-correct-hevc-profile.js +42 -0
- package/dist/controller.d.ts +16 -0
- package/dist/controller.js +16 -0
- package/dist/convert-media.d.ts +6 -6
- package/dist/convert-media.js +53 -21
- package/dist/create/iso-base-media/codec-specific/create-codec-specific-data.d.ts +11 -0
- package/dist/create/iso-base-media/codec-specific/create-codec-specific-data.js +29 -11
- package/dist/create/iso-base-media/codec-specific/hvc1.d.ts +2 -0
- package/dist/create/iso-base-media/codec-specific/hvc1.js +48 -0
- package/dist/create/iso-base-media/create-iso-base-media.js +8 -4
- package/dist/create/iso-base-media/serialize-track.js +3 -1
- package/dist/create/iso-base-media/trak/mdia/minf/stbl/stsd/create-hvcc.d.ts +1 -0
- package/dist/create/iso-base-media/trak/mdia/minf/stbl/stsd/create-hvcc.js +16 -0
- package/dist/create/matroska/cluster.d.ts +1 -1
- package/dist/create/matroska/create-matroska-media.js +8 -4
- package/dist/create/matroska/matroska-trackentry.js +3 -0
- package/dist/create/matroska/matroska-utils.d.ts +1 -1
- package/dist/create/media-fn.d.ts +2 -3
- package/dist/create/wav/create-wav.js +8 -4
- package/dist/emitter.d.ts +20 -0
- package/dist/emitter.js +29 -0
- package/dist/esm/buffer.mjs +5 -22
- package/dist/esm/index.mjs +633 -159
- package/dist/esm/web-fs.mjs +4 -22
- package/dist/get-available-containers.d.ts +1 -2
- package/dist/get-available-containers.js +3 -3
- package/dist/get-available-video-codecs.d.ts +1 -2
- package/dist/get-available-video-codecs.js +3 -4
- package/dist/get-codec-string.d.ts +7 -0
- package/dist/get-codec-string.js +21 -0
- package/dist/hevc-levels.d.ts +13 -0
- package/dist/hevc-levels.js +233 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3 -1
- package/dist/io-manager/io-synchronizer.d.ts +4 -3
- package/dist/io-manager/io-synchronizer.js +19 -14
- package/dist/io-manager/make-timeout-promise.d.ts +6 -1
- package/dist/io-manager/make-timeout-promise.js +23 -5
- package/dist/on-audio-track.d.ts +2 -2
- package/dist/on-audio-track.js +13 -16
- package/dist/on-video-track.d.ts +2 -2
- package/dist/on-video-track.js +10 -14
- package/dist/select-container-creator.d.ts +1 -1
- package/dist/test/remux-serverside.test.js +2 -2
- package/dist/throttled-state-update.d.ts +1 -1
- package/dist/throttled-state-update.js +2 -2
- package/dist/video-decoder.d.ts +3 -2
- package/dist/video-decoder.js +10 -8
- package/dist/video-encoder-config.js +4 -6
- package/dist/video-encoder.d.ts +4 -3
- package/dist/video-encoder.js +13 -8
- package/dist/wav-audio-encoder.d.ts +1 -1
- package/dist/wav-audio-encoder.js +2 -2
- package/dist/webcodecs-controller.d.ts +16 -0
- package/dist/webcodecs-controller.js +16 -0
- package/dist/writers/buffer-implementation/writer.d.ts +1 -1
- package/dist/writers/buffer-implementation/writer.js +5 -4
- package/dist/writers/buffer.d.ts +1 -1
- package/dist/writers/web-fs.d.ts +1 -1
- package/dist/writers/web-fs.js +4 -4
- package/package.json +5 -5
package/dist/audio-decoder.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AudioOrVideoSample, AudioTrack, LogLevel } from '@remotion/media-parser';
|
|
2
2
|
import type { ProgressTracker } from './create/progress-tracker';
|
|
3
|
+
import type { WebCodecsController } from './webcodecs-controller';
|
|
3
4
|
export type WebCodecsAudioDecoder = {
|
|
4
5
|
processSample: (audioSample: AudioOrVideoSample) => Promise<void>;
|
|
5
6
|
waitForFinish: () => Promise<void>;
|
|
@@ -9,10 +10,10 @@ export type WebCodecsAudioDecoder = {
|
|
|
9
10
|
export type CreateAudioDecoderInit = {
|
|
10
11
|
onFrame: (frame: AudioData) => Promise<void>;
|
|
11
12
|
onError: (error: DOMException) => void;
|
|
12
|
-
|
|
13
|
+
controller: WebCodecsController;
|
|
13
14
|
config: AudioDecoderConfig;
|
|
14
15
|
logLevel: LogLevel;
|
|
15
16
|
track: AudioTrack;
|
|
16
17
|
progressTracker: ProgressTracker;
|
|
17
18
|
};
|
|
18
|
-
export declare const createAudioDecoder: ({ onFrame, onError,
|
|
19
|
+
export declare const createAudioDecoder: ({ onFrame, onError, controller, config, logLevel, track, progressTracker, }: CreateAudioDecoderInit) => WebCodecsAudioDecoder;
|
package/dist/audio-decoder.js
CHANGED
|
@@ -3,8 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.createAudioDecoder = void 0;
|
|
4
4
|
const get_wave_audio_decoder_1 = require("./get-wave-audio-decoder");
|
|
5
5
|
const io_synchronizer_1 = require("./io-manager/io-synchronizer");
|
|
6
|
-
const createAudioDecoder = ({ onFrame, onError,
|
|
7
|
-
if (signal.aborted) {
|
|
6
|
+
const createAudioDecoder = ({ onFrame, onError, controller, config, logLevel, track, progressTracker, }) => {
|
|
7
|
+
if (controller._internals.signal.aborted) {
|
|
8
8
|
throw new Error('Not creating audio decoder, already aborted');
|
|
9
9
|
}
|
|
10
10
|
if (config.codec === 'pcm-s16') {
|
|
@@ -23,17 +23,19 @@ const createAudioDecoder = ({ onFrame, onError, signal, config, logLevel, track,
|
|
|
23
23
|
const abortHandler = () => {
|
|
24
24
|
frame.close();
|
|
25
25
|
};
|
|
26
|
-
signal.addEventListener('abort', abortHandler, {
|
|
26
|
+
controller._internals.signal.addEventListener('abort', abortHandler, {
|
|
27
|
+
once: true,
|
|
28
|
+
});
|
|
27
29
|
outputQueue = outputQueue
|
|
28
30
|
.then(() => {
|
|
29
|
-
if (signal.aborted) {
|
|
31
|
+
if (controller._internals.signal.aborted) {
|
|
30
32
|
return;
|
|
31
33
|
}
|
|
32
34
|
return onFrame(frame);
|
|
33
35
|
})
|
|
34
36
|
.then(() => {
|
|
35
37
|
ioSynchronizer.onProcessed();
|
|
36
|
-
signal.removeEventListener('abort', abortHandler);
|
|
38
|
+
controller._internals.signal.removeEventListener('abort', abortHandler);
|
|
37
39
|
return Promise.resolve();
|
|
38
40
|
})
|
|
39
41
|
.catch((err) => {
|
|
@@ -47,7 +49,7 @@ const createAudioDecoder = ({ onFrame, onError, signal, config, logLevel, track,
|
|
|
47
49
|
});
|
|
48
50
|
const close = () => {
|
|
49
51
|
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
50
|
-
signal.removeEventListener('abort', onAbort);
|
|
52
|
+
controller._internals.signal.removeEventListener('abort', onAbort);
|
|
51
53
|
if (audioDecoder.state === 'closed') {
|
|
52
54
|
return;
|
|
53
55
|
}
|
|
@@ -56,7 +58,7 @@ const createAudioDecoder = ({ onFrame, onError, signal, config, logLevel, track,
|
|
|
56
58
|
const onAbort = () => {
|
|
57
59
|
close();
|
|
58
60
|
};
|
|
59
|
-
signal.addEventListener('abort', onAbort);
|
|
61
|
+
controller._internals.signal.addEventListener('abort', onAbort);
|
|
60
62
|
audioDecoder.configure(config);
|
|
61
63
|
const processSample = async (audioSample) => {
|
|
62
64
|
var _a, _b;
|
|
@@ -68,7 +70,7 @@ const createAudioDecoder = ({ onFrame, onError, signal, config, logLevel, track,
|
|
|
68
70
|
unemitted: 20,
|
|
69
71
|
unprocessed: 20,
|
|
70
72
|
minimumProgress: audioSample.timestamp - 10000000,
|
|
71
|
-
|
|
73
|
+
controller,
|
|
72
74
|
});
|
|
73
75
|
// Don't flush, it messes up the audio
|
|
74
76
|
const chunk = new EncodedAudioChunk(audioSample);
|
|
@@ -88,7 +90,7 @@ const createAudioDecoder = ({ onFrame, onError, signal, config, logLevel, track,
|
|
|
88
90
|
}
|
|
89
91
|
catch (_a) { }
|
|
90
92
|
await queue;
|
|
91
|
-
await ioSynchronizer.waitForFinish(
|
|
93
|
+
await ioSynchronizer.waitForFinish(controller);
|
|
92
94
|
await outputQueue;
|
|
93
95
|
},
|
|
94
96
|
close,
|
package/dist/audio-encoder.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type LogLevel } from '@remotion/media-parser';
|
|
2
2
|
import type { ProgressTracker } from './create/progress-tracker';
|
|
3
3
|
import type { ConvertMediaAudioCodec } from './get-available-audio-codecs';
|
|
4
|
+
import type { WebCodecsController } from './webcodecs-controller';
|
|
4
5
|
export type WebCodecsAudioEncoder = {
|
|
5
6
|
encodeFrame: (audioData: AudioData) => Promise<void>;
|
|
6
7
|
waitForFinish: () => Promise<void>;
|
|
@@ -11,10 +12,10 @@ export type AudioEncoderInit = {
|
|
|
11
12
|
onChunk: (chunk: EncodedAudioChunk) => Promise<void>;
|
|
12
13
|
onError: (error: DOMException) => void;
|
|
13
14
|
codec: ConvertMediaAudioCodec;
|
|
14
|
-
|
|
15
|
+
controller: WebCodecsController;
|
|
15
16
|
config: AudioEncoderConfig;
|
|
16
17
|
logLevel: LogLevel;
|
|
17
18
|
onNewAudioSampleRate: (sampleRate: number) => void;
|
|
18
19
|
progressTracker: ProgressTracker;
|
|
19
20
|
};
|
|
20
|
-
export declare const createAudioEncoder: ({ onChunk, onError, codec,
|
|
21
|
+
export declare const createAudioEncoder: ({ onChunk, onError, codec, controller, config: audioEncoderConfig, logLevel, onNewAudioSampleRate, progressTracker, }: AudioEncoderInit) => WebCodecsAudioEncoder;
|
package/dist/audio-encoder.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createAudioEncoder = void 0;
|
|
4
|
+
const media_parser_1 = require("@remotion/media-parser");
|
|
4
5
|
const io_synchronizer_1 = require("./io-manager/io-synchronizer");
|
|
5
6
|
const wav_audio_encoder_1 = require("./wav-audio-encoder");
|
|
6
|
-
const createAudioEncoder = ({ onChunk, onError, codec,
|
|
7
|
-
if (signal.aborted) {
|
|
8
|
-
throw new
|
|
7
|
+
const createAudioEncoder = ({ onChunk, onError, codec, controller, config: audioEncoderConfig, logLevel, onNewAudioSampleRate, progressTracker, }) => {
|
|
8
|
+
if (controller._internals.signal.aborted) {
|
|
9
|
+
throw new media_parser_1.MediaParserAbortError('Not creating audio encoder, already aborted');
|
|
9
10
|
}
|
|
10
11
|
if (codec === 'wav') {
|
|
11
|
-
return (0, wav_audio_encoder_1.getWaveAudioEncoder)({ onChunk,
|
|
12
|
+
return (0, wav_audio_encoder_1.getWaveAudioEncoder)({ onChunk, controller });
|
|
12
13
|
}
|
|
13
14
|
const ioSynchronizer = (0, io_synchronizer_1.makeIoSynchronizer)({
|
|
14
15
|
logLevel,
|
|
@@ -21,7 +22,7 @@ const createAudioEncoder = ({ onChunk, onError, codec, signal, config: audioEnco
|
|
|
21
22
|
ioSynchronizer.onOutput(chunk.timestamp);
|
|
22
23
|
prom = prom
|
|
23
24
|
.then(() => {
|
|
24
|
-
if (signal.aborted) {
|
|
25
|
+
if (controller._internals.signal.aborted) {
|
|
25
26
|
return;
|
|
26
27
|
}
|
|
27
28
|
return onChunk(chunk);
|
|
@@ -40,7 +41,7 @@ const createAudioEncoder = ({ onChunk, onError, codec, signal, config: audioEnco
|
|
|
40
41
|
});
|
|
41
42
|
const close = () => {
|
|
42
43
|
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
43
|
-
signal.removeEventListener('abort', onAbort);
|
|
44
|
+
controller._internals.signal.removeEventListener('abort', onAbort);
|
|
44
45
|
if (encoder.state === 'closed') {
|
|
45
46
|
return;
|
|
46
47
|
}
|
|
@@ -49,7 +50,7 @@ const createAudioEncoder = ({ onChunk, onError, codec, signal, config: audioEnco
|
|
|
49
50
|
const onAbort = () => {
|
|
50
51
|
close();
|
|
51
52
|
};
|
|
52
|
-
signal.addEventListener('abort', onAbort);
|
|
53
|
+
controller._internals.signal.addEventListener('abort', onAbort);
|
|
53
54
|
if (codec !== 'opus' && codec !== 'aac') {
|
|
54
55
|
throw new Error('Only `codec: "opus"` and `codec: "aac"` is supported currently');
|
|
55
56
|
}
|
|
@@ -63,7 +64,7 @@ const createAudioEncoder = ({ onChunk, onError, codec, signal, config: audioEnco
|
|
|
63
64
|
unemitted: 20,
|
|
64
65
|
unprocessed: 20,
|
|
65
66
|
minimumProgress: audioData.timestamp - 10000000,
|
|
66
|
-
|
|
67
|
+
controller,
|
|
67
68
|
});
|
|
68
69
|
// @ts-expect-error - can have changed in the meanwhile
|
|
69
70
|
if (encoder.state === 'closed') {
|
|
@@ -92,7 +93,7 @@ const createAudioEncoder = ({ onChunk, onError, codec, signal, config: audioEnco
|
|
|
92
93
|
},
|
|
93
94
|
waitForFinish: async () => {
|
|
94
95
|
await encoder.flush();
|
|
95
|
-
await ioSynchronizer.waitForFinish(
|
|
96
|
+
await ioSynchronizer.waitForFinish(controller);
|
|
96
97
|
await prom;
|
|
97
98
|
},
|
|
98
99
|
close,
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
+
import type { WriterInterface } from '@remotion/media-parser';
|
|
1
2
|
import type { LogLevel } from './log';
|
|
2
|
-
import type { WriterInterface } from './writers/writer';
|
|
3
3
|
export declare const autoSelectWriter: (writer: WriterInterface | undefined, logLevel: LogLevel) => Promise<WriterInterface>;
|
|
@@ -24,7 +24,8 @@ const canCopyVideoTrack = ({ outputContainer, rotationToApply, inputContainer, r
|
|
|
24
24
|
inputTrack.codecWithoutConfig === 'vp9');
|
|
25
25
|
}
|
|
26
26
|
if (outputContainer === 'mp4') {
|
|
27
|
-
return (inputTrack.codecWithoutConfig === 'h264'
|
|
27
|
+
return ((inputTrack.codecWithoutConfig === 'h264' ||
|
|
28
|
+
inputTrack.codecWithoutConfig === 'h265') &&
|
|
28
29
|
(inputContainer === 'mp4' || inputContainer === 'avi'));
|
|
29
30
|
}
|
|
30
31
|
if (outputContainer === 'wav') {
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.chooseCorrectHevcProfile = void 0;
|
|
4
|
+
const hevc_levels_1 = require("./hevc-levels");
|
|
5
|
+
const chooseCorrectHevcProfile = ({ width, height, fps, }) => {
|
|
6
|
+
const profile = hevc_levels_1.hevcLevels.find((p) => {
|
|
7
|
+
return p.maxResolutionsAndFrameRates.some((max) => {
|
|
8
|
+
if (width > max.width) {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
if (height > max.height) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
// if has no fps, use 60 as a conservative fallback
|
|
15
|
+
const fallbackFps = fps !== null && fps !== void 0 ? fps : 60;
|
|
16
|
+
return fallbackFps <= max.fps;
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
if (!profile) {
|
|
20
|
+
throw new Error(`No suitable HEVC profile found for ${width}x${height}@${fps}fps`);
|
|
21
|
+
}
|
|
22
|
+
// HEVC codec string format: hev1.2.${level_hex} or hvc1.2.${level_hex}
|
|
23
|
+
// We'll use hvc1 as it's more widely supported
|
|
24
|
+
return `hvc1.${
|
|
25
|
+
// Profile
|
|
26
|
+
// 1 = Main
|
|
27
|
+
// 2 = Main 10
|
|
28
|
+
// Chrome seems to support only Main
|
|
29
|
+
1}.${
|
|
30
|
+
// Profile space
|
|
31
|
+
// Unclear which value to set, but 0 works
|
|
32
|
+
0}.${
|
|
33
|
+
// L = Main tier
|
|
34
|
+
// H = High tier
|
|
35
|
+
// TODO: Select high tier if resolution is big
|
|
36
|
+
'L'}${
|
|
37
|
+
// Level
|
|
38
|
+
Math.round(Number(profile.level) * 30)}.${
|
|
39
|
+
// Bit depth
|
|
40
|
+
'b0'}`;
|
|
41
|
+
};
|
|
42
|
+
exports.chooseCorrectHevcProfile = chooseCorrectHevcProfile;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ParseMediaController } from '@remotion/media-parser';
|
|
2
|
+
export type WebCodecsController = {
|
|
3
|
+
abort: (reason?: any) => void;
|
|
4
|
+
pause: ParseMediaController['pause'];
|
|
5
|
+
resume: ParseMediaController['resume'];
|
|
6
|
+
addEventListener: ParseMediaController['addEventListener'];
|
|
7
|
+
removeEventListener: ParseMediaController['removeEventListener'];
|
|
8
|
+
/**
|
|
9
|
+
* @deprecated Not public API
|
|
10
|
+
*/
|
|
11
|
+
_internals: {
|
|
12
|
+
signal: AbortSignal;
|
|
13
|
+
checkForAbortAndPause: ParseMediaController['_internals']['checkForAbortAndPause'];
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
export declare const webcodecsController: () => WebCodecsController;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.webcodecsController = void 0;
|
|
4
|
+
const media_parser_1 = require("@remotion/media-parser");
|
|
5
|
+
const webcodecsController = () => {
|
|
6
|
+
const controller = (0, media_parser_1.mediaParserController)();
|
|
7
|
+
return {
|
|
8
|
+
abort: controller.abort,
|
|
9
|
+
pause: controller.pause,
|
|
10
|
+
resume: controller.resume,
|
|
11
|
+
addEventListener: controller.addEventListener,
|
|
12
|
+
removeEventListener: controller.removeEventListener,
|
|
13
|
+
_internals: controller._internals,
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
exports.webcodecsController = webcodecsController;
|
package/dist/convert-media.d.ts
CHANGED
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
* Copyright (c) 2025 Remotion AG
|
|
3
3
|
* For licensing, see: https://remotion.dev/docs/webcodecs#license
|
|
4
4
|
*/
|
|
5
|
-
import type { AudioTrack, LogLevel, Options, ParseMediaFields, ParseMediaOptions, VideoTrack } from '@remotion/media-parser';
|
|
5
|
+
import type { AudioTrack, LogLevel, Options, ParseMediaFields, ParseMediaOptions, VideoTrack, WriterInterface } from '@remotion/media-parser';
|
|
6
6
|
import type { ParseMediaCallbacks } from '@remotion/media-parser';
|
|
7
7
|
import type { ConvertMediaAudioCodec } from './get-available-audio-codecs';
|
|
8
|
-
import type
|
|
9
|
-
import type
|
|
8
|
+
import { type ConvertMediaContainer } from './get-available-containers';
|
|
9
|
+
import { type ConvertMediaVideoCodec } from './get-available-video-codecs';
|
|
10
10
|
import { type ConvertMediaOnAudioTrackHandler } from './on-audio-track-handler';
|
|
11
11
|
import { type ConvertMediaOnVideoTrackHandler } from './on-video-track-handler';
|
|
12
12
|
import type { ResizeOperation } from './resizing/mode';
|
|
13
|
-
import type
|
|
13
|
+
import { type WebCodecsController } from './webcodecs-controller';
|
|
14
14
|
export type ConvertMediaProgress = {
|
|
15
15
|
decodedVideoFrames: number;
|
|
16
16
|
decodedAudioFrames: number;
|
|
@@ -35,7 +35,7 @@ export type ConvertMediaOnAudioData = (options: {
|
|
|
35
35
|
audioData: AudioData;
|
|
36
36
|
track: AudioTrack;
|
|
37
37
|
}) => Promise<AudioData> | AudioData;
|
|
38
|
-
export declare const convertMedia: <F extends Options<ParseMediaFields>>({ src, onVideoFrame, onAudioData, onProgress: onProgressDoNotCallDirectly, audioCodec, container, videoCodec,
|
|
38
|
+
export declare const convertMedia: <F extends Options<ParseMediaFields>>({ src, onVideoFrame, onAudioData, onProgress: onProgressDoNotCallDirectly, audioCodec, container, videoCodec, controller, onAudioTrack: userAudioResolver, onVideoTrack: userVideoResolver, reader, fields, logLevel, writer, progressIntervalInMs, rotate, apiKey, resize, onAudioCodec, onContainer, onDimensions, onDurationInSeconds, onFps, onImages, onInternalStats, onIsHdr, onKeyframes, onLocation, onMetadata, onMimeType, onName, onNumberOfAudioChannels, onRotation, onSampleRate, onSize, onSlowAudioBitrate, onSlowDurationInSeconds, onSlowFps, onSlowKeyframes, onSlowNumberOfFrames, onSlowVideoBitrate, onStructure, onTracks, onUnrotatedDimensions, onVideoCodec, ...more }: {
|
|
39
39
|
src: ParseMediaOptions<F>["src"];
|
|
40
40
|
container: ConvertMediaContainer;
|
|
41
41
|
onVideoFrame?: ConvertMediaOnVideoFrame;
|
|
@@ -43,7 +43,7 @@ export declare const convertMedia: <F extends Options<ParseMediaFields>>({ src,
|
|
|
43
43
|
onProgress?: ConvertMediaOnProgress;
|
|
44
44
|
videoCodec?: ConvertMediaVideoCodec;
|
|
45
45
|
audioCodec?: ConvertMediaAudioCodec;
|
|
46
|
-
|
|
46
|
+
controller?: WebCodecsController;
|
|
47
47
|
onAudioTrack?: ConvertMediaOnAudioTrackHandler;
|
|
48
48
|
onVideoTrack?: ConvertMediaOnVideoTrackHandler;
|
|
49
49
|
reader?: ParseMediaOptions<F>["reader"];
|
package/dist/convert-media.js
CHANGED
|
@@ -3,52 +3,51 @@
|
|
|
3
3
|
* Copyright (c) 2025 Remotion AG
|
|
4
4
|
* For licensing, see: https://remotion.dev/docs/webcodecs#license
|
|
5
5
|
*/
|
|
6
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
7
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
8
|
-
};
|
|
9
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
7
|
exports.convertMedia = void 0;
|
|
11
8
|
const media_parser_1 = require("@remotion/media-parser");
|
|
9
|
+
const fetch_1 = require("@remotion/media-parser/fetch");
|
|
12
10
|
const auto_select_writer_1 = require("./auto-select-writer");
|
|
13
11
|
const calculate_progress_1 = require("./calculate-progress");
|
|
14
12
|
const progress_tracker_1 = require("./create/progress-tracker");
|
|
15
13
|
const with_resolvers_1 = require("./create/with-resolvers");
|
|
16
|
-
const error_cause_1 = __importDefault(require("./error-cause"));
|
|
17
14
|
const generate_output_filename_1 = require("./generate-output-filename");
|
|
15
|
+
const get_available_containers_1 = require("./get-available-containers");
|
|
16
|
+
const get_available_video_codecs_1 = require("./get-available-video-codecs");
|
|
18
17
|
const log_1 = require("./log");
|
|
19
18
|
const on_audio_track_1 = require("./on-audio-track");
|
|
20
19
|
const on_video_track_1 = require("./on-video-track");
|
|
21
20
|
const select_container_creator_1 = require("./select-container-creator");
|
|
22
21
|
const send_telemetry_event_1 = require("./send-telemetry-event");
|
|
23
22
|
const throttled_state_update_1 = require("./throttled-state-update");
|
|
24
|
-
const
|
|
23
|
+
const webcodecs_controller_1 = require("./webcodecs-controller");
|
|
24
|
+
const convertMedia = async function ({ src, onVideoFrame, onAudioData, onProgress: onProgressDoNotCallDirectly, audioCodec, container, videoCodec, controller = (0, webcodecs_controller_1.webcodecsController)(), onAudioTrack: userAudioResolver, onVideoTrack: userVideoResolver, reader, fields, logLevel = 'info', writer, progressIntervalInMs, rotate, apiKey, resize, onAudioCodec, onContainer, onDimensions, onDurationInSeconds, onFps, onImages, onInternalStats, onIsHdr, onKeyframes, onLocation, onMetadata, onMimeType, onName, onNumberOfAudioChannels, onRotation, onSampleRate, onSize, onSlowAudioBitrate, onSlowDurationInSeconds, onSlowFps, onSlowKeyframes, onSlowNumberOfFrames, onSlowVideoBitrate, onStructure, onTracks, onUnrotatedDimensions, onVideoCodec, ...more }) {
|
|
25
25
|
var _a, _b;
|
|
26
|
-
if (
|
|
27
|
-
return Promise.reject(new
|
|
26
|
+
if (controller._internals.signal.aborted) {
|
|
27
|
+
return Promise.reject(new media_parser_1.MediaParserAbortError('Aborted'));
|
|
28
28
|
}
|
|
29
|
-
if (container
|
|
30
|
-
return Promise.reject(new TypeError(
|
|
29
|
+
if (get_available_containers_1.availableContainers.indexOf(container) === -1) {
|
|
30
|
+
return Promise.reject(new TypeError(`Only the following values for "container" are supported currently: ${JSON.stringify(get_available_containers_1.availableContainers)}`));
|
|
31
31
|
}
|
|
32
|
-
if (videoCodec && videoCodec
|
|
33
|
-
return Promise.reject(new TypeError(
|
|
32
|
+
if (videoCodec && get_available_video_codecs_1.availableVideoCodecs.indexOf(videoCodec) === -1) {
|
|
33
|
+
return Promise.reject(new TypeError(`Only the following values for "videoCodec" are supported currently: ${JSON.stringify(get_available_video_codecs_1.availableVideoCodecs)}`));
|
|
34
34
|
}
|
|
35
35
|
const { resolve, reject, getPromiseToImmediatelyReturn } = (0, with_resolvers_1.withResolversAndWaitForReturn)();
|
|
36
|
-
const controller = new AbortController();
|
|
37
36
|
const abortConversion = (errCause) => {
|
|
38
37
|
reject(errCause);
|
|
39
|
-
if (!controller.signal.aborted) {
|
|
38
|
+
if (!controller._internals.signal.aborted) {
|
|
40
39
|
controller.abort();
|
|
41
40
|
}
|
|
42
41
|
};
|
|
43
42
|
const onUserAbort = () => {
|
|
44
|
-
abortConversion(new
|
|
43
|
+
abortConversion(new media_parser_1.MediaParserAbortError('Conversion aborted by user'));
|
|
45
44
|
};
|
|
46
|
-
|
|
45
|
+
controller._internals.signal.addEventListener('abort', onUserAbort);
|
|
47
46
|
const creator = (0, select_container_creator_1.selectContainerCreator)(container);
|
|
48
47
|
const throttledState = (0, throttled_state_update_1.throttledStateUpdate)({
|
|
49
48
|
updateFn: onProgressDoNotCallDirectly !== null && onProgressDoNotCallDirectly !== void 0 ? onProgressDoNotCallDirectly : null,
|
|
50
49
|
everyMilliseconds: progressIntervalInMs !== null && progressIntervalInMs !== void 0 ? progressIntervalInMs : 100,
|
|
51
|
-
signal: controller.signal,
|
|
50
|
+
signal: controller._internals.signal,
|
|
52
51
|
});
|
|
53
52
|
const progressTracker = (0, progress_tracker_1.makeProgressTracker)();
|
|
54
53
|
const state = await creator({
|
|
@@ -108,17 +107,17 @@ const convertMedia = async function ({ src, onVideoFrame, onAudioData, onProgres
|
|
|
108
107
|
progressTracker,
|
|
109
108
|
onAudioData: onAudioData !== null && onAudioData !== void 0 ? onAudioData : null,
|
|
110
109
|
});
|
|
111
|
-
|
|
110
|
+
media_parser_1.MediaParserInternals.internalParseMedia({
|
|
112
111
|
logLevel,
|
|
113
112
|
src,
|
|
114
113
|
onVideoTrack,
|
|
115
114
|
onAudioTrack,
|
|
116
|
-
|
|
115
|
+
controller,
|
|
117
116
|
fields: {
|
|
118
117
|
...fields,
|
|
119
118
|
durationInSeconds: true,
|
|
120
119
|
},
|
|
121
|
-
reader,
|
|
120
|
+
reader: reader !== null && reader !== void 0 ? reader : fetch_1.fetchReader,
|
|
122
121
|
...more,
|
|
123
122
|
onDurationInSeconds: (durationInSeconds) => {
|
|
124
123
|
var _a;
|
|
@@ -141,13 +140,46 @@ const convertMedia = async function ({ src, onVideoFrame, onAudioData, onProgres
|
|
|
141
140
|
};
|
|
142
141
|
});
|
|
143
142
|
},
|
|
143
|
+
acknowledgeRemotionLicense: true,
|
|
144
|
+
mode: 'query',
|
|
145
|
+
onDiscardedData: null,
|
|
146
|
+
onError: () => ({ action: 'fail' }),
|
|
147
|
+
onParseProgress: null,
|
|
148
|
+
progressIntervalInMs: null,
|
|
149
|
+
onAudioCodec: onAudioCodec !== null && onAudioCodec !== void 0 ? onAudioCodec : null,
|
|
150
|
+
onContainer: onContainer !== null && onContainer !== void 0 ? onContainer : null,
|
|
151
|
+
onDimensions: onDimensions !== null && onDimensions !== void 0 ? onDimensions : null,
|
|
152
|
+
onFps: onFps !== null && onFps !== void 0 ? onFps : null,
|
|
153
|
+
onImages: onImages !== null && onImages !== void 0 ? onImages : null,
|
|
154
|
+
onInternalStats: onInternalStats !== null && onInternalStats !== void 0 ? onInternalStats : null,
|
|
155
|
+
onIsHdr: onIsHdr !== null && onIsHdr !== void 0 ? onIsHdr : null,
|
|
156
|
+
onKeyframes: onKeyframes !== null && onKeyframes !== void 0 ? onKeyframes : null,
|
|
157
|
+
onLocation: onLocation !== null && onLocation !== void 0 ? onLocation : null,
|
|
158
|
+
onMetadata: onMetadata !== null && onMetadata !== void 0 ? onMetadata : null,
|
|
159
|
+
onMimeType: onMimeType !== null && onMimeType !== void 0 ? onMimeType : null,
|
|
160
|
+
onName: onName !== null && onName !== void 0 ? onName : null,
|
|
161
|
+
onNumberOfAudioChannels: onNumberOfAudioChannels !== null && onNumberOfAudioChannels !== void 0 ? onNumberOfAudioChannels : null,
|
|
162
|
+
onRotation: onRotation !== null && onRotation !== void 0 ? onRotation : null,
|
|
163
|
+
onSampleRate: onSampleRate !== null && onSampleRate !== void 0 ? onSampleRate : null,
|
|
164
|
+
onSize: onSize !== null && onSize !== void 0 ? onSize : null,
|
|
165
|
+
onSlowAudioBitrate: onSlowAudioBitrate !== null && onSlowAudioBitrate !== void 0 ? onSlowAudioBitrate : null,
|
|
166
|
+
onSlowDurationInSeconds: onSlowDurationInSeconds !== null && onSlowDurationInSeconds !== void 0 ? onSlowDurationInSeconds : null,
|
|
167
|
+
onSlowFps: onSlowFps !== null && onSlowFps !== void 0 ? onSlowFps : null,
|
|
168
|
+
onSlowKeyframes: onSlowKeyframes !== null && onSlowKeyframes !== void 0 ? onSlowKeyframes : null,
|
|
169
|
+
onSlowNumberOfFrames: onSlowNumberOfFrames !== null && onSlowNumberOfFrames !== void 0 ? onSlowNumberOfFrames : null,
|
|
170
|
+
onSlowVideoBitrate: onSlowVideoBitrate !== null && onSlowVideoBitrate !== void 0 ? onSlowVideoBitrate : null,
|
|
171
|
+
onStructure: onStructure !== null && onStructure !== void 0 ? onStructure : null,
|
|
172
|
+
onTracks: onTracks !== null && onTracks !== void 0 ? onTracks : null,
|
|
173
|
+
onUnrotatedDimensions: onUnrotatedDimensions !== null && onUnrotatedDimensions !== void 0 ? onUnrotatedDimensions : null,
|
|
174
|
+
onVideoCodec: onVideoCodec !== null && onVideoCodec !== void 0 ? onVideoCodec : null,
|
|
175
|
+
apiName: 'convertMedia()',
|
|
144
176
|
})
|
|
145
177
|
.then(() => {
|
|
146
178
|
return state.waitForFinish();
|
|
147
179
|
})
|
|
148
180
|
.then(() => {
|
|
149
181
|
resolve({
|
|
150
|
-
save: state.
|
|
182
|
+
save: state.getBlob,
|
|
151
183
|
remove: state.remove,
|
|
152
184
|
finalState: throttledState.get(),
|
|
153
185
|
});
|
|
@@ -167,7 +199,7 @@ const convertMedia = async function ({ src, onVideoFrame, onAudioData, onProgres
|
|
|
167
199
|
throttledState.stopAndGetLastProgress();
|
|
168
200
|
});
|
|
169
201
|
return getPromiseToImmediatelyReturn().finally(() => {
|
|
170
|
-
|
|
202
|
+
controller._internals.signal.removeEventListener('abort', onUserAbort);
|
|
171
203
|
});
|
|
172
204
|
};
|
|
173
205
|
exports.convertMedia = convertMedia;
|
|
@@ -10,6 +10,17 @@ export type Avc1Data = {
|
|
|
10
10
|
depth: number;
|
|
11
11
|
type: 'avc1-data';
|
|
12
12
|
};
|
|
13
|
+
export type Hvc1Data = {
|
|
14
|
+
pasp: Uint8Array;
|
|
15
|
+
hvccBox: Uint8Array;
|
|
16
|
+
width: number;
|
|
17
|
+
height: number;
|
|
18
|
+
horizontalResolution: number;
|
|
19
|
+
verticalResolution: number;
|
|
20
|
+
compressorName: string;
|
|
21
|
+
depth: number;
|
|
22
|
+
type: 'hvc1-data';
|
|
23
|
+
};
|
|
13
24
|
export type Mp4aData = {
|
|
14
25
|
type: 'mp4a-data';
|
|
15
26
|
sampleRate: number;
|
|
@@ -2,22 +2,40 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createCodecSpecificData = void 0;
|
|
4
4
|
const create_avcc_1 = require("../trak/mdia/minf/stbl/stsd/create-avcc");
|
|
5
|
+
const create_hvcc_1 = require("../trak/mdia/minf/stbl/stsd/create-hvcc");
|
|
5
6
|
const create_pasp_1 = require("../trak/mdia/minf/stbl/stsd/create-pasp");
|
|
6
7
|
const avc1_1 = require("./avc1");
|
|
8
|
+
const hvc1_1 = require("./hvc1");
|
|
7
9
|
const mp4a_1 = require("./mp4a");
|
|
8
10
|
const createCodecSpecificData = (track) => {
|
|
9
11
|
if (track.type === 'video') {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
12
|
+
if (track.codec === 'h264') {
|
|
13
|
+
return (0, avc1_1.createAvc1Data)({
|
|
14
|
+
avccBox: (0, create_avcc_1.createAvccBox)(track.codecPrivate),
|
|
15
|
+
compressorName: 'WebCodecs',
|
|
16
|
+
depth: 24,
|
|
17
|
+
horizontalResolution: 72,
|
|
18
|
+
verticalResolution: 72,
|
|
19
|
+
height: track.height,
|
|
20
|
+
width: track.width,
|
|
21
|
+
pasp: (0, create_pasp_1.createPasp)(1, 1),
|
|
22
|
+
type: 'avc1-data',
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
if (track.codec === 'h265') {
|
|
26
|
+
return (0, hvc1_1.createHvc1Data)({
|
|
27
|
+
hvccBox: (0, create_hvcc_1.createHvccBox)(track.codecPrivate),
|
|
28
|
+
compressorName: 'WebCodecs',
|
|
29
|
+
depth: 24,
|
|
30
|
+
horizontalResolution: 72,
|
|
31
|
+
verticalResolution: 72,
|
|
32
|
+
height: track.height,
|
|
33
|
+
width: track.width,
|
|
34
|
+
pasp: (0, create_pasp_1.createPasp)(1, 1),
|
|
35
|
+
type: 'hvc1-data',
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
throw new Error('Unsupported codec specific data ' + track.codec);
|
|
21
39
|
}
|
|
22
40
|
if (track.type === 'audio') {
|
|
23
41
|
return (0, mp4a_1.createMp4a)({
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createHvc1Data = void 0;
|
|
4
|
+
const matroska_utils_1 = require("../../matroska/matroska-utils");
|
|
5
|
+
const primitives_1 = require("../primitives");
|
|
6
|
+
const createHvc1Data = ({ compressorName, depth, height, horizontalResolution, hvccBox, pasp, verticalResolution, width, }) => {
|
|
7
|
+
return (0, primitives_1.addSize)((0, matroska_utils_1.combineUint8Arrays)([
|
|
8
|
+
// type
|
|
9
|
+
(0, primitives_1.stringsToUint8Array)('hvc1'),
|
|
10
|
+
// reserved
|
|
11
|
+
new Uint8Array([0, 0, 0, 0, 0, 0]),
|
|
12
|
+
// data_reference_index
|
|
13
|
+
new Uint8Array([0, 1]),
|
|
14
|
+
// version
|
|
15
|
+
new Uint8Array([0, 0]),
|
|
16
|
+
// revisionLevel
|
|
17
|
+
new Uint8Array([0, 0]),
|
|
18
|
+
// vendor
|
|
19
|
+
new Uint8Array([0, 0, 0, 0]),
|
|
20
|
+
// temporalQuality
|
|
21
|
+
new Uint8Array([0, 0, 0, 0]),
|
|
22
|
+
// spatialQuality
|
|
23
|
+
new Uint8Array([0, 0, 0, 0]),
|
|
24
|
+
// width
|
|
25
|
+
(0, primitives_1.numberTo16BitUIntOrInt)(width),
|
|
26
|
+
// height
|
|
27
|
+
(0, primitives_1.numberTo16BitUIntOrInt)(height),
|
|
28
|
+
// horizontalResolution
|
|
29
|
+
(0, primitives_1.setFixedPointSignedOrUnsigned1616Number)(horizontalResolution),
|
|
30
|
+
// verticalResolution
|
|
31
|
+
(0, primitives_1.setFixedPointSignedOrUnsigned1616Number)(verticalResolution),
|
|
32
|
+
// dataSize
|
|
33
|
+
new Uint8Array([0, 0, 0, 0]),
|
|
34
|
+
// frame count per sample
|
|
35
|
+
(0, primitives_1.numberTo16BitUIntOrInt)(1),
|
|
36
|
+
// compressor name
|
|
37
|
+
(0, primitives_1.stringToPascalString)(compressorName),
|
|
38
|
+
// depth
|
|
39
|
+
(0, primitives_1.numberTo16BitUIntOrInt)(depth),
|
|
40
|
+
// colorTableId
|
|
41
|
+
(0, primitives_1.numberTo16BitUIntOrInt)(-1),
|
|
42
|
+
// hvcc box
|
|
43
|
+
hvccBox,
|
|
44
|
+
// pasp
|
|
45
|
+
pasp,
|
|
46
|
+
]));
|
|
47
|
+
};
|
|
48
|
+
exports.createHvc1Data = createHvc1Data;
|
|
@@ -13,7 +13,11 @@ const createIsoBaseMedia = async ({ writer, onBytesProgress, onMillisecondsProgr
|
|
|
13
13
|
majorBrand: 'isom',
|
|
14
14
|
minorBrand: 512,
|
|
15
15
|
});
|
|
16
|
-
const w = await writer.createContent({
|
|
16
|
+
const w = await writer.createContent({
|
|
17
|
+
filename,
|
|
18
|
+
mimeType: 'video/mp4',
|
|
19
|
+
logLevel,
|
|
20
|
+
});
|
|
17
21
|
await w.write(header);
|
|
18
22
|
let globalDurationInUnits = 0;
|
|
19
23
|
const lowestTrackTimestamps = {};
|
|
@@ -135,8 +139,8 @@ const createIsoBaseMedia = async ({ writer, onBytesProgress, onMillisecondsProgr
|
|
|
135
139
|
};
|
|
136
140
|
const waitForFinishPromises = [];
|
|
137
141
|
return {
|
|
138
|
-
|
|
139
|
-
return w.
|
|
142
|
+
getBlob: () => {
|
|
143
|
+
return w.getBlob();
|
|
140
144
|
},
|
|
141
145
|
remove: async () => {
|
|
142
146
|
await w.remove();
|
|
@@ -177,7 +181,7 @@ const createIsoBaseMedia = async ({ writer, onBytesProgress, onMillisecondsProgr
|
|
|
177
181
|
await updateMoov();
|
|
178
182
|
await updateMdatSize();
|
|
179
183
|
media_parser_1.MediaParserInternals.Log.verbose(logLevel, 'All write operations done. Waiting for finish...');
|
|
180
|
-
await w.
|
|
184
|
+
await w.finish();
|
|
181
185
|
},
|
|
182
186
|
};
|
|
183
187
|
};
|
|
@@ -13,7 +13,9 @@ const create_stbl_1 = require("./trak/mdia/minf/create-stbl");
|
|
|
13
13
|
const create_vmhd_1 = require("./trak/mdia/minf/create-vmhd");
|
|
14
14
|
const create_hdlr_1 = require("./udta/meta/create-hdlr");
|
|
15
15
|
const serializeTrack = ({ track, durationInUnits, samplePositions, timescale, }) => {
|
|
16
|
-
if (track.codec !== 'h264' &&
|
|
16
|
+
if (track.codec !== 'h264' &&
|
|
17
|
+
track.codec !== 'h265' &&
|
|
18
|
+
track.codec !== 'aac') {
|
|
17
19
|
throw new Error('Currently only H.264 and AAC is supported');
|
|
18
20
|
}
|
|
19
21
|
return (0, create_trak_1.createTrak)({
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const createHvccBox: (privateData: Uint8Array | null) => Uint8Array;
|