@twick/ffmpeg 0.14.17 → 0.14.19

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.
@@ -1,265 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.VideoFrameExtractor = void 0;
4
- const telemetry_1 = require("@twick/telemetry");
5
- const ffmpeg = require("fluent-ffmpeg");
6
- const fs = require("fs");
7
- const os = require("os");
8
- const path = require("path");
9
- const uuid_1 = require("uuid");
10
- const settings_1 = require("./settings");
11
- const utils_1 = require("./utils");
12
- /**
13
- * Walks through a video file and extracts frames.
14
- */
15
- class VideoFrameExtractor {
16
- constructor(filePath, startTime, fps, duration) {
17
- this.ffmpegPath = settings_1.ffmpegSettings.getFfmpegPath();
18
- this.buffer = Buffer.alloc(0);
19
- this.bufferOffset = 0;
20
- // Images are buffered in memory until they are requested.
21
- this.imageBuffers = [];
22
- this.lastImage = null;
23
- this.framesProcessed = 0;
24
- this.width = 0;
25
- this.height = 0;
26
- this.frameSize = 0;
27
- this.codec = null;
28
- this.process = null;
29
- this.terminated = false;
30
- this.state = 'processing';
31
- this.filePath = filePath;
32
- this.downloadedFilePath = VideoFrameExtractor.downloadedVideoMap.get(filePath)?.localPath;
33
- this.startTimeOffset = VideoFrameExtractor.downloadedVideoMap.get(filePath)
34
- ?.startTimeOffset;
35
- this.startTime = startTime;
36
- this.duration = duration;
37
- this.toTime = this.getEndTime(this.startTime);
38
- this.fps = fps;
39
- (0, utils_1.getVideoMetadata)(this.downloadedFilePath).then(metadata => {
40
- this.width = metadata.width;
41
- this.height = metadata.height;
42
- this.frameSize = this.width * this.height * 4;
43
- this.buffer = Buffer.alloc(this.frameSize);
44
- this.codec = metadata.codec;
45
- if (this.startTime >= this.duration) {
46
- this.process = this.createFfmpegProcessToExtractFirstFrame(this.downloadedFilePath, this.codec);
47
- return;
48
- }
49
- this.process = this.createFfmpegProcess(this.startTime - this.startTimeOffset, this.toTime, this.downloadedFilePath, this.fps, this.codec);
50
- });
51
- }
52
- static downloadVideoChunk(url, startTime, endTime) {
53
- const outputDir = path.join(os.tmpdir(), `twick-decoder-chunks`);
54
- if (!fs.existsSync(outputDir)) {
55
- fs.mkdirSync(outputDir, { recursive: true });
56
- }
57
- return new Promise((resolve, reject) => {
58
- ffmpeg.ffprobe(url, (err, metadata) => {
59
- if (err) {
60
- reject(err);
61
- return;
62
- }
63
- const format = metadata.format.format_name?.split(',')[-1] || 'mp4';
64
- const outputFileName = `chunk_${(0, uuid_1.v4)()}.${format}`;
65
- const outputPath = path.join(outputDir, outputFileName);
66
- const toleranceInSeconds = 0.5;
67
- const adjustedStartTime = Math.max(startTime - toleranceInSeconds, 0);
68
- ffmpeg(url)
69
- .setFfmpegPath(settings_1.ffmpegSettings.getFfmpegPath())
70
- .inputOptions([
71
- `-ss ${adjustedStartTime}`,
72
- `-to ${endTime + toleranceInSeconds}`,
73
- ])
74
- .outputOptions(['-c copy'])
75
- .output(outputPath)
76
- .on('end', () => {
77
- this.downloadedVideoMap.set(url, {
78
- localPath: outputPath,
79
- startTimeOffset: adjustedStartTime,
80
- });
81
- resolve(outputPath);
82
- })
83
- .on('error', err => reject(err))
84
- .run();
85
- });
86
- });
87
- }
88
- getTime() {
89
- return this.startTime + this.framesProcessed / this.fps;
90
- }
91
- getLastTime() {
92
- return this.startTime + (this.framesProcessed - 1) / this.fps;
93
- }
94
- getLastFrame() {
95
- return this.lastImage;
96
- }
97
- getWidth() {
98
- return this.width;
99
- }
100
- getHeight() {
101
- return this.height;
102
- }
103
- getEndTime(startTime) {
104
- return Math.min(startTime + VideoFrameExtractor.chunkLengthInSeconds, this.duration);
105
- }
106
- getArgs(codec, range, fps) {
107
- const inputOptions = [];
108
- const outputOptions = [];
109
- inputOptions.push('-loglevel', settings_1.ffmpegSettings.getLogLevel());
110
- if (range) {
111
- inputOptions.push(...['-ss', range[0].toFixed(2), '-to', range[1].toFixed(2)]);
112
- }
113
- if (codec === 'vp9') {
114
- inputOptions.push('-vcodec', 'libvpx-vp9');
115
- }
116
- if (fps) {
117
- outputOptions.push('-vf', `fps=fps=${fps}`);
118
- }
119
- if (!range) {
120
- outputOptions.push('-vframes', '1');
121
- }
122
- outputOptions.push('-f', 'rawvideo');
123
- outputOptions.push('-pix_fmt', 'rgba');
124
- return { inputOptions, outputOptions };
125
- }
126
- createFfmpegProcess(startTime, toTime, filePath, fps, codec) {
127
- const { inputOptions, outputOptions } = this.getArgs(codec, [startTime, toTime], fps);
128
- const process = ffmpeg(filePath)
129
- .setFfmpegPath(this.ffmpegPath)
130
- .inputOptions(inputOptions)
131
- .outputOptions(outputOptions)
132
- .on('end', () => {
133
- this.handleClose(0);
134
- })
135
- .on('error', err => {
136
- this.handleError(err);
137
- })
138
- .on('stderr', stderrLine => {
139
- console.log(stderrLine);
140
- })
141
- .on('stdout', stderrLine => {
142
- console.log(stderrLine);
143
- });
144
- const ffstream = process.pipe();
145
- ffstream.on('data', (data) => {
146
- this.processData(data);
147
- });
148
- return process;
149
- }
150
- /**
151
- * We call this in the case that the time requested is greater than the
152
- * duration of the video. In this case, we want to display the first frame
153
- * of the video.
154
- *
155
- * Note: This does NOT match the behavior of the old implementation
156
- * inside of 2d/src/lib/components/Video.ts. In the old implementation, the
157
- * last frame is shown instead of the first frame.
158
- */
159
- createFfmpegProcessToExtractFirstFrame(filePath, codec) {
160
- const { inputOptions, outputOptions } = this.getArgs(codec, undefined, undefined);
161
- const process = ffmpeg(filePath)
162
- .setFfmpegPath(this.ffmpegPath)
163
- .inputOptions(inputOptions)
164
- .outputOptions(outputOptions)
165
- .on('end', () => {
166
- this.handleClose(0);
167
- })
168
- .on('error', err => {
169
- this.handleError(err);
170
- })
171
- .on('stderr', stderrLine => {
172
- console.log(stderrLine);
173
- })
174
- .on('stdout', stderrLine => {
175
- console.log(stderrLine);
176
- });
177
- const ffstream = process.pipe();
178
- ffstream.on('data', (data) => {
179
- this.processData(data);
180
- });
181
- return process;
182
- }
183
- processData(data) {
184
- let dataOffset = 0;
185
- while (dataOffset < data.length) {
186
- const remainingSpace = this.frameSize - this.bufferOffset;
187
- const chunkSize = Math.min(remainingSpace, data.length - dataOffset);
188
- data.copy(this.buffer, this.bufferOffset, dataOffset, dataOffset + chunkSize);
189
- this.bufferOffset += chunkSize;
190
- dataOffset += chunkSize;
191
- // We have a complete frame
192
- if (this.bufferOffset === this.frameSize) {
193
- this.imageBuffers.push(Buffer.from(this.buffer)); // Create a copy
194
- this.bufferOffset = 0; // Reset buffer for next frame
195
- }
196
- }
197
- }
198
- async popImage() {
199
- if (this.imageBuffers.length) {
200
- const image = this.imageBuffers.shift();
201
- this.framesProcessed++;
202
- this.lastImage = image;
203
- return image;
204
- }
205
- if (this.state === 'error') {
206
- throw new Error('An error occurred while extracting the video frames.');
207
- }
208
- // If the video is done and there are no more frames to extract, return the last frame.
209
- if (this.state === 'done' && this.toTime >= this.duration) {
210
- return this.lastImage;
211
- }
212
- // If there are more frames to extract, request the next chunk.
213
- if (this.state === 'done') {
214
- this.startTime = this.toTime;
215
- this.toTime = Math.min(this.startTime + VideoFrameExtractor.chunkLengthInSeconds, this.duration);
216
- if (!this.codec) {
217
- throw new Error("Can't extract frames without a codec. This error should never happen.");
218
- }
219
- this.process = this.createFfmpegProcess(this.startTime, this.toTime, this.downloadedFilePath, this.fps, this.codec);
220
- this.state = 'processing';
221
- }
222
- while (this.imageBuffers.length < 1) {
223
- await new Promise(resolve => setTimeout(resolve, 50));
224
- }
225
- const image = this.imageBuffers.shift();
226
- this.framesProcessed++;
227
- this.lastImage = image;
228
- return image;
229
- }
230
- handleClose(code) {
231
- this.state = code === 0 ? 'done' : 'error';
232
- }
233
- async handleError(err) {
234
- const code = err.code;
235
- if (this.terminated) {
236
- return;
237
- }
238
- if (code === 'ENOENT') {
239
- (0, telemetry_1.sendEvent)(telemetry_1.EventName.Error, { error: 'ffmpeg-not-found' });
240
- throw new Error('Error: ffmpeg not found. Make sure ffmpeg is installed on your system.');
241
- }
242
- else if (err.message.includes('SIGSEGV')) {
243
- (0, telemetry_1.sendEvent)(telemetry_1.EventName.Error, {
244
- error: 'ffmpeg-sigsegv',
245
- message: err.message,
246
- });
247
- throw new Error(`Error: Segmentation fault when running ffmpeg. This is a common issue on Linux, you might be able to fix it by installing nscd ('sudo apt-get install nscd'). For more information, see https://docs.re.video/common-issues/ffmpeg/`);
248
- }
249
- else {
250
- await (0, telemetry_1.sendEvent)(telemetry_1.EventName.Error, {
251
- error: 'ffmpeg-error',
252
- message: err.message,
253
- });
254
- throw new Error(`An ffmpeg error occurred while fetching frames from source video ${this.filePath}: ${err}`);
255
- }
256
- }
257
- destroy() {
258
- this.terminated = true;
259
- this.process?.kill('SIGTERM');
260
- }
261
- }
262
- exports.VideoFrameExtractor = VideoFrameExtractor;
263
- VideoFrameExtractor.chunkLengthInSeconds = 5;
264
- VideoFrameExtractor.downloadedVideoMap = new Map();
265
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidmlkZW8tZnJhbWUtZXh0cmFjdG9yLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL3ZpZGVvLWZyYW1lLWV4dHJhY3Rvci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSxnREFBc0Q7QUFDdEQsd0NBQXdDO0FBQ3hDLHlCQUF5QjtBQUN6Qix5QkFBeUI7QUFDekIsNkJBQTZCO0FBQzdCLCtCQUFrQztBQUNsQyx5Q0FBMEM7QUFDMUMsbUNBQXlDO0FBSXpDOztHQUVHO0FBQ0gsTUFBYSxtQkFBbUI7SUFtQzlCLFlBQ0UsUUFBZ0IsRUFDaEIsU0FBaUIsRUFDakIsR0FBVyxFQUNYLFFBQWdCO1FBcENELGVBQVUsR0FBRyx5QkFBYyxDQUFDLGFBQWEsRUFBRSxDQUFDO1FBTXJELFdBQU0sR0FBVyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQ2pDLGlCQUFZLEdBQVcsQ0FBQyxDQUFDO1FBRWpDLDBEQUEwRDtRQUNsRCxpQkFBWSxHQUFhLEVBQUUsQ0FBQztRQUM1QixjQUFTLEdBQWtCLElBQUksQ0FBQztRQU9oQyxvQkFBZSxHQUFXLENBQUMsQ0FBQztRQUU1QixVQUFLLEdBQVcsQ0FBQyxDQUFDO1FBQ2xCLFdBQU0sR0FBVyxDQUFDLENBQUM7UUFDbkIsY0FBUyxHQUFXLENBQUMsQ0FBQztRQUN0QixVQUFLLEdBQWtCLElBQUksQ0FBQztRQUM1QixZQUFPLEdBQWdDLElBQUksQ0FBQztRQUM1QyxlQUFVLEdBQVksS0FBSyxDQUFDO1FBYWxDLElBQUksQ0FBQyxLQUFLLEdBQUcsWUFBWSxDQUFDO1FBQzFCLElBQUksQ0FBQyxRQUFRLEdBQUcsUUFBUSxDQUFDO1FBQ3pCLElBQUksQ0FBQyxrQkFBa0IsR0FBRyxtQkFBbUIsQ0FBQyxrQkFBa0IsQ0FBQyxHQUFHLENBQ2xFLFFBQVEsQ0FDVCxFQUFFLFNBQW1CLENBQUM7UUFDdkIsSUFBSSxDQUFDLGVBQWUsR0FBRyxtQkFBbUIsQ0FBQyxrQkFBa0IsQ0FB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