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="/libavjs-webcodecs-polyfill.js"></script>
|
||||
<script src="/webm-muxer.js"></script>
|
||||
<script src="/mp4-muxer.js"></script>
|
||||
<script src="UPNG.js"></script>
|
||||
<script src="pako.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"],
|
||||
["libaom-av1", "av01"],
|
||||
["libvpx-vp9", "vp09"],
|
||||
["libvpx", "vp8"]
|
||||
["libvpx", "vp8"],
|
||||
["libopenh264", "avc"]
|
||||
]) {
|
||||
if (encoders) {
|
||||
if (yield libav.avcodec_find_encoder_by_name(avname))
|
||||
|
|
@ -807,6 +808,14 @@
|
|||
options["cpu-used"] = "8";
|
||||
}
|
||||
break;
|
||||
case "h264":
|
||||
video = true;
|
||||
outCodec = "libopenh264";
|
||||
if (config.latencyMode === "realtime") {
|
||||
options.quality = "realtime";
|
||||
options["cpu-used"] = "8";
|
||||
}
|
||||
break;
|
||||
// Unsupported
|
||||
case "mp3":
|
||||
case "mp4a":
|
||||
|
|
|
|||
120
src/main.js
120
src/main.js
|
|
@ -5205,18 +5205,6 @@ async function render() {
|
|||
selection: [],
|
||||
shapeselection: [],
|
||||
};
|
||||
|
||||
|
||||
switch (ext) {
|
||||
case "mp4":
|
||||
exportMp4(path)
|
||||
return
|
||||
break
|
||||
case "webm":
|
||||
|
||||
createProgressModal();
|
||||
|
||||
// Store the original context
|
||||
const oldContext = context;
|
||||
context = exportContext;
|
||||
|
||||
|
|
@ -5224,11 +5212,113 @@ async function render() {
|
|||
let currentFrame = 0;
|
||||
const bitrate = 1e6
|
||||
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()
|
||||
console.log("Codecs loaded")
|
||||
const target = new WebMMuxer.ArrayBufferTarget()
|
||||
const muxer = new WebMMuxer.Muxer({
|
||||
target = new Mp4Muxer.ArrayBufferTarget()
|
||||
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,
|
||||
video: {
|
||||
codec: 'V_VP9',
|
||||
|
|
@ -5238,7 +5328,7 @@ async function render() {
|
|||
},
|
||||
firstTimestampBehavior: 'offset',
|
||||
})
|
||||
let videoEncoder = new VideoEncoder({
|
||||
videoEncoder = new VideoEncoder({
|
||||
output: (chunk, meta) => muxer.addVideoChunk(chunk, meta),//, currentFrame * frameTimeMicroseconds),
|
||||
error: (e) => console.error(e),
|
||||
})
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue