-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathencode.test.ts
94 lines (79 loc) · 2.64 KB
/
encode.test.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
import * as path from 'node:path';
import * as fs from 'node:fs';
import { Magick } from 'magickwand.js/native';
import ffmpeg from '@mmomtchev/ffmpeg';
import { VideoEncoder, Muxer } from '@mmomtchev/ffmpeg/stream';
ffmpeg.setLogLevel(process.env.DEBUG_FFMPEG ? ffmpeg.AV_LOG_DEBUG : ffmpeg.AV_LOG_ERROR);
const width = 320;
const height = 200;
const ballRadius = 20;
// Produce bouncing ball frames
function genFrame(state: { height: number; speed: number; }) {
const image = new Magick.Image(`${width}x${height}`, 'black');
image.magick('yuv');
image.depth(8);
image.samplingFactor('4:2:0');
image.fillColor('blue');
image.draw(new Magick.DrawableCircle(width / 2, state.height, width / 2 + ballRadius, state.height + ballRadius));
// movement
state.height -= state.speed;
// gravity acceleration
state.speed -= 1;
if (state.height > height - ballRadius) {
state.height = height - ballRadius;
state.speed = -state.speed;
}
return image;
}
it('produce a video from stills', (done) => {
try {
const tmpFile = path.resolve(__dirname, 'bouncing.mp4');
const format = new ffmpeg.PixelFormat('yuv420p');
// If the timebase is 1/25th, each frame's duration is 1
// (which is very practical but does not allow to add audio)
const timeBase = new ffmpeg.Rational(1, 25);
const videoOutput = new VideoEncoder({
type: 'Video',
codec: ffmpeg.AV_CODEC_H264,
bitRate: 2.5e6,
width,
height,
frameRate: new ffmpeg.Rational(25, 1),
timeBase,
pixelFormat: format
});
videoOutput.on('error', done);
const output = new Muxer({ outputFile: tmpFile, streams: [videoOutput] });
output.on('finish', () => {
fs.rm(tmpFile, done);
});
const state = { height: 720 / 2, speed: 0 };
let totalFrames = 250;
let pts = 0;
const write = function () {
try {
let frame;
do {
const image = genFrame(state);
const blob = new Magick.Blob;
image.write(blob);
frame = ffmpeg.VideoFrame.create(Buffer.from(blob.data()), format, width, height);
frame.setTimeBase(timeBase);
frame.setPts(new ffmpeg.Timestamp(pts++, timeBase));
// This is the Node.js Writable protocol
// write until write returns false, then wait for 'drain'
} while (videoOutput.write(frame, 'binary') && --totalFrames > 0);
if (totalFrames > 0)
videoOutput.once('drain', write);
else
videoOutput.end();
} catch (error) {
done(error);
}
};
write();
videoOutput.pipe(output.video[0]);
} catch (error) {
done(error);
}
});