input as a stream-ish
This commit is contained in:
@@ -1,44 +1,41 @@
|
|||||||
import fs from "node:fs";
|
|
||||||
import beamcoder from "beamcoder";
|
import beamcoder from "beamcoder";
|
||||||
import EventEmitter from "node:events";
|
import EventEmitter from "node:events";
|
||||||
|
import fs from "node:fs";
|
||||||
|
import Stream from "node:stream";
|
||||||
|
|
||||||
|
const sleep = (waitTimeInMs) => new Promise((resolve) => setTimeout(resolve, waitTimeInMs));
|
||||||
|
let c = 10;
|
||||||
|
const readableStream = new Stream.Readable({
|
||||||
|
async read() {
|
||||||
|
await sleep(10);
|
||||||
|
if (c < 810) {
|
||||||
|
const data = fs.readFileSync(
|
||||||
|
`captures/${parseInt(c / 10)
|
||||||
|
.toString()
|
||||||
|
.padStart(4, "0")}.jpg`,
|
||||||
|
);
|
||||||
|
this.push(data);
|
||||||
|
} else {
|
||||||
|
this.push(null);
|
||||||
|
}
|
||||||
|
c++;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const ee = new EventEmitter();
|
const ee = new EventEmitter();
|
||||||
|
|
||||||
let demuxers = beamcoder.demuxers();
|
let demuxers = beamcoder.demuxers();
|
||||||
//console.log(demuxers);
|
// console.log(demuxers);
|
||||||
|
|
||||||
ee.on("inputFrame", (f) => {});
|
ee.on("inputFrame", (f) => {});
|
||||||
async function imageToVideo(imagePath, duration, frameRate = 30) {
|
|
||||||
const muxTimeBase = 90000;
|
|
||||||
let demuxerStream = beamcoder.demuxerStream({ highwaterMark: 65536 });
|
|
||||||
fs.createReadStream(imagePath).pipe(demuxerStream);
|
|
||||||
// Create a demuxer for the JPEG image
|
|
||||||
// let demuxer = await beamcoder.demuxer(imagePath);
|
|
||||||
let demuxer = await demuxerStream.demuxer({ name: "jpeg_pipe" });
|
|
||||||
|
|
||||||
// Read the image packet
|
const makeEncoder = (frame, frameRate) => {
|
||||||
let packet = await demuxer.read();
|
return beamcoder.encoder({
|
||||||
|
|
||||||
// Create a decoder for the image
|
|
||||||
let decoder = beamcoder.decoder({ demuxer: demuxer, name: "mjpeg" });
|
|
||||||
|
|
||||||
// Decode the image to get the frame
|
|
||||||
let frames = await decoder.decode(packet);
|
|
||||||
// decoder.flush();
|
|
||||||
//console.log("frames", frames);
|
|
||||||
let frame = frames.frames[0];
|
|
||||||
//console.log("frame", frame.width, frame.height);
|
|
||||||
|
|
||||||
// Create an H.264 encoder
|
|
||||||
// https://stackoverflow.com/a/13646293/3530257
|
|
||||||
// > the codec unit of measurement is commonly set to the interval between each frame and the next,
|
|
||||||
// > so that frame times are successive integers.
|
|
||||||
let encoder = beamcoder.encoder({
|
|
||||||
name: "libx264",
|
name: "libx264",
|
||||||
width: frame.width,
|
width: frame.width,
|
||||||
height: frame.height,
|
height: frame.height,
|
||||||
bit_rate: 400000,
|
bit_rate: 400000,
|
||||||
//qmin: 22,
|
// qmin: 22,
|
||||||
time_base: [1, frameRate],
|
time_base: [1, frameRate],
|
||||||
framerate: [frameRate, 1],
|
framerate: [frameRate, 1],
|
||||||
pix_fmt: "yuv420p",
|
pix_fmt: "yuv420p",
|
||||||
@@ -46,56 +43,88 @@ async function imageToVideo(imagePath, duration, frameRate = 30) {
|
|||||||
gop_size: 10,
|
gop_size: 10,
|
||||||
max_b_frames: 1,
|
max_b_frames: 1,
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
async function imageToVideo(imagePath, duration, frameRate = 20) {
|
||||||
|
const muxTimeBase = 90000;
|
||||||
|
let demuxerStream = beamcoder.demuxerStream({ highwaterMark: 65536 });
|
||||||
|
|
||||||
let stream = beamcoder.muxerStream({});
|
console.log("piping");
|
||||||
stream.pipe(fs.createWriteStream("test.mp4"));
|
readableStream.pipe(demuxerStream);
|
||||||
// Create a muxer for the output video
|
console.log("creating demuxer");
|
||||||
let muxer = stream.muxer({ format_name: "mp4" });
|
// Create a demuxer for the JPEG image
|
||||||
|
// let demuxer = await beamcoder.demuxer(imagePath);
|
||||||
|
let demuxer = await demuxerStream.demuxer({ name: "jpeg_pipe" });
|
||||||
|
// Create a decoder for the image
|
||||||
|
let decoder = beamcoder.decoder({ demuxer: demuxer, name: "mjpeg" });
|
||||||
|
|
||||||
//console.log(demuxer.streams[0].codecpar.extradata); // null
|
// Read the image packet
|
||||||
let vstr = muxer.newStream({
|
console.log("wait demuxer");
|
||||||
name: "h264",
|
// let packet = await demuxer.read();
|
||||||
time_base: [1, muxTimeBase], //frameRate],
|
let encoder = null;
|
||||||
interleaved: true,
|
let muxer = null;
|
||||||
});
|
let vstr = null;
|
||||||
|
let i = 0;
|
||||||
|
while (true) {
|
||||||
|
let packet = await demuxer.read();
|
||||||
|
if (packet == null) break;
|
||||||
|
let frames = await decoder.decode(packet);
|
||||||
|
let frame = frames.frames[0];
|
||||||
|
if (encoder == null) {
|
||||||
|
encoder = makeEncoder(frame, frameRate);
|
||||||
|
// Create an H.264 encoder
|
||||||
|
// https://stackoverflow.com/a/13646293/3530257
|
||||||
|
// > the codec unit of measurement is commonly set to the interval between
|
||||||
|
// each frame and the next, > so that frame times are successive integers.
|
||||||
|
|
||||||
// the Object.assign is structural (!!)
|
// TODO a muxer per client?
|
||||||
Object.assign(vstr.codecpar, {
|
let stream = beamcoder.muxerStream({});
|
||||||
width: encoder.width,
|
stream.pipe(fs.createWriteStream("test.mp4"));
|
||||||
height: encoder.height,
|
// Create a muxer for the output video
|
||||||
format: encoder.pix_fmt,
|
muxer = stream.muxer({ format_name: "mp4" });
|
||||||
});
|
|
||||||
|
|
||||||
await muxer.openIO();
|
// console.log(demuxer.streams[0].codecpar.extradata); // null
|
||||||
// adding "empty_moov" crashes mpv/ffmpeg
|
vstr = muxer.newStream({
|
||||||
//await muxer.initOutput({ movflags: "frag_keyframe+default_base_moof+faststart" });
|
name: "h264",
|
||||||
await muxer.initOutput({ movflags: "frag_keyframe" });
|
time_base: [1, muxTimeBase], // frameRate],
|
||||||
console.log("inited");
|
interleaved: true,
|
||||||
// Add a video stream to the muxer
|
});
|
||||||
await muxer.writeHeader();
|
|
||||||
console.log("header written");
|
|
||||||
// Number of frames to encode
|
|
||||||
let totalFrames = duration * frameRate;
|
|
||||||
|
|
||||||
console.log("making frames", totalFrames);
|
// the Object.assign is structural (!!)
|
||||||
for (let i = 0; i < totalFrames; i++) {
|
Object.assign(vstr.codecpar, {
|
||||||
frame.pts = i; // << the successive integers
|
width: encoder.width,
|
||||||
frame.dts = i; // << the successive integers
|
height: encoder.height,
|
||||||
let encodedPackets = await encoder.encode(frame);
|
format: encoder.pix_fmt,
|
||||||
// console.log(encodedPackets);
|
});
|
||||||
|
|
||||||
// Write the encoded packets to the output file
|
await muxer.openIO();
|
||||||
for (let packet of encodedPackets.packets) {
|
// adding "empty_moov" crashes mpv/ffmpeg
|
||||||
packet.duration = 1;
|
// await muxer.initOutput({ movflags:
|
||||||
packet.stream_index = vstr.index;
|
// "frag_keyframe+default_base_moof+faststart" });
|
||||||
packet.pts = (packet.pts * muxTimeBase) / frameRate;
|
await muxer.initOutput({ movflags: "frag_keyframe" });
|
||||||
packet.dts = (packet.dts * muxTimeBase) / frameRate;
|
console.log("inited");
|
||||||
//packet.pts = i;
|
// Add a video stream to the muxer
|
||||||
await muxer.writeFrame(packet);
|
await muxer.writeHeader();
|
||||||
// outFile.write(packet.data);
|
console.log("header written");
|
||||||
}
|
}
|
||||||
}
|
if (frame) {
|
||||||
|
frame.pts = i; // << the successive integers
|
||||||
|
frame.dts = i; // << the successive integers
|
||||||
|
let encodedPackets = await encoder.encode(frame);
|
||||||
|
|
||||||
|
// Write the encoded packets to the output file
|
||||||
|
for (let packet of encodedPackets.packets) {
|
||||||
|
packet.duration = 1;
|
||||||
|
packet.stream_index = vstr.index;
|
||||||
|
packet.pts = (packet.pts * muxTimeBase) / frameRate;
|
||||||
|
packet.dts = (packet.dts * muxTimeBase) / frameRate;
|
||||||
|
// packet.pts = i;
|
||||||
|
await muxer.writeFrame(packet);
|
||||||
|
// outFile.write(packet.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
// Finalize the encoder and muxer
|
// Finalize the encoder and muxer
|
||||||
let encodedPackets = await encoder.flush();
|
let encodedPackets = await encoder.flush();
|
||||||
// after flushing the encoder, we may hve some more packets
|
// after flushing the encoder, we may hve some more packets
|
||||||
|
|||||||
Reference in New Issue
Block a user