@twick/ffmpeg 0.11.0
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.
- package/LICENSE +21 -0
- package/dist/ffmpeg-exporter-server.d.ts +26 -0
- package/dist/ffmpeg-exporter-server.d.ts.map +1 -0
- package/dist/ffmpeg-exporter-server.js +90 -0
- package/dist/generate-audio.d.ts +13 -0
- package/dist/generate-audio.d.ts.map +1 -0
- package/dist/generate-audio.js +195 -0
- package/dist/image-stream.d.ts +8 -0
- package/dist/image-stream.d.ts.map +1 -0
- package/dist/image-stream.js +25 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/settings.d.ts +22 -0
- package/dist/settings.d.ts.map +1 -0
- package/dist/settings.js +56 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/utils.d.ts +21 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +232 -0
- package/dist/video-frame-extractor.d.ts +58 -0
- package/dist/video-frame-extractor.d.ts.map +1 -0
- package/dist/video-frame-extractor.js +265 -0
- package/package.json +31 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022 motion-canvas
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { FfmpegExporterOptions, RendererResult, RendererSettings } from '@twick/core';
|
|
2
|
+
export interface FFmpegExporterSettings extends RendererSettings {
|
|
3
|
+
fastStart: boolean;
|
|
4
|
+
includeAudio: boolean;
|
|
5
|
+
output: string;
|
|
6
|
+
}
|
|
7
|
+
export declare const extensions: Record<FfmpegExporterOptions['format'], string>;
|
|
8
|
+
/**
|
|
9
|
+
* The server-side implementation of the FFmpeg video exporter.
|
|
10
|
+
*/
|
|
11
|
+
export declare class FFmpegExporterServer {
|
|
12
|
+
private readonly stream;
|
|
13
|
+
private readonly command;
|
|
14
|
+
private readonly promise;
|
|
15
|
+
private readonly settings;
|
|
16
|
+
private readonly jobFolder;
|
|
17
|
+
private readonly format;
|
|
18
|
+
constructor(settings: FFmpegExporterSettings);
|
|
19
|
+
start(): Promise<void>;
|
|
20
|
+
handleFrame({ data }: {
|
|
21
|
+
data: string;
|
|
22
|
+
}): Promise<void>;
|
|
23
|
+
end(result: RendererResult): Promise<void>;
|
|
24
|
+
kill(): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=ffmpeg-exporter-server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ffmpeg-exporter-server.d.ts","sourceRoot":"","sources":["../src/ffmpeg-exporter-server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,qBAAqB,EACrB,cAAc,EACd,gBAAgB,EACjB,MAAM,aAAa,CAAC;AAQrB,MAAM,WAAW,sBAAuB,SAAQ,gBAAgB;IAC9D,SAAS,EAAE,OAAO,CAAC;IACnB,YAAY,EAAE,OAAO,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;CAChB;AAQD,eAAO,MAAM,UAAU,EAAE,MAAM,CAAC,qBAAqB,CAAC,QAAQ,CAAC,EAAE,MAAM,CAItE,CAAC;AAEF;;GAEG;AACH,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;IACrC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAuB;IAC/C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAgB;IACxC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAyB;IAClD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkC;gBAEtC,QAAQ,EAAE,sBAAsB;IA4CtC,KAAK;IAIL,WAAW,CAAC,EAAC,IAAI,EAAC,EAAE;QAAC,IAAI,EAAE,MAAM,CAAA;KAAC;IAKlC,GAAG,CAAC,MAAM,EAAE,cAAc;IAc1B,IAAI;CAQlB"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FFmpegExporterServer = exports.extensions = void 0;
|
|
4
|
+
const telemetry_1 = require("@twick/telemetry");
|
|
5
|
+
const ffmpeg = require("fluent-ffmpeg");
|
|
6
|
+
const os = require("os");
|
|
7
|
+
const path = require("path");
|
|
8
|
+
const image_stream_1 = require("./image-stream");
|
|
9
|
+
const settings_1 = require("./settings");
|
|
10
|
+
const pixelFormats = {
|
|
11
|
+
mp4: 'yuv420p',
|
|
12
|
+
webm: 'yuva420p',
|
|
13
|
+
proRes: 'yuva444p10le',
|
|
14
|
+
};
|
|
15
|
+
exports.extensions = {
|
|
16
|
+
mp4: 'mp4',
|
|
17
|
+
webm: 'webm',
|
|
18
|
+
proRes: 'mov',
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* The server-side implementation of the FFmpeg video exporter.
|
|
22
|
+
*/
|
|
23
|
+
class FFmpegExporterServer {
|
|
24
|
+
constructor(settings) {
|
|
25
|
+
if (settings.exporter.name !== '@twick/core/ffmpeg') {
|
|
26
|
+
throw new Error('Invalid exporter');
|
|
27
|
+
}
|
|
28
|
+
this.settings = settings;
|
|
29
|
+
this.format = settings.exporter.options.format;
|
|
30
|
+
this.jobFolder = path.join(os.tmpdir(), `twick-${this.settings.name}-${settings.hiddenFolderId}`);
|
|
31
|
+
this.stream = new image_stream_1.ImageStream();
|
|
32
|
+
ffmpeg.setFfmpegPath(settings_1.ffmpegSettings.getFfmpegPath());
|
|
33
|
+
this.command = ffmpeg();
|
|
34
|
+
// Input image sequence
|
|
35
|
+
this.command
|
|
36
|
+
.input(this.stream)
|
|
37
|
+
.inputFormat('image2pipe')
|
|
38
|
+
.inputFps(settings.fps);
|
|
39
|
+
// Output settings
|
|
40
|
+
const size = {
|
|
41
|
+
x: Math.round(settings.size.x * settings.resolutionScale),
|
|
42
|
+
y: Math.round(settings.size.y * settings.resolutionScale),
|
|
43
|
+
};
|
|
44
|
+
this.command
|
|
45
|
+
.output(path.join(this.jobFolder, `visuals.${exports.extensions[this.format]}`))
|
|
46
|
+
.outputOptions([`-pix_fmt ${pixelFormats[this.format]}`, '-shortest'])
|
|
47
|
+
.outputFps(settings.fps)
|
|
48
|
+
.size(`${size.x}x${size.y}`);
|
|
49
|
+
if (this.format === 'proRes') {
|
|
50
|
+
this.command.outputOptions(['-c:v prores_ks', '-profile:v 4444']);
|
|
51
|
+
}
|
|
52
|
+
this.command.outputOptions(['-movflags +faststart']);
|
|
53
|
+
this.promise = new Promise((resolve, reject) => {
|
|
54
|
+
this.command.on('end', resolve).on('error', reject);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
async start() {
|
|
58
|
+
this.command.run();
|
|
59
|
+
}
|
|
60
|
+
async handleFrame({ data }) {
|
|
61
|
+
const base64Data = data.slice(data.indexOf(',') + 1);
|
|
62
|
+
this.stream.pushImage(Buffer.from(base64Data, 'base64'));
|
|
63
|
+
}
|
|
64
|
+
async end(result) {
|
|
65
|
+
this.stream.pushImage(null);
|
|
66
|
+
if (result === 1) {
|
|
67
|
+
try {
|
|
68
|
+
this.command.kill('SIGKILL');
|
|
69
|
+
await this.promise;
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
(0, telemetry_1.sendEvent)(telemetry_1.EventName.Error, { message: err.message });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
await this.promise;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
async kill() {
|
|
80
|
+
try {
|
|
81
|
+
this.command.kill('SIGKILL');
|
|
82
|
+
await this.promise;
|
|
83
|
+
}
|
|
84
|
+
catch (_) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
exports.FFmpegExporterServer = FFmpegExporterServer;
|
|
90
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmZtcGVnLWV4cG9ydGVyLXNlcnZlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9mZm1wZWctZXhwb3J0ZXItc2VydmVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUtBLGdEQUFzRDtBQUN0RCx3Q0FBd0M7QUFDeEMseUJBQXlCO0FBQ3pCLDZCQUE2QjtBQUM3QixpREFBMkM7QUFDM0MseUNBQTBDO0FBUTFDLE1BQU0sWUFBWSxHQUFvRDtJQUNwRSxHQUFHLEVBQUUsU0FBUztJQUNkLElBQUksRUFBRSxVQUFVO0lBQ2hCLE1BQU0sRUFBRSxjQUFjO0NBQ3ZCLENBQUM7QUFFVyxRQUFBLFVBQVUsR0FBb0Q7SUFDekUsR0FBRyxFQUFFLEtBQUs7SUFDVixJQUFJLEVBQUUsTUFBTTtJQUNaLE1BQU0sRUFBRSxLQUFLO0NBQ2QsQ0FBQztBQUVGOztHQUVHO0FBQ0gsTUFBYSxvQkFBb0I7SUFRL0IsWUFBbUIsUUFBZ0M7UUFDakQsSUFBSSxRQUFRLENBQUMsUUFBUSxDQUFDLElBQUksS0FBSyxvQkFBb0IsRUFBRSxDQUFDO1lBQ3BELE1BQU0sSUFBSSxLQUFLLENBQUMsa0JBQWtCLENBQUMsQ0FBQztRQUN0QyxDQUFDO1FBRUQsSUFBSSxDQUFDLFFBQVEsR0FBRyxRQUFRLENBQUM7UUFDekIsSUFBSSxDQUFDLE1BQU0sR0FBRyxRQUFRLENBQUMsUUFBUSxDQUFDLE9BQU8sQ0FBQyxNQUFNLENBQUM7UUFFL0MsSUFBSSxDQUFDLFNBQVMsR0FBRyxJQUFJLENBQUMsSUFBSSxDQUN4QixFQUFFLENBQUMsTUFBTSxFQUFFLEVBQ1gsU0FBUyxJQUFJLENBQUMsUUFBUSxDQUFDLElBQUksSUFBSSxRQUFRLENBQUMsY0FBYyxFQUFFLENBQ3pELENBQUM7UUFDRixJQUFJLENBQUMsTUFBTSxHQUFHLElBQUksMEJBQVcsRUFBRSxDQUFDO1FBRWhDLE1BQU0sQ0FBQyxhQUFhLENBQUMseUJBQWMsQ0FBQyxhQUFhLEVBQUUsQ0FBQyxDQUFDO1FBQ3JELElBQUksQ0FBQyxPQUFPLEdBQUcsTUFBTSxFQUFFLENBQUM7UUFFeEIsdUJBQXVCO1FBQ3ZCLElBQUksQ0FBQyxPQUFPO2FBQ1QsS0FBSyxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUM7YUFDbEIsV0FBVyxDQUFDLFlBQVksQ0FBQzthQUN6QixRQUFRLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBRTFCLGtCQUFrQjtRQUNsQixNQUFNLElBQUksR0FBRztZQUNYLENBQUMsRUFBRSxJQUFJLENBQUMsS0FBSyxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUMsQ0FBQyxHQUFHLFFBQVEsQ0FBQyxlQUFlLENBQUM7WUFDekQsQ0FBQyxFQUFFLElBQUksQ0FBQyxLQUFLLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxDQUFDLEdBQUcsUUFBUSxDQUFDLGVBQWUsQ0FBQztTQUMxRCxDQUFDO1FBQ0YsSUFBSSxDQUFDLE9BQU87YUFDVCxNQUFNLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsU0FBUyxFQUFFLFdBQVcsa0JBQVUsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLEVBQUUsQ0FBQyxDQUFDO2FBQ3ZFLGFBQWEsQ0FBQyxDQUFDLFlBQVksWUFBWSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsRUFBRSxFQUFFLFdBQVcsQ0FBQyxDQUFDO2FBQ3JFLFNBQVMsQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFDO2FBQ3ZCLElBQUksQ0FBQyxHQUFHLElBQUksQ0FBQyxDQUFDLElBQUksSUFBSSxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUM7UUFFL0IsSUFBSSxJQUFJLENBQUMsTUFBTSxLQUFLLFFBQVEsRUFBRSxDQUFDO1lBQzdCLElBQUksQ0FBQyxPQUFPLENBQUMsYUFBYSxDQUFDLENBQUMsZ0JBQWdCLEVBQUUsaUJBQWlCLENBQUMsQ0FBQyxDQUFDO1FBQ3BFLENBQUM7UUFFRCxJQUFJLENBQUMsT0FBTyxDQUFDLGFBQWEsQ0FBQyxDQUFDLHNCQUFzQixDQUFDLENBQUMsQ0FBQztRQUNyRCxJQUFJLENBQUMsT0FBTyxHQUFHLElBQUksT0FBTyxDQUFPLENBQUMsT0FBTyxFQUFFLE1BQU0sRUFBRSxFQUFFO1lBQ25ELElBQUksQ0FBQyxPQUFPLENBQUMsRUFBRSxDQUFDLEtBQUssRUFBRSxPQUFPLENBQUMsQ0FBQyxFQUFFLENBQUMsT0FBTyxFQUFFLE1BQU0sQ0FBQyxDQUFDO1FBQ3RELENBQUMsQ0FBQyxDQUFDO0lBQ0wsQ0FBQztJQUVNLEtBQUssQ0FBQyxLQUFLO1FBQ2hCLElBQUksQ0FBQyxPQUFPLENBQUMsR0FBRyxFQUFFLENBQUM7SUFDckIsQ0FBQztJQUVNLEtBQUssQ0FBQyxXQUFXLENBQUMsRUFBQyxJQUFJLEVBQWlCO1FBQzdDLE1BQU0sVUFBVSxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQztRQUNyRCxJQUFJLENBQUMsTUFBTSxDQUFDLFNBQVMsQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLFVBQVUsRUFBRSxRQUFRLENBQUMsQ0FBQyxDQUFDO0lBQzNELENBQUM7SUFFTSxLQUFLLENBQUMsR0FBRyxDQUFDLE1BQXNCO1FBQ3JDLElBQUksQ0FBQyxNQUFNLENBQUMsU0FBUyxDQUFDLElBQUksQ0FBQyxDQUFDO1FBQzVCLElBQUksTUFBTSxLQUFLLENBQUMsRUFBRSxDQUFDO1lBQ2pCLElBQUksQ0FBQztnQkFDSCxJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsQ0FBQztnQkFDN0IsTUFBTSxJQUFJLENBQUMsT0FBTyxDQUFDO1lBQ3JCLENBQUM7WUFBQyxPQUFPLEdBQUcsRUFBRSxDQUFDO2dCQUNiLElBQUEscUJBQVMsRUFBQyxxQkFBUyxDQUFDLEtBQUssRUFBRSxFQUFDLE9BQU8sRUFBRyxHQUFhLENBQUMsT0FBTyxFQUFDLENBQUMsQ0FBQztZQUNoRSxDQUFDO1FBQ0gsQ0FBQzthQUFNLENBQUM7WUFDTixNQUFNLElBQUksQ0FBQyxPQUFPLENBQUM7UUFDckIsQ0FBQztJQUNILENBQUM7SUFFTSxLQUFLLENBQUMsSUFBSTtRQUNmLElBQUksQ0FBQztZQUNILElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxDQUFDO1lBQzdCLE1BQU0sSUFBSSxDQUFDLE9BQU8sQ0FBQztRQUNyQixDQUFDO1FBQUMsT0FBTyxDQUFDLEVBQUUsQ0FBQztZQUNYLE9BQU87UUFDVCxDQUFDO0lBQ0gsQ0FBQztDQUNGO0FBbkZELG9EQW1GQyJ9
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { AssetInfo, FfmpegExporterOptions } from '@twick/core';
|
|
2
|
+
import type { AudioCodec } from './utils';
|
|
3
|
+
export declare const audioCodecs: Record<FfmpegExporterOptions['format'], AudioCodec>;
|
|
4
|
+
export declare function generateAudio({ outputDir, tempDir, assets, startFrame, endFrame, fps, }: {
|
|
5
|
+
outputDir: string;
|
|
6
|
+
tempDir: string;
|
|
7
|
+
assets: AssetInfo[][];
|
|
8
|
+
startFrame: number;
|
|
9
|
+
endFrame: number;
|
|
10
|
+
fps: number;
|
|
11
|
+
}): Promise<string[]>;
|
|
12
|
+
export declare function mergeMedia(outputFilename: string, outputDir: string, tempDir: string, format: FfmpegExporterOptions['format']): Promise<void>;
|
|
13
|
+
//# sourceMappingURL=generate-audio.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate-audio.d.ts","sourceRoot":"","sources":["../src/generate-audio.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,SAAS,EAAE,qBAAqB,EAAC,MAAM,aAAa,CAAC;AAOlE,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,SAAS,CAAC;AASxC,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,qBAAqB,CAAC,QAAQ,CAAC,EAAE,UAAU,CAKzE,CAAC;AAkMJ,wBAAsB,aAAa,CAAC,EAClC,SAAS,EACT,OAAO,EACP,MAAM,EACN,UAAU,EACV,QAAQ,EACR,GAAG,GACJ,EAAE;IACD,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,SAAS,EAAE,EAAE,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;CACb,qBAkCA;AAED,wBAAsB,UAAU,CAC9B,cAAc,EAAE,MAAM,EACtB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,qBAAqB,CAAC,QAAQ,CAAC,iBA6BxC"}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.audioCodecs = void 0;
|
|
4
|
+
exports.generateAudio = generateAudio;
|
|
5
|
+
exports.mergeMedia = mergeMedia;
|
|
6
|
+
const ffmpeg = require("fluent-ffmpeg");
|
|
7
|
+
const fs = require("fs");
|
|
8
|
+
const os = require("os");
|
|
9
|
+
const path = require("path");
|
|
10
|
+
const ffmpeg_exporter_server_1 = require("./ffmpeg-exporter-server");
|
|
11
|
+
const settings_1 = require("./settings");
|
|
12
|
+
const utils_1 = require("./utils");
|
|
13
|
+
exports.audioCodecs = {
|
|
14
|
+
mp4: 'aac',
|
|
15
|
+
webm: 'libopus',
|
|
16
|
+
proRes: 'aac',
|
|
17
|
+
};
|
|
18
|
+
const SAMPLE_RATE = 48000;
|
|
19
|
+
function getAssetPlacement(frames) {
|
|
20
|
+
const assets = [];
|
|
21
|
+
// A map to keep track of the first and last currentTime for each asset.
|
|
22
|
+
const assetTimeMap = new Map();
|
|
23
|
+
for (let frame = 0; frame < frames.length; frame++) {
|
|
24
|
+
for (const asset of frames[frame]) {
|
|
25
|
+
if (!assetTimeMap.has(asset.key)) {
|
|
26
|
+
// If the asset is not in the map, add it with its current time as both start and end.
|
|
27
|
+
assetTimeMap.set(asset.key, {
|
|
28
|
+
start: asset.currentTime,
|
|
29
|
+
end: asset.currentTime,
|
|
30
|
+
});
|
|
31
|
+
assets.push({
|
|
32
|
+
key: asset.key,
|
|
33
|
+
src: asset.src,
|
|
34
|
+
type: asset.type,
|
|
35
|
+
startInVideo: frame,
|
|
36
|
+
endInVideo: frame,
|
|
37
|
+
duration: 0, // Placeholder, will be recalculated later based on frames
|
|
38
|
+
durationInSeconds: 0, // Placeholder, will be calculated based on currentTime
|
|
39
|
+
playbackRate: asset.playbackRate,
|
|
40
|
+
volume: asset.volume,
|
|
41
|
+
trimLeftInSeconds: asset.currentTime,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
// If the asset is already in the map, update the end time.
|
|
46
|
+
const timeInfo = assetTimeMap.get(asset.key);
|
|
47
|
+
if (timeInfo) {
|
|
48
|
+
timeInfo.end = asset.currentTime;
|
|
49
|
+
assetTimeMap.set(asset.key, timeInfo);
|
|
50
|
+
}
|
|
51
|
+
const existingAsset = assets.find(a => a.key === asset.key);
|
|
52
|
+
if (existingAsset) {
|
|
53
|
+
existingAsset.endInVideo = frame;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// Calculate the duration based on frame count and durationInSeconds based on currentTime.
|
|
59
|
+
assets.forEach(asset => {
|
|
60
|
+
const timeInfo = assetTimeMap.get(asset.key);
|
|
61
|
+
if (timeInfo) {
|
|
62
|
+
// Calculate durationInSeconds based on the start and end currentTime values.
|
|
63
|
+
asset.durationInSeconds =
|
|
64
|
+
(timeInfo.end - timeInfo.start) / asset.playbackRate;
|
|
65
|
+
}
|
|
66
|
+
// Recalculate the original duration based on frame count.
|
|
67
|
+
asset.duration = asset.endInVideo - asset.startInVideo + 1;
|
|
68
|
+
});
|
|
69
|
+
return assets;
|
|
70
|
+
}
|
|
71
|
+
function calculateAtempoFilters(playbackRate) {
|
|
72
|
+
const atempoFilters = [];
|
|
73
|
+
// Calculate how many times we need to 100x the speed
|
|
74
|
+
let rate = playbackRate;
|
|
75
|
+
while (rate > 100.0) {
|
|
76
|
+
atempoFilters.push('atempo=100.0');
|
|
77
|
+
rate /= 100.0;
|
|
78
|
+
}
|
|
79
|
+
// Add the last atempo filter with the remaining rate
|
|
80
|
+
if (rate > 1.0) {
|
|
81
|
+
atempoFilters.push(`atempo=${rate}`);
|
|
82
|
+
}
|
|
83
|
+
// Calculate how many times we need to halve the speed
|
|
84
|
+
rate = playbackRate;
|
|
85
|
+
while (rate < 0.5) {
|
|
86
|
+
atempoFilters.push('atempo=0.5');
|
|
87
|
+
rate *= 2.0;
|
|
88
|
+
}
|
|
89
|
+
// Add the last atempo filter with the remaining rate
|
|
90
|
+
if (rate < 1.0) {
|
|
91
|
+
atempoFilters.push(`atempo=${rate}`);
|
|
92
|
+
}
|
|
93
|
+
return atempoFilters;
|
|
94
|
+
}
|
|
95
|
+
async function prepareAudio(outputDir, tempDir, asset, startFrame, endFrame, fps) {
|
|
96
|
+
// Construct the output path
|
|
97
|
+
const sanitizedKey = asset.key.replace(/[/[\]]/g, '-');
|
|
98
|
+
const outputPath = path.join(tempDir, `${sanitizedKey}.wav`);
|
|
99
|
+
const trimLeft = asset.trimLeftInSeconds / asset.playbackRate;
|
|
100
|
+
const trimRight = 1 / fps +
|
|
101
|
+
Math.min(trimLeft + asset.durationInSeconds, trimLeft + (endFrame - startFrame) / fps);
|
|
102
|
+
const padStart = (asset.startInVideo / fps) * 1000;
|
|
103
|
+
const assetSampleRate = await (0, utils_1.getSampleRate)((0, utils_1.resolvePath)(outputDir, asset.src));
|
|
104
|
+
const padEnd = Math.max(0, (assetSampleRate * (endFrame - startFrame + 1)) / fps -
|
|
105
|
+
(assetSampleRate * asset.duration) / fps -
|
|
106
|
+
(assetSampleRate * padStart) / 1000);
|
|
107
|
+
const atempoFilters = calculateAtempoFilters(asset.playbackRate); // atempo filter value must be >=0.5 and <=100. If the value is higher or lower, this function sets multiple atempo filters
|
|
108
|
+
const resolvedPath = (0, utils_1.resolvePath)(outputDir, asset.src);
|
|
109
|
+
await new Promise((resolve, reject) => {
|
|
110
|
+
const audioFilters = [
|
|
111
|
+
...atempoFilters,
|
|
112
|
+
`atrim=start=${trimLeft}:end=${trimRight}`,
|
|
113
|
+
`apad=pad_len=${padEnd}`,
|
|
114
|
+
`adelay=${padStart}|${padStart}|${padStart}`,
|
|
115
|
+
`volume=${asset.volume}`,
|
|
116
|
+
].join(',');
|
|
117
|
+
ffmpeg.setFfmpegPath(settings_1.ffmpegSettings.getFfmpegPath());
|
|
118
|
+
ffmpeg(resolvedPath)
|
|
119
|
+
.audioChannels(2)
|
|
120
|
+
.audioCodec('pcm_s16le')
|
|
121
|
+
.audioFrequency(SAMPLE_RATE)
|
|
122
|
+
.outputOptions([`-af`, audioFilters])
|
|
123
|
+
.on('end', () => {
|
|
124
|
+
resolve();
|
|
125
|
+
})
|
|
126
|
+
.on('error', err => {
|
|
127
|
+
console.error(`Error processing audio for asset key: ${asset.key}`, err);
|
|
128
|
+
reject(err);
|
|
129
|
+
})
|
|
130
|
+
.save(outputPath);
|
|
131
|
+
});
|
|
132
|
+
return outputPath;
|
|
133
|
+
}
|
|
134
|
+
async function mergeAudioTracks(tempDir, audioFilenames) {
|
|
135
|
+
return new Promise((resolve, reject) => {
|
|
136
|
+
ffmpeg.setFfmpegPath(settings_1.ffmpegSettings.getFfmpegPath());
|
|
137
|
+
const command = ffmpeg();
|
|
138
|
+
audioFilenames.forEach(filename => {
|
|
139
|
+
command.input(filename);
|
|
140
|
+
});
|
|
141
|
+
command
|
|
142
|
+
.complexFilter([
|
|
143
|
+
`amix=inputs=${audioFilenames.length}:duration=longest,volume=${audioFilenames.length}`,
|
|
144
|
+
])
|
|
145
|
+
.outputOptions(['-c:a', 'pcm_s16le'])
|
|
146
|
+
.on('end', () => {
|
|
147
|
+
resolve();
|
|
148
|
+
})
|
|
149
|
+
.on('error', err => {
|
|
150
|
+
console.error(`Error merging audio tracks: ${err.message}`);
|
|
151
|
+
reject(err);
|
|
152
|
+
})
|
|
153
|
+
.save(path.join(tempDir, `audio.wav`));
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
async function generateAudio({ outputDir, tempDir, assets, startFrame, endFrame, fps, }) {
|
|
157
|
+
const fullTempDir = path.join(os.tmpdir(), tempDir);
|
|
158
|
+
await (0, utils_1.makeSureFolderExists)(outputDir);
|
|
159
|
+
await (0, utils_1.makeSureFolderExists)(fullTempDir);
|
|
160
|
+
const assetPositions = getAssetPlacement(assets);
|
|
161
|
+
const audioFilenames = [];
|
|
162
|
+
for (const asset of assetPositions) {
|
|
163
|
+
let hasAudioStream = true;
|
|
164
|
+
if (asset.type !== 'audio') {
|
|
165
|
+
hasAudioStream = await (0, utils_1.checkForAudioStream)((0, utils_1.resolvePath)(outputDir, asset.src));
|
|
166
|
+
}
|
|
167
|
+
if (asset.playbackRate !== 0 && asset.volume !== 0 && hasAudioStream) {
|
|
168
|
+
const filename = await prepareAudio(outputDir, fullTempDir, asset, startFrame, endFrame, fps);
|
|
169
|
+
audioFilenames.push(filename);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (audioFilenames.length > 0) {
|
|
173
|
+
await mergeAudioTracks(fullTempDir, audioFilenames);
|
|
174
|
+
}
|
|
175
|
+
return audioFilenames;
|
|
176
|
+
}
|
|
177
|
+
async function mergeMedia(outputFilename, outputDir, tempDir, format) {
|
|
178
|
+
const fullTempDir = path.join(os.tmpdir(), tempDir);
|
|
179
|
+
await (0, utils_1.makeSureFolderExists)(outputDir);
|
|
180
|
+
await (0, utils_1.makeSureFolderExists)(fullTempDir);
|
|
181
|
+
const audioWavExists = fs.existsSync(path.join(fullTempDir, `audio.wav`));
|
|
182
|
+
if (audioWavExists) {
|
|
183
|
+
await (0, utils_1.mergeAudioWithVideo)(path.join(fullTempDir, `audio.wav`), path.join(fullTempDir, `visuals.${ffmpeg_exporter_server_1.extensions[format]}`), path.join(outputDir, `${outputFilename}.${ffmpeg_exporter_server_1.extensions[format]}`), exports.audioCodecs[format]);
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
const destination = path.join(outputDir, `${outputFilename}.${ffmpeg_exporter_server_1.extensions[format]}`);
|
|
187
|
+
await fs.promises.copyFile(path.join(fullTempDir, `visuals.${ffmpeg_exporter_server_1.extensions[format]}`), destination);
|
|
188
|
+
}
|
|
189
|
+
if (fullTempDir.endsWith('-undefined')) {
|
|
190
|
+
await fs.promises
|
|
191
|
+
.rm(fullTempDir, { recursive: true, force: true })
|
|
192
|
+
.catch(() => { });
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZ2VuZXJhdGUtYXVkaW8uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvZ2VuZXJhdGUtYXVkaW8udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBdU5BLHNDQWdEQztBQUVELGdDQWlDQztBQXpTRCx3Q0FBd0M7QUFDeEMseUJBQXlCO0FBQ3pCLHlCQUF5QjtBQUN6Qiw2QkFBNkI7QUFDN0IscUVBQW9EO0FBQ3BELHlDQUEwQztBQUUxQyxtQ0FNaUI7QUFFSixRQUFBLFdBQVcsR0FDdEI7SUFDRSxHQUFHLEVBQUUsS0FBSztJQUNWLElBQUksRUFBRSxTQUFTO0lBQ2YsTUFBTSxFQUFFLEtBQUs7Q0FDZCxDQUFDO0FBZUosTUFBTSxXQUFXLEdBQUcsS0FBSyxDQUFDO0FBRTFCLFNBQVMsaUJBQWlCLENBQUMsTUFBcUI7SUFDOUMsTUFBTSxNQUFNLEdBQWlCLEVBQUUsQ0FBQztJQUVoQyx3RUFBd0U7SUFDeEUsTUFBTSxZQUFZLEdBQUcsSUFBSSxHQUFHLEVBQXdDLENBQUM7SUFFckUsS0FBSyxJQUFJLEtBQUssR0FBRyxDQUFDLEVBQUUsS0FBSyxHQUFHLE1BQU0sQ0FBQyxNQUFNLEVBQUUsS0FBSyxFQUFFLEVBQUUsQ0FBQztRQUNuRCxLQUFLLE1BQU0sS0FBSyxJQUFJLE1BQU0sQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDO1lBQ2xDLElBQUksQ0FBQyxZQUFZLENBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDO2dCQUNqQyxzRkFBc0Y7Z0JBQ3RGLFlBQVksQ0FBQyxHQUFHLENBQUMsS0FBSyxDQUFDLEdBQUcsRUFBRTtvQkFDMUIsS0FBSyxFQUFFLEtBQUssQ0FBQyxXQUFXO29CQUN4QixHQUFHLEVBQUUsS0FBSyxDQUFDLFdBQVc7aUJBQ3ZCLENBQUMsQ0FBQztnQkFDSCxNQUFNLENBQUMsSUFBSSxDQUFDO29CQUNWLEdBQUcsRUFBRSxLQUFLLENBQUMsR0FBRztvQkFDZCxHQUFHLEVBQUUsS0FB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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-stream.d.ts","sourceRoot":"","sources":["../src/image-stream.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,QAAQ,EAAC,MAAM,QAAQ,CAAC;AAEhC,qBAAa,WAAY,SAAQ,QAAQ;IACvC,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,OAAO,CAAS;IAEjB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAOrB,KAAK;CAMtB"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ImageStream = void 0;
|
|
4
|
+
const stream_1 = require("stream");
|
|
5
|
+
class ImageStream extends stream_1.Readable {
|
|
6
|
+
constructor() {
|
|
7
|
+
super(...arguments);
|
|
8
|
+
this.image = null;
|
|
9
|
+
this.hasData = false;
|
|
10
|
+
}
|
|
11
|
+
pushImage(image) {
|
|
12
|
+
this.image = image;
|
|
13
|
+
this.hasData = true;
|
|
14
|
+
this._read();
|
|
15
|
+
}
|
|
16
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
17
|
+
_read() {
|
|
18
|
+
if (this.hasData) {
|
|
19
|
+
this.hasData = false;
|
|
20
|
+
this.push(this.image);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
exports.ImageStream = ImageStream;
|
|
25
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW1hZ2Utc3RyZWFtLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2ltYWdlLXN0cmVhbS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSxtQ0FBZ0M7QUFFaEMsTUFBYSxXQUFZLFNBQVEsaUJBQVE7SUFBekM7O1FBQ1UsVUFBSyxHQUFrQixJQUFJLENBQUM7UUFDNUIsWUFBTyxHQUFHLEtBQUssQ0FBQztJQWUxQixDQUFDO0lBYlEsU0FBUyxDQUFDLEtBQW9CO1FBQ25DLElBQUksQ0FBQyxLQUFLLEdBQUcsS0FBSyxDQUFDO1FBQ25CLElBQUksQ0FBQyxPQUFPLEdBQUcsSUFBSSxDQUFDO1FBQ3BCLElBQUksQ0FBQyxLQUFLLEVBQUUsQ0FBQztJQUNmLENBQUM7SUFFRCxnRUFBZ0U7SUFDaEQsS0FBSztRQUNuQixJQUFJLElBQUksQ0FBQyxPQUFPLEVBQUUsQ0FBQztZQUNqQixJQUFJLENBQUMsT0FBTyxHQUFHLEtBQUssQ0FBQztZQUNyQixJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUN4QixDQUFDO0lBQ0gsQ0FBQztDQUNGO0FBakJELGtDQWlCQyJ9
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,0BAA0B,CAAC;AACzC,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC;AAC3B,cAAc,SAAS,CAAC;AACxB,cAAc,yBAAyB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./ffmpeg-exporter-server"), exports);
|
|
18
|
+
__exportStar(require("./generate-audio"), exports);
|
|
19
|
+
__exportStar(require("./settings"), exports);
|
|
20
|
+
__exportStar(require("./utils"), exports);
|
|
21
|
+
__exportStar(require("./video-frame-extractor"), exports);
|
|
22
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7OztBQUFBLDJEQUF5QztBQUN6QyxtREFBaUM7QUFDakMsNkNBQTJCO0FBQzNCLDBDQUF3QjtBQUN4QiwwREFBd0MifQ==
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
declare const ffmpegLogLevels: readonly ["quiet", "panic", "fatal", "error", "warning", "info", "verbose", "debug", "trace"];
|
|
2
|
+
export type LogLevel = (typeof ffmpegLogLevels)[number];
|
|
3
|
+
export type FfmpegSettings = {
|
|
4
|
+
ffmpegPath?: string;
|
|
5
|
+
ffprobePath?: string;
|
|
6
|
+
ffmpegLogLevel?: LogLevel;
|
|
7
|
+
};
|
|
8
|
+
declare class FfmpegSettingState {
|
|
9
|
+
private ffmpegPath;
|
|
10
|
+
private ffprobePath;
|
|
11
|
+
private logLevel;
|
|
12
|
+
constructor();
|
|
13
|
+
getFfmpegPath(): string;
|
|
14
|
+
setFfmpegPath(ffmpegPath: string): void;
|
|
15
|
+
getFfprobePath(): string;
|
|
16
|
+
setFfprobePath(ffprobePath: string): void;
|
|
17
|
+
getLogLevel(): "error" | "info" | "verbose" | "debug" | "warning" | "quiet" | "panic" | "fatal" | "trace";
|
|
18
|
+
setLogLevel(logLevel: LogLevel): void;
|
|
19
|
+
}
|
|
20
|
+
export declare const ffmpegSettings: FfmpegSettingState;
|
|
21
|
+
export {};
|
|
22
|
+
//# sourceMappingURL=settings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"AAGA,QAAA,MAAM,eAAe,+FAUX,CAAC;AAEX,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AAExD,MAAM,MAAM,cAAc,GAAG;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,QAAQ,CAAC;CAC3B,CAAC;AAEF,cAAM,kBAAkB;IACtB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,QAAQ,CAAW;;IA2BpB,aAAa;IAIb,aAAa,CAAC,UAAU,EAAE,MAAM;IAIhC,cAAc;IAId,cAAc,CAAC,WAAW,EAAE,MAAM;IAIlC,WAAW;IAIX,WAAW,CAAC,QAAQ,EAAE,QAAQ;CAGtC;AAED,eAAO,MAAM,cAAc,oBAA2B,CAAC"}
|
package/dist/settings.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ffmpegSettings = void 0;
|
|
4
|
+
const ffmpegInstaller = require("@ffmpeg-installer/ffmpeg");
|
|
5
|
+
const ffprobeInstaller = require("@ffprobe-installer/ffprobe");
|
|
6
|
+
const ffmpegLogLevels = [
|
|
7
|
+
'quiet',
|
|
8
|
+
'panic',
|
|
9
|
+
'fatal',
|
|
10
|
+
'error',
|
|
11
|
+
'warning',
|
|
12
|
+
'info',
|
|
13
|
+
'verbose',
|
|
14
|
+
'debug',
|
|
15
|
+
'trace',
|
|
16
|
+
];
|
|
17
|
+
class FfmpegSettingState {
|
|
18
|
+
constructor() {
|
|
19
|
+
this.ffmpegPath = ffmpegInstaller.path;
|
|
20
|
+
this.ffprobePath = ffprobeInstaller.path;
|
|
21
|
+
// Use the FFMPEG_PATH environment variable if it is set
|
|
22
|
+
if (process.env.FFMPEG_PATH) {
|
|
23
|
+
this.ffmpegPath = process.env.FFMPEG_PATH;
|
|
24
|
+
}
|
|
25
|
+
// Use the FFPROBE_PATH environment variable if it is set
|
|
26
|
+
if (process.env.FFPROBE_PATH) {
|
|
27
|
+
this.ffprobePath = process.env.FFPROBE_PATH;
|
|
28
|
+
}
|
|
29
|
+
this.logLevel = 'error';
|
|
30
|
+
// Use the FFMPEG_LOG_LEVEL environment variable if it is set
|
|
31
|
+
if (process.env.FFMPEG_LOG_LEVEL &&
|
|
32
|
+
ffmpegLogLevels.includes(process.env.FFMPEG_LOG_LEVEL)) {
|
|
33
|
+
this.logLevel = process.env.FFMPEG_LOG_LEVEL;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
getFfmpegPath() {
|
|
37
|
+
return this.ffmpegPath;
|
|
38
|
+
}
|
|
39
|
+
setFfmpegPath(ffmpegPath) {
|
|
40
|
+
this.ffmpegPath = ffmpegPath;
|
|
41
|
+
}
|
|
42
|
+
getFfprobePath() {
|
|
43
|
+
return this.ffprobePath;
|
|
44
|
+
}
|
|
45
|
+
setFfprobePath(ffprobePath) {
|
|
46
|
+
this.ffprobePath = ffprobePath;
|
|
47
|
+
}
|
|
48
|
+
getLogLevel() {
|
|
49
|
+
return this.logLevel;
|
|
50
|
+
}
|
|
51
|
+
setLogLevel(logLevel) {
|
|
52
|
+
this.logLevel = logLevel;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
exports.ffmpegSettings = new FfmpegSettingState();
|
|
56
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2V0dGluZ3MuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvc2V0dGluZ3MudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQUEsNERBQTREO0FBQzVELCtEQUErRDtBQUUvRCxNQUFNLGVBQWUsR0FBRztJQUN0QixPQUFPO0lBQ1AsT0FBTztJQUNQLE9BQU87SUFDUCxPQUFPO0lBQ1AsU0FBUztJQUNULE1BQU07SUFDTixTQUFTO0lBQ1QsT0FBTztJQUNQLE9BQU87Q0FDQyxDQUFDO0FBVVgsTUFBTSxrQkFBa0I7SUFLdEI7UUFDRSxJQUFJLENBQUMsVUFBVSxHQUFHLGVBQWUsQ0FBQyxJQUF5QixDQUFDO1FBQzVELElBQUksQ0FBQyxXQUFXLEdBQUcsZ0JBQWdCLENBQUMsSUFBeUIsQ0FBQztRQUU5RCx3REFBd0Q7UUFDeEQsSUFBSSxPQUFPLENBQUMsR0FBRyxDQUFDLFdBQVcsRUFBRSxDQUFDO1lBQzVCLElBQUksQ0FBQyxVQUFVLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxXQUFXLENBQUM7UUFDNUMsQ0FBQztRQUVELHlEQUF5RDtRQUN6RCxJQUFJLE9BQU8sQ0FBQyxHQUFHLENBQUMsWUFBWSxFQUFFLENBQUM7WUFDN0IsSUFBSSxDQUFDLFdBQVcsR0FBRyxPQUFPLENBQUMsR0FBRyxDQUFDLFlBQVksQ0FBQztRQUM5QyxDQUFDO1FBRUQsSUFBSSxDQUFDLFFBQVEsR0FBRyxPQUFPLENBQUM7UUFFeEIsNkRBQTZEO1FBQzdELElBQ0UsT0FBTyxDQUFDLEdBQUcsQ0FBQyxnQkFBZ0I7WUFDNUIsZUFBZSxDQUFDLFFBQVEsQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLGdCQUE0QixDQUFDLEVBQ2xFLENBQUM7WUFDRCxJQUFJLENBQUMsUUFBUSxHQUFHLE9BQU8sQ0FBQyxHQUFHLENBQUMsZ0JBQTRCLENBQUM7UUFDM0QsQ0FBQztJQUNILENBQUM7SUFFTSxhQUFhO1FBQ2xCLE9BQU8sSUFBSSxDQUFDLFVBQVUsQ0FBQztJQUN6QixDQUFDO0lBRU0sYUFBYSxDQUFDLFVBQWtCO1FBQ3JDLElBQUksQ0FBQyxVQUFVLEdBQUcsVUFBVSxDQUFDO0lBQy9CLENBQUM7SUFFTSxjQUFjO1FBQ25CLE9BQU8sSUFBSSxDQUFDLFdBQVcsQ0FBQztJQUMxQixDQUFDO0lBRU0sY0FBYyxDQUFDLFdBQW1CO1FBQ3ZDLElBQUksQ0FBQyxXQUFXLEdBQUcsV0FBVyxDQUFDO0lBQ2pDLENBQUM7SUFFTSxXQUFXO1FBQ2hCLE9BQU8sSUFBSSxDQUFDLFFBQVEsQ0FBQztJQUN2QixDQUFDO0lBRU0sV0FBVyxDQUFDLFFBQWtCO1FBQ25DLElBQUksQ0FBQyxRQUFRLEdBQUcsUUFBUSxDQUFDO0lBQzNCLENBQUM7Q0FDRjtBQUVZLFFBQUEsY0FBYyxHQUFHLElBQUksa0JBQWtCLEVBQUUsQ0FBQyJ9
|