@remotion/webcodecs 4.0.304 → 4.0.306
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 +15 -10
- package/dist/audio-decoder.js +49 -52
- package/dist/audio-encoder.d.ts +5 -5
- package/dist/audio-encoder.js +20 -42
- package/dist/convert-encoded-chunk.d.ts +1 -1
- package/dist/convert-encoded-chunk.js +2 -5
- package/dist/convert-media.js +2 -2
- package/dist/copy-audio-track.d.ts +11 -0
- package/dist/copy-audio-track.js +31 -0
- package/dist/copy-video-track.d.ts +11 -0
- package/dist/copy-video-track.js +32 -0
- package/dist/create/event-emitter.d.ts +0 -1
- package/dist/create/iso-base-media/create-iso-base-media.js +3 -3
- package/dist/create/iso-base-media/example-stts.js +620 -620
- package/dist/create/iso-base-media/trak/mdia/minf/create-stbl.js +3 -1
- package/dist/create/iso-base-media/trak/mdia/minf/stbl/create-ctts.js +1 -1
- package/dist/create/iso-base-media/trak/mdia/minf/stbl/create-stts.js +3 -2
- package/dist/create/matroska/create-matroska-media.js +1 -1
- package/dist/create/progress-tracker.d.ts +0 -2
- package/dist/create/progress-tracker.js +3 -20
- package/dist/esm/index.mjs +583 -496
- package/dist/get-wave-audio-decoder.d.ts +6 -1
- package/dist/get-wave-audio-decoder.js +16 -11
- package/dist/io-manager/io-synchronizer.d.ts +6 -13
- package/dist/io-manager/io-synchronizer.js +31 -72
- package/dist/io-manager/make-timeout-promise.d.ts +1 -1
- package/dist/io-manager/make-timeout-promise.js +8 -4
- package/dist/on-audio-track.d.ts +2 -2
- package/dist/on-audio-track.js +15 -150
- package/dist/on-frame.d.ts +2 -4
- package/dist/on-frame.js +8 -9
- package/dist/on-video-track.d.ts +2 -2
- package/dist/on-video-track.js +18 -129
- package/dist/processing-queue.d.ts +19 -0
- package/dist/processing-queue.js +47 -0
- package/dist/reencode-audio-track.d.ts +18 -0
- package/dist/reencode-audio-track.js +164 -0
- package/dist/reencode-video-track.d.ts +19 -0
- package/dist/reencode-video-track.js +151 -0
- package/dist/sort-video-frames.d.ts +4 -3
- package/dist/sort-video-frames.js +7 -3
- package/dist/video-decoder.d.ts +14 -8
- package/dist/video-decoder.js +37 -72
- package/dist/video-encoder.d.ts +6 -5
- package/dist/video-encoder.js +16 -40
- package/dist/wav-audio-encoder.d.ts +4 -1
- package/dist/wav-audio-encoder.js +3 -2
- package/package.json +5 -5
package/dist/on-video-track.js
CHANGED
|
@@ -1,19 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.makeVideoTrackHandler = void 0;
|
|
4
|
-
const arraybuffer_to_uint8_array_1 = require("./arraybuffer-to-uint8-array");
|
|
5
4
|
const can_copy_video_track_1 = require("./can-copy-video-track");
|
|
6
|
-
const
|
|
5
|
+
const copy_video_track_1 = require("./copy-video-track");
|
|
7
6
|
const default_on_video_track_handler_1 = require("./default-on-video-track-handler");
|
|
8
7
|
const get_default_video_codec_1 = require("./get-default-video-codec");
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const rotation_1 = require("./rotation");
|
|
12
|
-
const video_decoder_1 = require("./video-decoder");
|
|
13
|
-
const video_decoder_config_1 = require("./video-decoder-config");
|
|
14
|
-
const video_encoder_1 = require("./video-encoder");
|
|
15
|
-
const video_encoder_config_1 = require("./video-encoder-config");
|
|
16
|
-
const makeVideoTrackHandler = ({ state, onVideoFrame, onMediaStateUpdate, abortConversion, controller, defaultVideoCodec, onVideoTrack, logLevel, outputContainer, rotate, progress, resizeOperation, }) => async ({ track, container: inputContainer }) => {
|
|
8
|
+
const reencode_video_track_1 = require("./reencode-video-track");
|
|
9
|
+
const makeVideoTrackHandler = ({ state, onVideoFrame, onMediaStateUpdate, abortConversion, controller, defaultVideoCodec, onVideoTrack, logLevel, outputContainer, rotate, resizeOperation, progressTracker, }) => async ({ track, container: inputContainer }) => {
|
|
17
10
|
if (controller._internals._mediaParserController._internals.signal.aborted) {
|
|
18
11
|
throw new Error('Aborted');
|
|
19
12
|
}
|
|
@@ -41,129 +34,25 @@ const makeVideoTrackHandler = ({ state, onVideoFrame, onMediaStateUpdate, abortC
|
|
|
41
34
|
throw new Error(`Video track with ID ${track.trackId} resolved 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`);
|
|
42
35
|
}
|
|
43
36
|
if (videoOperation.type === 'copy') {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
codec: track.codecEnum,
|
|
51
|
-
codecPrivate: track.codecData?.data ?? null,
|
|
52
|
-
timescale: track.timescale,
|
|
37
|
+
return (0, copy_video_track_1.copyVideoTrack)({
|
|
38
|
+
logLevel,
|
|
39
|
+
onMediaStateUpdate,
|
|
40
|
+
state,
|
|
41
|
+
track,
|
|
42
|
+
progressTracker,
|
|
53
43
|
});
|
|
54
|
-
return async (sample) => {
|
|
55
|
-
await state.addSample({
|
|
56
|
-
chunk: sample,
|
|
57
|
-
trackNumber: videoTrack.trackNumber,
|
|
58
|
-
isVideo: true,
|
|
59
|
-
codecPrivate: track.codecData?.data ?? null,
|
|
60
|
-
});
|
|
61
|
-
onMediaStateUpdate?.((prevState) => {
|
|
62
|
-
return {
|
|
63
|
-
...prevState,
|
|
64
|
-
decodedVideoFrames: prevState.decodedVideoFrames + 1,
|
|
65
|
-
};
|
|
66
|
-
});
|
|
67
|
-
};
|
|
68
44
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const rotation = (videoOperation.rotate ?? rotate) - track.rotation;
|
|
73
|
-
const { height: newHeight, width: newWidth } = (0, rotation_1.calculateNewDimensionsFromRotateAndScale)({
|
|
74
|
-
width: track.codedWidth,
|
|
75
|
-
height: track.codedHeight,
|
|
76
|
-
rotation,
|
|
77
|
-
videoCodec: videoOperation.videoCodec,
|
|
78
|
-
resizeOperation: videoOperation.resize ?? null,
|
|
79
|
-
});
|
|
80
|
-
const videoEncoderConfig = await (0, video_encoder_config_1.getVideoEncoderConfig)({
|
|
81
|
-
codec: videoOperation.videoCodec,
|
|
82
|
-
height: newHeight,
|
|
83
|
-
width: newWidth,
|
|
84
|
-
fps: track.fps,
|
|
85
|
-
});
|
|
86
|
-
const videoDecoderConfig = await (0, video_decoder_config_1.getVideoDecoderConfigWithHardwareAcceleration)(track);
|
|
87
|
-
log_1.Log.verbose(logLevel, 'Video encoder config', videoEncoderConfig);
|
|
88
|
-
log_1.Log.verbose(logLevel, 'Video decoder config', videoDecoderConfig ?? track);
|
|
89
|
-
if (videoEncoderConfig === null) {
|
|
90
|
-
abortConversion(new Error(`Could not configure video encoder of track ${track.trackId}`));
|
|
91
|
-
return null;
|
|
92
|
-
}
|
|
93
|
-
if (videoDecoderConfig === null) {
|
|
94
|
-
abortConversion(new Error(`Could not configure video decoder of track ${track.trackId}`));
|
|
95
|
-
return null;
|
|
96
|
-
}
|
|
97
|
-
const { trackNumber } = await state.addTrack({
|
|
98
|
-
type: 'video',
|
|
99
|
-
color: track.advancedColor,
|
|
100
|
-
width: newWidth,
|
|
101
|
-
height: newHeight,
|
|
102
|
-
codec: videoOperation.videoCodec,
|
|
103
|
-
codecPrivate: null,
|
|
104
|
-
timescale: track.timescale,
|
|
105
|
-
});
|
|
106
|
-
log_1.Log.verbose(logLevel, `Created new video track with ID ${trackNumber}, codec ${videoOperation.videoCodec} and timescale ${track.timescale}`);
|
|
107
|
-
const videoEncoder = (0, video_encoder_1.createVideoEncoder)({
|
|
108
|
-
onChunk: async (chunk, metadata) => {
|
|
109
|
-
await state.addSample({
|
|
110
|
-
chunk: (0, convert_encoded_chunk_1.convertEncodedChunk)(chunk, trackNumber),
|
|
111
|
-
trackNumber,
|
|
112
|
-
isVideo: true,
|
|
113
|
-
codecPrivate: (0, arraybuffer_to_uint8_array_1.arrayBufferToUint8Array)((metadata?.decoderConfig?.description ??
|
|
114
|
-
null)),
|
|
115
|
-
});
|
|
116
|
-
onMediaStateUpdate?.((prevState) => {
|
|
117
|
-
return {
|
|
118
|
-
...prevState,
|
|
119
|
-
encodedVideoFrames: prevState.encodedVideoFrames + 1,
|
|
120
|
-
};
|
|
121
|
-
});
|
|
122
|
-
},
|
|
123
|
-
onError: (err) => {
|
|
124
|
-
abortConversion(new Error(`Video encoder of track ${track.trackId} failed (see .cause of this error)`, {
|
|
125
|
-
cause: err,
|
|
126
|
-
}));
|
|
127
|
-
},
|
|
128
|
-
controller,
|
|
129
|
-
config: videoEncoderConfig,
|
|
130
|
-
logLevel,
|
|
131
|
-
outputCodec: videoOperation.videoCodec,
|
|
132
|
-
progress,
|
|
133
|
-
});
|
|
134
|
-
const videoDecoder = (0, video_decoder_1.createVideoDecoder)({
|
|
135
|
-
config: videoDecoderConfig,
|
|
136
|
-
onFrame: async (frame) => {
|
|
137
|
-
await (0, on_frame_1.onFrame)({
|
|
138
|
-
frame,
|
|
139
|
-
track,
|
|
140
|
-
videoEncoder,
|
|
141
|
-
onVideoFrame,
|
|
142
|
-
outputCodec: videoOperation.videoCodec,
|
|
143
|
-
rotation,
|
|
144
|
-
resizeOperation: videoOperation.resize ?? null,
|
|
145
|
-
});
|
|
146
|
-
},
|
|
147
|
-
onError: (err) => {
|
|
148
|
-
abortConversion(new Error(`Video decoder of track ${track.trackId} failed (see .cause of this error)`, {
|
|
149
|
-
cause: err,
|
|
150
|
-
}));
|
|
151
|
-
},
|
|
45
|
+
return (0, reencode_video_track_1.reencodeVideoTrack)({
|
|
46
|
+
videoOperation,
|
|
47
|
+
abortConversion,
|
|
152
48
|
controller,
|
|
153
49
|
logLevel,
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
log_1.Log.verbose(logLevel, 'Video decoder finished. Waiting for encoder to finish');
|
|
161
|
-
await videoEncoder.waitForFinish();
|
|
162
|
-
videoEncoder.close();
|
|
163
|
-
log_1.Log.verbose(logLevel, 'Encoder finished');
|
|
50
|
+
rotate,
|
|
51
|
+
track,
|
|
52
|
+
onVideoFrame,
|
|
53
|
+
state,
|
|
54
|
+
onMediaStateUpdate,
|
|
55
|
+
progressTracker,
|
|
164
56
|
});
|
|
165
|
-
return async (chunk) => {
|
|
166
|
-
await videoDecoder.processSample(chunk);
|
|
167
|
-
};
|
|
168
57
|
};
|
|
169
58
|
exports.makeVideoTrackHandler = makeVideoTrackHandler;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { LogLevel } from './log';
|
|
2
|
+
import type { WebCodecsController } from './webcodecs-controller';
|
|
3
|
+
type Processable = EncodedAudioChunk | EncodedVideoChunk | AudioData | VideoFrame;
|
|
4
|
+
export declare function processingQueue<T extends Processable>({ onOutput, logLevel, label, onError, controller, }: {
|
|
5
|
+
onOutput: (item: T) => Promise<void>;
|
|
6
|
+
onError: (error: Error) => void;
|
|
7
|
+
logLevel: LogLevel;
|
|
8
|
+
label: string;
|
|
9
|
+
controller: WebCodecsController;
|
|
10
|
+
}): {
|
|
11
|
+
input: (item: T) => void;
|
|
12
|
+
ioSynchronizer: {
|
|
13
|
+
inputItem: (timestamp: number) => void;
|
|
14
|
+
onOutput: (timestamp: number) => void;
|
|
15
|
+
waitForFinish: () => Promise<void>;
|
|
16
|
+
waitForQueueSize: (queueSize: number) => Promise<void>;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.processingQueue = processingQueue;
|
|
4
|
+
const io_synchronizer_1 = require("./io-manager/io-synchronizer");
|
|
5
|
+
function processingQueue({ onOutput, logLevel, label, onError, controller, }) {
|
|
6
|
+
const ioSynchronizer = (0, io_synchronizer_1.makeIoSynchronizer)({
|
|
7
|
+
logLevel,
|
|
8
|
+
label,
|
|
9
|
+
controller,
|
|
10
|
+
});
|
|
11
|
+
let queue = Promise.resolve();
|
|
12
|
+
let stopped = false;
|
|
13
|
+
const input = (item) => {
|
|
14
|
+
if (stopped) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
if (controller._internals._mediaParserController._internals.signal.aborted) {
|
|
18
|
+
stopped = true;
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const { timestamp } = item; // Saving in variable, because timestamp might become nulled
|
|
22
|
+
ioSynchronizer.inputItem(timestamp);
|
|
23
|
+
queue = queue
|
|
24
|
+
.then(() => {
|
|
25
|
+
if (stopped) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (controller._internals._mediaParserController._internals.signal.aborted) {
|
|
29
|
+
stopped = true;
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
return onOutput(item);
|
|
33
|
+
})
|
|
34
|
+
.then(() => {
|
|
35
|
+
ioSynchronizer.onOutput(timestamp);
|
|
36
|
+
return Promise.resolve();
|
|
37
|
+
})
|
|
38
|
+
.catch((err) => {
|
|
39
|
+
stopped = true;
|
|
40
|
+
onError(err);
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
return {
|
|
44
|
+
input,
|
|
45
|
+
ioSynchronizer,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { MediaParserAudioTrack, MediaParserLogLevel, MediaParserOnAudioSample } from '@remotion/media-parser';
|
|
2
|
+
import type { ConvertMediaOnAudioData } from './convert-media';
|
|
3
|
+
import type { MediaFn } from './create/media-fn';
|
|
4
|
+
import type { ProgressTracker } from './create/progress-tracker';
|
|
5
|
+
import type { AudioOperation } from './on-audio-track-handler';
|
|
6
|
+
import type { ConvertMediaProgressFn } from './throttled-state-update';
|
|
7
|
+
import type { WebCodecsController } from './webcodecs-controller';
|
|
8
|
+
export declare const reencodeAudioTrack: ({ audioOperation, track, logLevel, abortConversion, state, controller, onMediaStateUpdate, onAudioData, progressTracker, }: {
|
|
9
|
+
audioOperation: AudioOperation;
|
|
10
|
+
track: MediaParserAudioTrack;
|
|
11
|
+
logLevel: MediaParserLogLevel;
|
|
12
|
+
abortConversion: (errCause: Error) => void;
|
|
13
|
+
state: MediaFn;
|
|
14
|
+
controller: WebCodecsController;
|
|
15
|
+
onMediaStateUpdate: null | ConvertMediaProgressFn;
|
|
16
|
+
onAudioData: ConvertMediaOnAudioData | null;
|
|
17
|
+
progressTracker: ProgressTracker;
|
|
18
|
+
}) => Promise<MediaParserOnAudioSample | null>;
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.reencodeAudioTrack = void 0;
|
|
4
|
+
const media_parser_1 = require("@remotion/media-parser");
|
|
5
|
+
const audio_decoder_1 = require("./audio-decoder");
|
|
6
|
+
const audio_decoder_config_1 = require("./audio-decoder-config");
|
|
7
|
+
const audio_encoder_1 = require("./audio-encoder");
|
|
8
|
+
const audio_encoder_config_1 = require("./audio-encoder-config");
|
|
9
|
+
const convert_encoded_chunk_1 = require("./convert-encoded-chunk");
|
|
10
|
+
const log_1 = require("./log");
|
|
11
|
+
const processing_queue_1 = require("./processing-queue");
|
|
12
|
+
const reencodeAudioTrack = async ({ audioOperation, track, logLevel, abortConversion, state, controller, onMediaStateUpdate, onAudioData, progressTracker, }) => {
|
|
13
|
+
if (audioOperation.type !== 'reencode') {
|
|
14
|
+
throw new Error(`Audio track with ID ${track.trackId} could not be resolved with a valid operation. Received ${JSON.stringify(audioOperation)}, but must be either "copy", "reencode", "drop" or "fail"`);
|
|
15
|
+
}
|
|
16
|
+
const audioEncoderConfig = await (0, audio_encoder_config_1.getAudioEncoderConfig)({
|
|
17
|
+
numberOfChannels: track.numberOfChannels,
|
|
18
|
+
sampleRate: audioOperation.sampleRate ?? track.sampleRate,
|
|
19
|
+
codec: audioOperation.audioCodec,
|
|
20
|
+
bitrate: audioOperation.bitrate,
|
|
21
|
+
});
|
|
22
|
+
const audioDecoderConfig = await (0, audio_decoder_config_1.getAudioDecoderConfig)({
|
|
23
|
+
codec: track.codec,
|
|
24
|
+
numberOfChannels: track.numberOfChannels,
|
|
25
|
+
sampleRate: track.sampleRate,
|
|
26
|
+
description: track.description,
|
|
27
|
+
});
|
|
28
|
+
log_1.Log.verbose(logLevel, 'Audio encoder config', audioEncoderConfig);
|
|
29
|
+
log_1.Log.verbose(logLevel, 'Audio decoder config', audioDecoderConfig ?? track);
|
|
30
|
+
if (!audioEncoderConfig) {
|
|
31
|
+
abortConversion(new Error(`Could not configure audio encoder of track ${track.trackId}`));
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
if (!audioDecoderConfig) {
|
|
35
|
+
abortConversion(new Error(`Could not configure audio decoder of track ${track.trackId}`));
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
const codecPrivate = audioOperation.audioCodec === 'aac'
|
|
39
|
+
? media_parser_1.MediaParserInternals.createAacCodecPrivate({
|
|
40
|
+
audioObjectType: 2,
|
|
41
|
+
sampleRate: audioOperation.sampleRate ?? audioEncoderConfig.sampleRate,
|
|
42
|
+
channelConfiguration: audioEncoderConfig.numberOfChannels,
|
|
43
|
+
codecPrivate: null,
|
|
44
|
+
})
|
|
45
|
+
: null;
|
|
46
|
+
const { trackNumber } = await state.addTrack({
|
|
47
|
+
type: 'audio',
|
|
48
|
+
codec: audioOperation.audioCodec === 'wav'
|
|
49
|
+
? 'pcm-s16'
|
|
50
|
+
: audioOperation.audioCodec,
|
|
51
|
+
numberOfChannels: audioEncoderConfig.numberOfChannels,
|
|
52
|
+
sampleRate: audioOperation.sampleRate ?? audioEncoderConfig.sampleRate,
|
|
53
|
+
codecPrivate,
|
|
54
|
+
timescale: track.originalTimescale,
|
|
55
|
+
});
|
|
56
|
+
const audioEncoder = (0, audio_encoder_1.createAudioEncoder)({
|
|
57
|
+
// This is weird 😵💫
|
|
58
|
+
// Chrome completely ignores the sample rate and uses it's own
|
|
59
|
+
// We cannot determine it here because it depends on the system
|
|
60
|
+
// sample rate. Unhardcode then declare it later once we know.
|
|
61
|
+
onNewAudioSampleRate: (sampleRate) => {
|
|
62
|
+
state.updateTrackSampleRate({ sampleRate, trackNumber });
|
|
63
|
+
},
|
|
64
|
+
onChunk: async (chunk) => {
|
|
65
|
+
await state.addSample({
|
|
66
|
+
chunk: (0, convert_encoded_chunk_1.convertEncodedChunk)(chunk),
|
|
67
|
+
trackNumber,
|
|
68
|
+
isVideo: false,
|
|
69
|
+
codecPrivate,
|
|
70
|
+
});
|
|
71
|
+
onMediaStateUpdate?.((prevState) => {
|
|
72
|
+
return {
|
|
73
|
+
...prevState,
|
|
74
|
+
encodedAudioFrames: prevState.encodedAudioFrames + 1,
|
|
75
|
+
};
|
|
76
|
+
});
|
|
77
|
+
},
|
|
78
|
+
onError: (err) => {
|
|
79
|
+
abortConversion(new Error(`Audio encoder of track ${track.trackId} failed (see .cause of this error)`, {
|
|
80
|
+
cause: err,
|
|
81
|
+
}));
|
|
82
|
+
},
|
|
83
|
+
codec: audioOperation.audioCodec,
|
|
84
|
+
controller,
|
|
85
|
+
config: audioEncoderConfig,
|
|
86
|
+
logLevel,
|
|
87
|
+
});
|
|
88
|
+
const audioProcessingQueue = (0, processing_queue_1.processingQueue)({
|
|
89
|
+
controller,
|
|
90
|
+
label: 'AudioData processing queue',
|
|
91
|
+
logLevel,
|
|
92
|
+
onError(error) {
|
|
93
|
+
abortConversion(new Error(`Audio decoder of track ${track.trackId} failed. Config: ${JSON.stringify(audioDecoderConfig)} (see .cause of this error)`, {
|
|
94
|
+
cause: error,
|
|
95
|
+
}));
|
|
96
|
+
},
|
|
97
|
+
onOutput: async (audioData) => {
|
|
98
|
+
const newAudioData = onAudioData
|
|
99
|
+
? await onAudioData?.({ audioData, track })
|
|
100
|
+
: audioData;
|
|
101
|
+
if (newAudioData !== audioData) {
|
|
102
|
+
if (newAudioData.duration !== audioData.duration) {
|
|
103
|
+
throw new Error(`onAudioData returned a different duration than the input audio data. Original duration: ${audioData.duration}, new duration: ${newAudioData.duration}`);
|
|
104
|
+
}
|
|
105
|
+
if (newAudioData.numberOfChannels !== audioData.numberOfChannels) {
|
|
106
|
+
throw new Error(`onAudioData returned a different number of channels than the input audio data. Original channels: ${audioData.numberOfChannels}, new channels: ${newAudioData.numberOfChannels}`);
|
|
107
|
+
}
|
|
108
|
+
if (newAudioData.sampleRate !== audioData.sampleRate) {
|
|
109
|
+
throw new Error(`onAudioData returned a different sample rate than the input audio data. Original sample rate: ${audioData.sampleRate}, new sample rate: ${newAudioData.sampleRate}`);
|
|
110
|
+
}
|
|
111
|
+
if (newAudioData.format !== audioData.format) {
|
|
112
|
+
throw new Error(`onAudioData returned a different format than the input audio data. Original format: ${audioData.format}, new format: ${newAudioData.format}`);
|
|
113
|
+
}
|
|
114
|
+
if (newAudioData.timestamp !== audioData.timestamp) {
|
|
115
|
+
throw new Error(`onAudioData returned a different timestamp than the input audio data. Original timestamp: ${audioData.timestamp}, new timestamp: ${newAudioData.timestamp}`);
|
|
116
|
+
}
|
|
117
|
+
audioData.close();
|
|
118
|
+
}
|
|
119
|
+
await controller._internals._mediaParserController._internals.checkForAbortAndPause();
|
|
120
|
+
await audioEncoder.ioSynchronizer.waitForQueueSize(10);
|
|
121
|
+
await controller._internals._mediaParserController._internals.checkForAbortAndPause();
|
|
122
|
+
audioEncoder.encode(newAudioData);
|
|
123
|
+
onMediaStateUpdate?.((prevState) => {
|
|
124
|
+
return {
|
|
125
|
+
...prevState,
|
|
126
|
+
decodedAudioFrames: prevState.decodedAudioFrames + 1,
|
|
127
|
+
};
|
|
128
|
+
});
|
|
129
|
+
newAudioData.close();
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
const audioDecoder = (0, audio_decoder_1.internalCreateAudioDecoder)({
|
|
133
|
+
onFrame: async (audioData) => {
|
|
134
|
+
await controller._internals._mediaParserController._internals.checkForAbortAndPause();
|
|
135
|
+
await audioProcessingQueue.ioSynchronizer.waitForQueueSize(10);
|
|
136
|
+
audioProcessingQueue.input(audioData);
|
|
137
|
+
},
|
|
138
|
+
onError(error) {
|
|
139
|
+
abortConversion(new Error(`Audio decoder of track ${track.trackId} failed. Config: ${JSON.stringify(audioDecoderConfig)} (see .cause of this error)`, {
|
|
140
|
+
cause: error,
|
|
141
|
+
}));
|
|
142
|
+
},
|
|
143
|
+
controller,
|
|
144
|
+
config: audioDecoderConfig,
|
|
145
|
+
logLevel,
|
|
146
|
+
});
|
|
147
|
+
state.addWaitForFinishPromise(async () => {
|
|
148
|
+
await audioDecoder.waitForFinish();
|
|
149
|
+
log_1.Log.verbose(logLevel, 'Audio decoder finished');
|
|
150
|
+
audioDecoder.close();
|
|
151
|
+
await audioProcessingQueue.ioSynchronizer.waitForFinish();
|
|
152
|
+
log_1.Log.verbose(logLevel, 'Audio processing queue finished');
|
|
153
|
+
await audioEncoder.waitForFinish();
|
|
154
|
+
log_1.Log.verbose(logLevel, 'Audio encoder finished');
|
|
155
|
+
audioEncoder.close();
|
|
156
|
+
});
|
|
157
|
+
return async (audioSample) => {
|
|
158
|
+
progressTracker.setPossibleLowestTimestamp(Math.min(audioSample.timestamp, audioSample.decodingTimestamp ?? Infinity));
|
|
159
|
+
await controller._internals._mediaParserController._internals.checkForAbortAndPause();
|
|
160
|
+
await audioDecoder.waitForQueueToBeLessThan(10);
|
|
161
|
+
audioDecoder.decode(audioSample);
|
|
162
|
+
};
|
|
163
|
+
};
|
|
164
|
+
exports.reencodeAudioTrack = reencodeAudioTrack;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { MediaParserLogLevel, MediaParserOnVideoSample, MediaParserVideoTrack } from '@remotion/media-parser';
|
|
2
|
+
import type { ConvertMediaOnVideoFrame } from './convert-media';
|
|
3
|
+
import type { MediaFn } from './create/media-fn';
|
|
4
|
+
import type { ProgressTracker } from './create/progress-tracker';
|
|
5
|
+
import type { VideoOperation } from './on-video-track-handler';
|
|
6
|
+
import type { ConvertMediaProgressFn } from './throttled-state-update';
|
|
7
|
+
import type { WebCodecsController } from './webcodecs-controller';
|
|
8
|
+
export declare const reencodeVideoTrack: ({ videoOperation, rotate, track, logLevel, abortConversion, onMediaStateUpdate, controller, onVideoFrame, state, progressTracker, }: {
|
|
9
|
+
videoOperation: VideoOperation;
|
|
10
|
+
rotate: number;
|
|
11
|
+
track: MediaParserVideoTrack;
|
|
12
|
+
logLevel: MediaParserLogLevel;
|
|
13
|
+
abortConversion: (errCause: Error) => void;
|
|
14
|
+
onMediaStateUpdate: null | ConvertMediaProgressFn;
|
|
15
|
+
controller: WebCodecsController;
|
|
16
|
+
onVideoFrame: ConvertMediaOnVideoFrame | null;
|
|
17
|
+
state: MediaFn;
|
|
18
|
+
progressTracker: ProgressTracker;
|
|
19
|
+
}) => Promise<MediaParserOnVideoSample | null>;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.reencodeVideoTrack = void 0;
|
|
4
|
+
const arraybuffer_to_uint8_array_1 = require("./arraybuffer-to-uint8-array");
|
|
5
|
+
const convert_encoded_chunk_1 = require("./convert-encoded-chunk");
|
|
6
|
+
const log_1 = require("./log");
|
|
7
|
+
const on_frame_1 = require("./on-frame");
|
|
8
|
+
const processing_queue_1 = require("./processing-queue");
|
|
9
|
+
const rotation_1 = require("./rotation");
|
|
10
|
+
const sort_video_frames_1 = require("./sort-video-frames");
|
|
11
|
+
const video_decoder_1 = require("./video-decoder");
|
|
12
|
+
const video_decoder_config_1 = require("./video-decoder-config");
|
|
13
|
+
const video_encoder_1 = require("./video-encoder");
|
|
14
|
+
const video_encoder_config_1 = require("./video-encoder-config");
|
|
15
|
+
const reencodeVideoTrack = async ({ videoOperation, rotate, track, logLevel, abortConversion, onMediaStateUpdate, controller, onVideoFrame, state, progressTracker, }) => {
|
|
16
|
+
if (videoOperation.type !== 'reencode') {
|
|
17
|
+
throw new Error(`Video track with ID ${track.trackId} could not be resolved with a valid operation. Received ${JSON.stringify(videoOperation)}, but must be either "copy", "reencode", "drop" or "fail"`);
|
|
18
|
+
}
|
|
19
|
+
const rotation = (videoOperation.rotate ?? rotate) - track.rotation;
|
|
20
|
+
const { height: newHeight, width: newWidth } = (0, rotation_1.calculateNewDimensionsFromRotateAndScale)({
|
|
21
|
+
width: track.codedWidth,
|
|
22
|
+
height: track.codedHeight,
|
|
23
|
+
rotation,
|
|
24
|
+
videoCodec: videoOperation.videoCodec,
|
|
25
|
+
resizeOperation: videoOperation.resize ?? null,
|
|
26
|
+
});
|
|
27
|
+
const videoEncoderConfig = await (0, video_encoder_config_1.getVideoEncoderConfig)({
|
|
28
|
+
codec: videoOperation.videoCodec,
|
|
29
|
+
height: newHeight,
|
|
30
|
+
width: newWidth,
|
|
31
|
+
fps: track.fps,
|
|
32
|
+
});
|
|
33
|
+
const videoDecoderConfig = await (0, video_decoder_config_1.getVideoDecoderConfigWithHardwareAcceleration)(track);
|
|
34
|
+
log_1.Log.verbose(logLevel, 'Video encoder config', videoEncoderConfig);
|
|
35
|
+
log_1.Log.verbose(logLevel, 'Video decoder config', videoDecoderConfig ?? track);
|
|
36
|
+
if (videoEncoderConfig === null) {
|
|
37
|
+
abortConversion(new Error(`Could not configure video encoder of track ${track.trackId}`));
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
if (videoDecoderConfig === null) {
|
|
41
|
+
abortConversion(new Error(`Could not configure video decoder of track ${track.trackId}`));
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
const { trackNumber } = await state.addTrack({
|
|
45
|
+
type: 'video',
|
|
46
|
+
color: track.advancedColor,
|
|
47
|
+
width: newWidth,
|
|
48
|
+
height: newHeight,
|
|
49
|
+
codec: videoOperation.videoCodec,
|
|
50
|
+
codecPrivate: null,
|
|
51
|
+
timescale: track.originalTimescale,
|
|
52
|
+
});
|
|
53
|
+
log_1.Log.verbose(logLevel, `Created new video track with ID ${trackNumber}, codec ${videoOperation.videoCodec} and timescale ${track.originalTimescale}`);
|
|
54
|
+
const videoEncoder = (0, video_encoder_1.createVideoEncoder)({
|
|
55
|
+
onChunk: async (chunk, metadata) => {
|
|
56
|
+
await state.addSample({
|
|
57
|
+
chunk: (0, convert_encoded_chunk_1.convertEncodedChunk)(chunk),
|
|
58
|
+
trackNumber,
|
|
59
|
+
isVideo: true,
|
|
60
|
+
codecPrivate: (0, arraybuffer_to_uint8_array_1.arrayBufferToUint8Array)((metadata?.decoderConfig?.description ?? null)),
|
|
61
|
+
});
|
|
62
|
+
onMediaStateUpdate?.((prevState) => {
|
|
63
|
+
return {
|
|
64
|
+
...prevState,
|
|
65
|
+
encodedVideoFrames: prevState.encodedVideoFrames + 1,
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
},
|
|
69
|
+
onError: (err) => {
|
|
70
|
+
abortConversion(new Error(`Video encoder of track ${track.trackId} failed (see .cause of this error)`, {
|
|
71
|
+
cause: err,
|
|
72
|
+
}));
|
|
73
|
+
},
|
|
74
|
+
controller,
|
|
75
|
+
config: videoEncoderConfig,
|
|
76
|
+
logLevel,
|
|
77
|
+
outputCodec: videoOperation.videoCodec,
|
|
78
|
+
keyframeInterval: 40,
|
|
79
|
+
});
|
|
80
|
+
const videoProcessingQueue = (0, processing_queue_1.processingQueue)({
|
|
81
|
+
controller,
|
|
82
|
+
label: 'VideoFrame processing queue',
|
|
83
|
+
logLevel,
|
|
84
|
+
onError: (err) => {
|
|
85
|
+
abortConversion(new Error(`VideoFrame processing queue of track ${track.trackId} failed (see .cause of this error)`, {
|
|
86
|
+
cause: err,
|
|
87
|
+
}));
|
|
88
|
+
},
|
|
89
|
+
onOutput: async (frame) => {
|
|
90
|
+
await controller._internals._mediaParserController._internals.checkForAbortAndPause();
|
|
91
|
+
const processedFrame = await (0, on_frame_1.onFrame)({
|
|
92
|
+
frame,
|
|
93
|
+
track,
|
|
94
|
+
onVideoFrame,
|
|
95
|
+
outputCodec: videoOperation.videoCodec,
|
|
96
|
+
rotation,
|
|
97
|
+
resizeOperation: videoOperation.resize ?? null,
|
|
98
|
+
});
|
|
99
|
+
await controller._internals._mediaParserController._internals.checkForAbortAndPause();
|
|
100
|
+
await videoEncoder.ioSynchronizer.waitForQueueSize(10);
|
|
101
|
+
await controller._internals._mediaParserController._internals.checkForAbortAndPause();
|
|
102
|
+
videoEncoder.encode(processedFrame);
|
|
103
|
+
processedFrame.close();
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
const frameSorter = (0, sort_video_frames_1.videoFrameSorter)({
|
|
107
|
+
controller,
|
|
108
|
+
onOutput: async (frame) => {
|
|
109
|
+
await controller._internals._mediaParserController._internals.checkForAbortAndPause();
|
|
110
|
+
await videoProcessingQueue.ioSynchronizer.waitForQueueSize(10);
|
|
111
|
+
videoProcessingQueue.input(frame);
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
const videoDecoder = (0, video_decoder_1.createVideoDecoder)({
|
|
115
|
+
track: videoDecoderConfig,
|
|
116
|
+
onFrame: async (frame) => {
|
|
117
|
+
await frameSorter.waitUntilProcessed();
|
|
118
|
+
frameSorter.inputFrame(frame);
|
|
119
|
+
},
|
|
120
|
+
onError: (err) => {
|
|
121
|
+
abortConversion(new Error(`Video decoder of track ${track.trackId} failed (see .cause of this error)`, {
|
|
122
|
+
cause: err,
|
|
123
|
+
}));
|
|
124
|
+
},
|
|
125
|
+
controller,
|
|
126
|
+
logLevel,
|
|
127
|
+
});
|
|
128
|
+
state.addWaitForFinishPromise(async () => {
|
|
129
|
+
log_1.Log.verbose(logLevel, 'Waiting for video decoder to finish');
|
|
130
|
+
await videoDecoder.waitForFinish();
|
|
131
|
+
videoDecoder.close();
|
|
132
|
+
log_1.Log.verbose(logLevel, 'Video decoder finished. Waiting for encoder to finish');
|
|
133
|
+
await frameSorter.flush();
|
|
134
|
+
log_1.Log.verbose(logLevel, 'Frame sorter flushed');
|
|
135
|
+
await videoProcessingQueue.ioSynchronizer.waitForFinish();
|
|
136
|
+
log_1.Log.verbose(logLevel, 'Video processing queue finished');
|
|
137
|
+
await videoEncoder.waitForFinish();
|
|
138
|
+
videoEncoder.close();
|
|
139
|
+
log_1.Log.verbose(logLevel, 'Video encoder finished');
|
|
140
|
+
});
|
|
141
|
+
return async (chunk) => {
|
|
142
|
+
progressTracker.setPossibleLowestTimestamp(Math.min(chunk.timestamp, chunk.decodingTimestamp ?? Infinity));
|
|
143
|
+
await controller._internals._mediaParserController._internals.checkForAbortAndPause();
|
|
144
|
+
await videoDecoder.waitForQueueToBeLessThan(10);
|
|
145
|
+
if (chunk.type === 'key') {
|
|
146
|
+
await videoDecoder.flush();
|
|
147
|
+
}
|
|
148
|
+
videoDecoder.decode(chunk);
|
|
149
|
+
};
|
|
150
|
+
};
|
|
151
|
+
exports.reencodeVideoTrack = reencodeVideoTrack;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { WebCodecsController } from './webcodecs-controller';
|
|
2
|
-
export declare const videoFrameSorter: ({
|
|
3
|
-
onRelease: (frame: VideoFrame) => Promise<void>;
|
|
2
|
+
export declare const videoFrameSorter: ({ controller, onOutput, }: {
|
|
4
3
|
controller: WebCodecsController;
|
|
4
|
+
onOutput: (frame: VideoFrame) => Promise<void>;
|
|
5
5
|
}) => {
|
|
6
|
-
inputFrame: (frame: VideoFrame) =>
|
|
6
|
+
inputFrame: (frame: VideoFrame) => void;
|
|
7
|
+
waitUntilProcessed: () => Promise<void>;
|
|
7
8
|
flush: () => Promise<void>;
|
|
8
9
|
};
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.videoFrameSorter = void 0;
|
|
4
4
|
const MAX_QUEUE_SIZE = 5;
|
|
5
|
-
const videoFrameSorter = ({
|
|
5
|
+
const videoFrameSorter = ({ controller, onOutput, }) => {
|
|
6
6
|
const frames = [];
|
|
7
7
|
const releaseFrame = async () => {
|
|
8
8
|
await controller._internals._mediaParserController._internals.checkForAbortAndPause();
|
|
9
9
|
const frame = frames.shift();
|
|
10
10
|
if (frame) {
|
|
11
|
-
await
|
|
11
|
+
await onOutput(frame);
|
|
12
12
|
}
|
|
13
13
|
};
|
|
14
14
|
const sortFrames = () => {
|
|
@@ -44,8 +44,12 @@ const videoFrameSorter = ({ onRelease, controller, }) => {
|
|
|
44
44
|
controller._internals._mediaParserController._internals.signal.removeEventListener('abort', onAbort);
|
|
45
45
|
};
|
|
46
46
|
controller._internals._mediaParserController._internals.signal.addEventListener('abort', onAbort);
|
|
47
|
+
let promise = Promise.resolve();
|
|
47
48
|
return {
|
|
48
|
-
inputFrame
|
|
49
|
+
inputFrame: (frame) => {
|
|
50
|
+
promise = promise.then(() => inputFrame(frame));
|
|
51
|
+
},
|
|
52
|
+
waitUntilProcessed: () => promise,
|
|
49
53
|
flush,
|
|
50
54
|
};
|
|
51
55
|
};
|
package/dist/video-decoder.d.ts
CHANGED
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
import type { MediaParserLogLevel, MediaParserVideoSample } from '@remotion/media-parser';
|
|
2
|
-
import type { ProgressTracker } from './create/progress-tracker';
|
|
3
2
|
import type { WebCodecsController } from './webcodecs-controller';
|
|
4
3
|
export type WebCodecsVideoDecoder = {
|
|
5
|
-
|
|
6
|
-
waitForFinish: () => Promise<void>;
|
|
4
|
+
decode: (videoSample: MediaParserVideoSample | EncodedVideoChunk) => void;
|
|
7
5
|
close: () => void;
|
|
8
6
|
flush: () => Promise<void>;
|
|
7
|
+
waitForFinish: () => Promise<void>;
|
|
8
|
+
waitForQueueToBeLessThan: (items: number) => Promise<void>;
|
|
9
9
|
};
|
|
10
|
-
export declare const
|
|
11
|
-
onFrame: (frame: VideoFrame) => Promise<void
|
|
12
|
-
onError: (error:
|
|
13
|
-
controller: WebCodecsController;
|
|
10
|
+
export declare const internalCreateVideoDecoder: ({ onFrame, onError, controller, config, logLevel, }: {
|
|
11
|
+
onFrame: (frame: VideoFrame) => Promise<void> | void;
|
|
12
|
+
onError: (error: Error) => void;
|
|
13
|
+
controller: WebCodecsController | null;
|
|
14
14
|
config: VideoDecoderConfig;
|
|
15
15
|
logLevel: MediaParserLogLevel;
|
|
16
|
-
|
|
16
|
+
}) => WebCodecsVideoDecoder;
|
|
17
|
+
export declare const createVideoDecoder: ({ onFrame, onError, controller, track, logLevel, }: {
|
|
18
|
+
track: VideoDecoderConfig;
|
|
19
|
+
onFrame: (frame: VideoFrame) => Promise<void> | void;
|
|
20
|
+
onError: (error: Error) => void;
|
|
21
|
+
controller?: WebCodecsController;
|
|
22
|
+
logLevel?: MediaParserLogLevel;
|
|
17
23
|
}) => WebCodecsVideoDecoder;
|