Add proper mp4 encoding
This commit is contained in:
parent
4bb27e5e8c
commit
29f1b8cda2
|
|
@ -22,6 +22,7 @@
|
||||||
<script src="/libav-6.5.7.1-webm-vp9.js"></script>
|
<script src="/libav-6.5.7.1-webm-vp9.js"></script>
|
||||||
<script src="/libavjs-webcodecs-polyfill.js"></script>
|
<script src="/libavjs-webcodecs-polyfill.js"></script>
|
||||||
<script src="/webm-muxer.js"></script>
|
<script src="/webm-muxer.js"></script>
|
||||||
|
<script src="/mp4-muxer.js"></script>
|
||||||
<script src="UPNG.js"></script>
|
<script src="UPNG.js"></script>
|
||||||
<script src="pako.js"></script>
|
<script src="pako.js"></script>
|
||||||
<script type="module" src="/d3-interpolate-path.js"></script>
|
<script type="module" src="/d3-interpolate-path.js"></script>
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
|
|
@ -623,7 +623,8 @@
|
||||||
["libvorbis", "vorbis"],
|
["libvorbis", "vorbis"],
|
||||||
["libaom-av1", "av01"],
|
["libaom-av1", "av01"],
|
||||||
["libvpx-vp9", "vp09"],
|
["libvpx-vp9", "vp09"],
|
||||||
["libvpx", "vp8"]
|
["libvpx", "vp8"],
|
||||||
|
["libopenh264", "avc"]
|
||||||
]) {
|
]) {
|
||||||
if (encoders) {
|
if (encoders) {
|
||||||
if (yield libav.avcodec_find_encoder_by_name(avname))
|
if (yield libav.avcodec_find_encoder_by_name(avname))
|
||||||
|
|
@ -807,6 +808,14 @@
|
||||||
options["cpu-used"] = "8";
|
options["cpu-used"] = "8";
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case "h264":
|
||||||
|
video = true;
|
||||||
|
outCodec = "libopenh264";
|
||||||
|
if (config.latencyMode === "realtime") {
|
||||||
|
options.quality = "realtime";
|
||||||
|
options["cpu-used"] = "8";
|
||||||
|
}
|
||||||
|
break;
|
||||||
// Unsupported
|
// Unsupported
|
||||||
case "mp3":
|
case "mp3":
|
||||||
case "mp4a":
|
case "mp4a":
|
||||||
|
|
|
||||||
120
src/main.js
120
src/main.js
|
|
@ -5205,18 +5205,6 @@ async function render() {
|
||||||
selection: [],
|
selection: [],
|
||||||
shapeselection: [],
|
shapeselection: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
switch (ext) {
|
|
||||||
case "mp4":
|
|
||||||
exportMp4(path)
|
|
||||||
return
|
|
||||||
break
|
|
||||||
case "webm":
|
|
||||||
|
|
||||||
createProgressModal();
|
|
||||||
|
|
||||||
// Store the original context
|
|
||||||
const oldContext = context;
|
const oldContext = context;
|
||||||
context = exportContext;
|
context = exportContext;
|
||||||
|
|
||||||
|
|
@ -5224,11 +5212,113 @@ async function render() {
|
||||||
let currentFrame = 0;
|
let currentFrame = 0;
|
||||||
const bitrate = 1e6
|
const bitrate = 1e6
|
||||||
const frameTimeMicroseconds = parseInt(1_000_000 / config.framerate)
|
const frameTimeMicroseconds = parseInt(1_000_000 / config.framerate)
|
||||||
|
let target;
|
||||||
|
let muxer;
|
||||||
|
let videoEncoder;
|
||||||
|
|
||||||
|
|
||||||
|
switch (ext) {
|
||||||
|
case "mp4":
|
||||||
|
// exportMp4(path)
|
||||||
|
createProgressModal();
|
||||||
|
|
||||||
|
// Store the original context
|
||||||
|
|
||||||
await LibAVWebCodecs.load()
|
await LibAVWebCodecs.load()
|
||||||
console.log("Codecs loaded")
|
console.log("Codecs loaded")
|
||||||
const target = new WebMMuxer.ArrayBufferTarget()
|
target = new Mp4Muxer.ArrayBufferTarget()
|
||||||
const muxer = new WebMMuxer.Muxer({
|
muxer = new Mp4Muxer.Muxer({
|
||||||
|
target: target,
|
||||||
|
video: {
|
||||||
|
codec: 'avc',
|
||||||
|
width: config.fileWidth,
|
||||||
|
height: config.fileHeight,
|
||||||
|
frameRate: config.framerate,
|
||||||
|
},
|
||||||
|
fastStart: 'in-memory',
|
||||||
|
firstTimestampBehavior: 'offset',
|
||||||
|
})
|
||||||
|
videoEncoder = new VideoEncoder({
|
||||||
|
output: (chunk, meta) => muxer.addVideoChunk(chunk, meta, undefined, undefined, frameTimeMicroseconds),//, currentFrame * frameTimeMicroseconds),
|
||||||
|
error: (e) => console.error(e),
|
||||||
|
})
|
||||||
|
|
||||||
|
videoEncoder.configure({
|
||||||
|
codec: 'avc1.42001f',
|
||||||
|
width: 1280,
|
||||||
|
height: 720,
|
||||||
|
bitrate: 1e6
|
||||||
|
});
|
||||||
|
|
||||||
|
async function finishMp4Encoding() {
|
||||||
|
const progressText = document.getElementById('progressText');
|
||||||
|
progressText.innerText = 'Finalizing...';
|
||||||
|
const progressBar = document.getElementById('progressBar');
|
||||||
|
progressBar.value = 100;
|
||||||
|
await videoEncoder.flush()
|
||||||
|
muxer.finalize()
|
||||||
|
await writeFile(
|
||||||
|
path,
|
||||||
|
new Uint8Array(target.buffer),
|
||||||
|
);
|
||||||
|
const modal = document.getElementById('progressModal');
|
||||||
|
modal.style.display = 'none';
|
||||||
|
document.querySelector("body").style.cursor = "default";
|
||||||
|
}
|
||||||
|
|
||||||
|
const processMp4Frame = async () => {
|
||||||
|
if (currentFrame < root.maxFrame) {
|
||||||
|
// Update progress bar
|
||||||
|
const progressText = document.getElementById('progressText');
|
||||||
|
progressText.innerText = `Rendering frame ${currentFrame + 1} of ${root.maxFrame}`;
|
||||||
|
const progressBar = document.getElementById('progressBar');
|
||||||
|
const progress = Math.round(((currentFrame + 1) / root.maxFrame) * 100);
|
||||||
|
progressBar.value = progress;
|
||||||
|
|
||||||
|
root.setFrameNum(currentFrame)
|
||||||
|
exportContext.ctx.fillStyle = "white";
|
||||||
|
exportContext.ctx.rect(0, 0, config.fileWidth, config.fileHeight);
|
||||||
|
exportContext.ctx.fill();
|
||||||
|
root.draw(exportContext.ctx);
|
||||||
|
const frame = new VideoFrame(
|
||||||
|
await LibAVWebCodecs.createImageBitmap(canvas),
|
||||||
|
{ timestamp: currentFrame * frameTimeMicroseconds }
|
||||||
|
);
|
||||||
|
|
||||||
|
async function encodeFrame(frame) {
|
||||||
|
// const keyFrame = true
|
||||||
|
const keyFrame = currentFrame % 60 === 0
|
||||||
|
videoEncoder.encode(frame, { keyFrame })
|
||||||
|
frame.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
await encodeFrame(frame)
|
||||||
|
|
||||||
|
frame.close()
|
||||||
|
|
||||||
|
|
||||||
|
currentFrame++;
|
||||||
|
setTimeout(processMp4Frame, 4);
|
||||||
|
} else {
|
||||||
|
// Once all frames are processed, reset context and export
|
||||||
|
context = oldContext;
|
||||||
|
root.setFrameNum(oldRootFrame)
|
||||||
|
finishMp4Encoding()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
processMp4Frame();
|
||||||
|
return
|
||||||
|
break
|
||||||
|
case "webm":
|
||||||
|
|
||||||
|
createProgressModal();
|
||||||
|
|
||||||
|
|
||||||
|
await LibAVWebCodecs.load()
|
||||||
|
console.log("Codecs loaded")
|
||||||
|
target = new WebMMuxer.ArrayBufferTarget()
|
||||||
|
muxer = new WebMMuxer.Muxer({
|
||||||
target: target,
|
target: target,
|
||||||
video: {
|
video: {
|
||||||
codec: 'V_VP9',
|
codec: 'V_VP9',
|
||||||
|
|
@ -5238,7 +5328,7 @@ async function render() {
|
||||||
},
|
},
|
||||||
firstTimestampBehavior: 'offset',
|
firstTimestampBehavior: 'offset',
|
||||||
})
|
})
|
||||||
let videoEncoder = new VideoEncoder({
|
videoEncoder = new VideoEncoder({
|
||||||
output: (chunk, meta) => muxer.addVideoChunk(chunk, meta),//, currentFrame * frameTimeMicroseconds),
|
output: (chunk, meta) => muxer.addVideoChunk(chunk, meta),//, currentFrame * frameTimeMicroseconds),
|
||||||
error: (e) => console.error(e),
|
error: (e) => console.error(e),
|
||||||
})
|
})
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue