├── .gitignore
├── .project
├── Gruntfile.js
├── LICENSE
├── README.md
├── demos
├── audio_extract.js
├── ffmpeg_test.js
├── image_extract.js
├── temp.js
├── video_faststart.js
├── video_info.js
├── video_info_raw.js
├── video_playlist.js
├── video_to_gif.js
├── video_transcode.js
├── video_transcode_to_avi.js
├── video_transcode_to_mp4_av1.js
└── video_watermark.js
├── index.js
├── jsonize.js
├── package.json
├── src
├── ffmpeg-faststart.js
├── ffmpeg-graceful.js
├── ffmpeg-helpers.js
├── ffmpeg-multi-pass.js
├── ffmpeg-playlist.js
├── ffmpeg-simple.js
├── ffmpeg-test.js
├── ffmpeg-volume-detect.js
├── ffmpeg.js
├── ffprobe-simple.js
└── ffprobe.js
├── tests
├── assets
│ ├── audio.mp3
│ ├── etc_passwd_xbin.avi
│ ├── iphone_rotated.mov
│ ├── logo.png
│ ├── novideo.mp4
│ └── video-640-360.mp4
└── tests
│ ├── ffmpeg-simple-gif.js
│ ├── ffmpeg-simple-image.js
│ ├── ffmpeg-simple-rotation.js
│ ├── ffmpeg-simple-sizing.js
│ ├── ffmpeg-simple-watermarks.js
│ ├── ffmpeg-volume-detect.js
│ ├── ffmpeg.js
│ ├── ffprobe-simple.js
│ ├── ffprobe.js
│ └── settings.js
└── types
├── betajs.d.ts
├── ffmpeg-faststart.d.ts
├── ffmpeg-graceful.d.ts
├── ffmpeg-multi-pass.d.ts
├── ffmpeg-playlist.d.ts
├── ffmpeg-simple.d.ts
├── ffmpeg-test.d.ts
├── ffmpeg-volume-detect.d.ts
├── ffmpeg.d.ts
├── ffprobe-simple.d.ts
├── ffprobe.d.ts
├── index.d.ts
└── opts.d.ts
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | temp
3 | npm-debug.log
4 | .idea
5 | tmp
6 | package-lock.json
7 | .DS_Store
8 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | JS FFMpeg
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | module.exports = function(grunt) {
2 |
3 | grunt.initConfig({
4 | pkg : grunt.file.readJSON('package.json'),
5 | shell : {
6 | qunit : {
7 | command: ["node_modules/qunitjs/bin/qunit"].concat(["index.js", "tests/tests/*.js"]).join(" "),
8 | options: {
9 | stdout: true,
10 | stderr: true
11 | },
12 | src: [
13 | "index.js", "tests/tests/*.js"
14 | ]
15 | }
16 | },
17 | jshint : {
18 | options : {
19 | esversion: 6
20 | },
21 | source : [ "./Gruntfile.js", "./index.js", "./tests/tests/*.js",
22 | "./src/*.js" ]
23 | }
24 | });
25 |
26 | grunt.loadNpmTasks('grunt-shell');
27 | grunt.loadNpmTasks('grunt-contrib-jshint');
28 |
29 | grunt.registerTask('default', [ 'jshint', 'shell:qunit' ]);
30 |
31 | };
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) Jsonize. All rights reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # js-ffmpeg
2 |
3 | This is a simple wrapper for FFMPEG and FFPROBE.
4 |
5 |
6 | ## Getting Started
7 |
8 |
9 | ```javascript
10 | git clone https://github.com/jsonize/js-ffmpeg.git
11 | npm install
12 | grunt
13 | ```
14 |
15 |
16 |
17 | ## Basic Usage
18 |
19 |
20 | ```javascript
21 | ffmpeg = require('js-ffmpeg');
22 |
23 | // raw call of ffprobe
24 | ffmpeg.ffprobe('video.mp4').success(function (json) {
25 | console.log(json);
26 | }).error(function (error) {
27 | console.log(error);
28 | });
29 |
30 | // improved and simplified values and errors
31 | ffmpeg.ffprobe_simple('video.mp4').success(function (json) {
32 | console.log(json);
33 | }).error(function (error) {
34 | console.log(error);
35 | });
36 |
37 | // raw call of ffmpeg (source(s), arguments, target, progress callback)
38 | ffmpeg.ffmpeg('video.mp4', [...], 'output.mp4', function (progress) {
39 | console.log(progress);
40 | }).success(function (json) {
41 | console.log(json);
42 | }).error(function (error) {
43 | console.log(error);
44 | });
45 |
46 | // improved and simplified call of ffmpeg (source(s), arguments, target, progress callback)
47 | ffmpeg.ffmpeg_simple('video.mp4', {
48 | width: 640,
49 | height: 360,
50 | auto_rotate: true,
51 | ratio_strategy: "fixed",
52 | shrink_strategy: "crop",
53 | mixed_strategy: "crop-pad",
54 | stretch_strategy: "pad"
55 | }, 'output.mp4', function (progress) {
56 | console.log(progress);
57 | }).success(function (json) {
58 | console.log(json);
59 | }).error(function (error) {
60 | console.log(error);
61 | });
62 | ```
63 |
64 |
65 | ## Contributors
66 |
67 | - Ziggeo
68 | - Oliver Friedmann
69 |
70 |
71 | ## License
72 |
73 | Apache-2.0
74 |
75 |
--------------------------------------------------------------------------------
/demos/audio_extract.js:
--------------------------------------------------------------------------------
1 | var jsffmpeg = require(__dirname + "/../index.js");
2 |
3 | var args = require('node-getopt').create([
4 | ["", "source=FILE", "source video"],
5 | ["", "target=FILE", "target audio"],
6 | ["", "docker=CONTAINER", "docker"]
7 | ]).bindHelp().parseSystem().options;
8 |
9 | jsffmpeg.ffmpeg_simple(args.source, {
10 | output_type: "audio"
11 | }, args.target, null, null, {
12 | docker: args.docker,
13 | test_ffmpeg: true
14 | }).success(function (data) {
15 | console.log(data);
16 | }).error(function (error) {
17 | console.log(error);
18 | });
--------------------------------------------------------------------------------
/demos/ffmpeg_test.js:
--------------------------------------------------------------------------------
1 | var jsffmpeg = require(__dirname + "/../index.js");
2 |
3 | var args = require('node-getopt').create([
4 | ["", "docker=CONTAINER", "docker"]
5 | ]).bindHelp().parseSystem().options;
6 |
7 | jsffmpeg.ffmpeg_test({
8 | docker: args.docker
9 | }).success(function (data) {
10 | console.log(data);
11 | }).error(function (error) {
12 | console.log(error);
13 | });
--------------------------------------------------------------------------------
/demos/image_extract.js:
--------------------------------------------------------------------------------
1 | var jsffmpeg = require(__dirname + "/../index.js");
2 |
3 | var args = require('node-getopt').create([
4 | ["", "source=FILE", "source video"],
5 | ["", "target=FILE", "target image"],
6 | ["", "docker=CONTAINER", "docker"]
7 | ]).bindHelp().parseSystem().options;
8 |
9 | jsffmpeg.ffmpeg_simple(args.source, {
10 | output_type: "image",
11 | image_percentage: 0.5
12 | }, args.target, null, null, {
13 | docker: args.docker,
14 | test_ffmpeg: true
15 | }).success(function (data) {
16 | console.log(data);
17 | }).error(function (error) {
18 | console.log(error);
19 | });
--------------------------------------------------------------------------------
/demos/temp.js:
--------------------------------------------------------------------------------
1 | var jsffmpeg = require(__dirname + "/../index.js");
2 |
3 | var args = require('node-getopt').create([
4 | ["", "source=FILE", "source video"],
5 | ["", "target=FILE", "target video"],
6 | ["", "docker=CONTAINER", "docker"]
7 | ]).bindHelp().parseSystem().options;
8 |
9 | jsffmpeg.ffmpeg_simple(args.source, {
10 | width: 1280,
11 | height: 720,
12 | ratio_strategy: "stretch",
13 | size_strategy: "keep",
14 | shrink_strategy: "shrink-pad",
15 | stretch_strategy: "stretch-pad",
16 | mixed_strategy: "shrink-pad"
17 | }, args.target, null, null, {
18 | docker: args.docker,
19 | test_ffmpeg: true
20 | }).success(function (data) {
21 | console.log(data);
22 | }).error(function (error) {
23 | console.log(error);
24 | });
--------------------------------------------------------------------------------
/demos/video_faststart.js:
--------------------------------------------------------------------------------
1 | var jsffmpeg = require(__dirname + "/../index.js");
2 |
3 | var args = require('node-getopt').create([
4 | ["", "source=FILE", "source video"],
5 | ["", "target=FILE", "target video"]
6 | ]).bindHelp().parseSystem().options;
7 |
8 | jsffmpeg.ffmpeg_faststart(args.source, args.target, null, null, {
9 | /*
10 | docker: {
11 | "container" : "jrottenberg/ffmpeg",
12 | "proxy": "localhost:1234",
13 | "replaceArguments": {
14 | "libfaac": "libfdk_aac",
15 | "^/var": "/private/var"
16 | },
17 | "preprocessFiles": {
18 | "chown": "USERNAME",
19 | "chmod": 666,
20 | "mkdirs": true
21 | },
22 | "postprocessFiles": {
23 | "chown": "daemon",
24 | "chmod": 666,
25 | "recoverChown": true,
26 | "recoverChmod": true
27 | }
28 | },
29 | "test_info" : {
30 | "capabilities" : {
31 | "auto_rotate" : true
32 | },
33 | "encoders" : ["libdfk_aac", "aac"]
34 | },
35 | */
36 | test_ffmpeg: true,
37 | timeout: args.timeout
38 | }).success(function (data) {
39 | console.log(data);
40 | }).error(function (error) {
41 | console.log(error);
42 | });
--------------------------------------------------------------------------------
/demos/video_info.js:
--------------------------------------------------------------------------------
1 | var jsffmpeg = require(__dirname + "/../index.js");
2 |
3 | var args = require('node-getopt').create([
4 | ["", "source=FILE", "source video"] ,
5 | ["", "docker=CONTAINER", "docker"]
6 | ]).bindHelp().parseSystem().options;
7 |
8 | jsffmpeg.ffprobe_simple(args.source, {
9 | docker: args.docker
10 | }).success(function (data) {
11 | console.log(data);
12 | }).error(function (error) {
13 | console.log(error);
14 | });
--------------------------------------------------------------------------------
/demos/video_info_raw.js:
--------------------------------------------------------------------------------
1 | var jsffmpeg = require(__dirname + "/../index.js");
2 |
3 | var args = require('node-getopt').create([
4 | ["", "source=FILE", "source video"] ,
5 | ["", "docker=CONTAINER", "docker"]
6 | ]).bindHelp().parseSystem().options;
7 |
8 | jsffmpeg.ffprobe(args.source, {
9 | docker: args.docker
10 | }).success(function (data) {
11 | console.log(data);
12 | }).error(function (error) {
13 | console.log(error);
14 | });
--------------------------------------------------------------------------------
/demos/video_playlist.js:
--------------------------------------------------------------------------------
1 | var jsffmpeg = require(__dirname + "/../index.js");
2 |
3 | var args = require("node-getopt").create([
4 | ["", "source=FILE", "source video"],
5 | ["", "target=FOLDER", "target folder"],
6 | ["", "renditions=OBJECT", "renditions object"],
7 | ["", "watermark=FILE", "watermark image"],
8 | ["", "docker=CONTAINER", "docker"],
9 | ["", "timeout=MS", "timeout"],
10 | ["", "ratiostrategy=STRATEGY", "ratio strategy", "fixed"],
11 | ["", "sizestrategy=STRATEGY", "size strategy", "keep"],
12 | ["", "shrinkstrategy=STRATEGY", "shrink strategy", "shrink-pad"],
13 | ["", "stretchstrategy=STRATEGY", "stretch strategy", "pad"],
14 | ["", "mixedstrategy=STRATEGY", "mixed strategy", "shrink-pad"]
15 | ]).bindHelp().parseSystem().options;
16 | const renditions = args.renditions || [
17 | {resolution: "640x360", bitrate: 800, audio_rate: 96},
18 | {resolution: "842x480", bitrate: 1400, audio_rate: 128},
19 | {resolution: "1280x720", bitrate: 2800, audio_rate: 128}
20 | ];
21 | jsffmpeg.ffmpeg_playlist(args.source, {
22 | watermark: args.watermark,
23 | renditions: renditions,
24 | //normalize_audio: true
25 | ratio_strategy: args.ratiostrategy,
26 | size_strategy: args.sizestrategy,
27 | shrink_strategy: args.shrinkstrategy,
28 | stretch_strategy: args.stretchstrategy,
29 | mixed_strategy: args.mixedstrategy
30 | }, args.target, null, null, {
31 | test_ffmpeg: true,
32 | timeout: args.timeout
33 | /*docker: {
34 | "container" : "jrottenberg/ffmpeg",
35 | "proxy": "localhost:1234",
36 | "replaceArguments": {
37 | "libfaac": "libfdk_aac",
38 | "^/var": "/private/var"
39 | },
40 | "preprocessFiles": {
41 | "chown": "USERNAME",
42 | "chmod": 666,
43 | "mkdirs": true
44 | },
45 | "postprocessFiles": {
46 | "chown": "daemon",
47 | "chmod": 666,
48 | "recoverChown": true,
49 | "recoverChmod": true
50 | }
51 | },
52 | "test_info" : {
53 | "capabilities" : {
54 | "auto_rotate" : true
55 | },
56 | "encoders" : ["libdfk_aac", "aac"]
57 | }*/
58 | }).success(function(data) {
59 | console.log(data);
60 | }).error(function(error) {
61 | console.log(error);
62 | });
--------------------------------------------------------------------------------
/demos/video_to_gif.js:
--------------------------------------------------------------------------------
1 | var jsffmpeg = require(__dirname + "/../index.js");
2 |
3 | var args = require('node-getopt').create([
4 | ["", "source=FILE", "source video"],
5 | ["", "target=FILE", "target image"],
6 | ["", "docker=CONTAINER", "docker"]
7 | ]).bindHelp().parseSystem().options;
8 |
9 | jsffmpeg.ffmpeg_simple(args.source, {
10 | output_type: "gif",
11 | framerate: 12,
12 | width: 480,
13 | time_start: 0,
14 | time_end: 2
15 | }, args.target, null, null, {
16 | docker: args.docker,
17 | test_ffmpeg: true
18 | }).success(function (data) {
19 | console.log(data);
20 | }).error(function (error) {
21 | console.log(error);
22 | });
23 |
--------------------------------------------------------------------------------
/demos/video_transcode.js:
--------------------------------------------------------------------------------
1 | var jsffmpeg = require(__dirname + "/../index.js");
2 |
3 | var args = require('node-getopt').create([
4 | ["", "source=FILE", "source video"],
5 | ["", "target=FILE", "target video"],
6 | ["", "width=WIDTH", "target width"],
7 | ["", "height=HEIGHT", "target height"],
8 | ["", "watermark=FILE", "watermark image"],
9 | ["", "docker=CONTAINER", "docker"],
10 | ["", "timeout=MS", "timeout"],
11 | ["", "ratiostrategy=STRATEGY", "ratio strategy", "fixed"],
12 | ["", "sizestrategy=STRATEGY", "size strategy", "keep"],
13 | ["", "shrinkstrategy=STRATEGY", "shrink strategy", "shrink-pad"],
14 | ["", "stretchstrategy=STRATEGY", "stretch strategy", "pad"],
15 | ["", "mixedstrategy=STRATEGY", "mixed strategy", "shrink-pad"]
16 | ]).bindHelp().parseSystem().options;
17 |
18 | jsffmpeg.ffmpeg_graceful(args.source, {
19 | width: parseInt(args.width) || 640,
20 | height: parseInt(args.height) || 360,
21 | watermark: args.watermark,
22 | //normalize_audio: true
23 | ratio_strategy: args.ratiostrategy,
24 | size_strategy: args.sizestrategy,
25 | shrink_strategy: args.shrinkstrategy,
26 | stretch_strategy: args.stretchstrategy,
27 | mixed_strategy: args.mixedstrategy
28 | }, args.target, null, null, {
29 | /*
30 | docker: {
31 | "container" : "jrottenberg/ffmpeg",
32 | "proxy": "localhost:1234",
33 | "replaceArguments": {
34 | "libfaac": "libfdk_aac",
35 | "^/var": "/private/var"
36 | },
37 | "preprocessFiles": {
38 | "chown": "USERNAME",
39 | "chmod": 666,
40 | "mkdirs": true
41 | },
42 | "postprocessFiles": {
43 | "chown": "daemon",
44 | "chmod": 666,
45 | "recoverChown": true,
46 | "recoverChmod": true
47 | }
48 | },
49 | "test_info" : {
50 | "capabilities" : {
51 | "auto_rotate" : true
52 | },
53 | "encoders" : ["libdfk_aac", "aac"]
54 | },
55 | */
56 | test_ffmpeg: true,
57 | timeout: args.timeout
58 | }).success(function (data) {
59 | console.log(data);
60 | }).error(function (error) {
61 | console.log(error);
62 | });
--------------------------------------------------------------------------------
/demos/video_transcode_to_avi.js:
--------------------------------------------------------------------------------
1 | var jsffmpeg = require(__dirname + "/../index.js");
2 |
3 | var args = require("node-getopt").create([
4 | ["", "source=FILE", "source video"],
5 | ["", "target=FILE", "target video"],
6 | ["", "width=WIDTH", "target width"],
7 | ["", "height=HEIGHT", "target height"],
8 | ["", "watermark=FILE", "watermark image"],
9 | ["", "docker=CONTAINER", "docker"],
10 | ["", "timeout=MS", "timeout"],
11 | ["", "ratiostrategy=STRATEGY", "ratio strategy", "fixed"],
12 | ["", "sizestrategy=STRATEGY", "size strategy", "keep"],
13 | ["", "shrinkstrategy=STRATEGY", "shrink strategy", "shrink-pad"],
14 | ["", "stretchstrategy=STRATEGY", "stretch strategy", "pad"],
15 | ["", "mixedstrategy=STRATEGY", "mixed strategy", "shrink-pad"]
16 | ]).bindHelp().parseSystem().options;
17 |
18 | jsffmpeg.ffmpeg_graceful(args.source, {
19 | width: parseInt(args.width) || 640,
20 | height: parseInt(args.height) || 360,
21 | video_format: "avi"
22 | }, args.target, null, null, {
23 | test_ffmpeg: true,
24 | timeout: args.timeout
25 | }).success(function(data) {
26 | console.log(data);
27 | }).error(function(error) {
28 | console.log(error);
29 | });
--------------------------------------------------------------------------------
/demos/video_transcode_to_mp4_av1.js:
--------------------------------------------------------------------------------
1 | var jsffmpeg = require(__dirname + "/../index.js");
2 |
3 | var args = require("node-getopt").create([
4 | ["", "source=FILE", "source video"],
5 | ["", "target=FILE", "target video"],
6 | ["", "width=WIDTH", "target width"],
7 | ["", "height=HEIGHT", "target height"],
8 | ["", "watermark=FILE", "watermark image"],
9 | ["", "docker=CONTAINER", "docker"],
10 | ["", "timeout=MS", "timeout"],
11 | ["", "ratiostrategy=STRATEGY", "ratio strategy", "fixed"],
12 | ["", "sizestrategy=STRATEGY", "size strategy", "keep"],
13 | ["", "shrinkstrategy=STRATEGY", "shrink strategy", "shrink-pad"],
14 | ["", "stretchstrategy=STRATEGY", "stretch strategy", "pad"],
15 | ["", "mixedstrategy=STRATEGY", "mixed strategy", "shrink-pad"]
16 | ]).bindHelp().parseSystem().options;
17 |
18 | jsffmpeg.ffmpeg_graceful(args.source, {
19 | width: parseInt(args.width) || 640,
20 | height: parseInt(args.height) || 360,
21 | video_format: "mp4-av1"
22 | }, args.target, null, null, {
23 | test_ffmpeg: true,
24 | timeout: args.timeout
25 | }).success(function(data) {
26 | console.log(data);
27 | }).error(function(error) {
28 | console.log(error);
29 | });
--------------------------------------------------------------------------------
/demos/video_watermark.js:
--------------------------------------------------------------------------------
1 | var jsffmpeg = require(__dirname + "/../index.js");
2 |
3 | var args = require('node-getopt').create([
4 | ["", "source=FILE", "source video"],
5 | ["", "target=FILE", "target video"],
6 | ["", "watermark=FILE", "watermark image"],
7 | ["", "timeout=MS", "timeout"],
8 | ["", "watermarksize=SIZE", "watermarksize"],
9 | ["", "watermarkx=WATERMARKX", "watermarkx"],
10 | ["", "watermarky=WATERMARKY", "watermarky"]
11 | ]).bindHelp().parseSystem().options;
12 |
13 | jsffmpeg.ffmpeg_graceful(args.source, {
14 | watermark: args.watermark,
15 | watermark_size: parseFloat(args.watermarksize),
16 | watermark_x: parseFloat(args.watermarkx),
17 | watermark_y: parseFloat(args.watermarky)
18 | }, args.target, null, null, {
19 | test_ffmpeg: true,
20 | timeout: args.timeout
21 | }).success(function (data) {
22 | console.log(data);
23 | }).error(function (error) {
24 | console.log(error);
25 | });
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | Scoped = global.Scoped || require("betajs-scoped");
2 | BetaJS = global.BetaJS || require("betajs");
3 | Scoped.binding("betajs", "global:BetaJS");
4 |
5 | module.exports = {
6 |
7 | ffprobe: require(__dirname + "/src/ffprobe.js").ffprobe,
8 |
9 | ffmpeg: require(__dirname + "/src/ffmpeg.js").ffmpeg,
10 |
11 | ffprobe_simple: require(__dirname + "/src/ffprobe-simple.js").ffprobe_simple,
12 |
13 | ffmpeg_simple_raw: require(__dirname + "/src/ffmpeg-simple.js").ffmpeg_simple_raw,
14 |
15 | ffmpeg_simple: require(__dirname + "/src/ffmpeg-simple.js").ffmpeg_simple,
16 |
17 | ffmpeg_faststart: require(__dirname + "/src/ffmpeg-faststart.js").ffmpeg_faststart,
18 |
19 | ffmpeg_graceful: require(__dirname + "/src/ffmpeg-graceful.js").ffmpeg_graceful,
20 |
21 | ffmpeg_volume_detect: require(__dirname + "/src/ffmpeg-volume-detect.js").ffmpeg_volume_detect,
22 |
23 | ffmpeg_test: require(__dirname + "/src/ffmpeg-test.js").ffmpeg_test,
24 |
25 | ffmpeg_multi_pass: require(__dirname + "/src/ffmpeg-multi-pass.js").ffmpeg_multi_pass,
26 |
27 | ffmpeg_playlist: require(__dirname + "/src/ffmpeg-playlist.js").ffmpeg_playlist,
28 |
29 | ffmpeg_playlist_raw: require(__dirname + "/src/ffmpeg-playlist.js").ffmpeg_playlist_raw
30 |
31 | };
--------------------------------------------------------------------------------
/jsonize.js:
--------------------------------------------------------------------------------
1 | var jsffmpeg = require(__dirname + "/index.js");
2 |
3 |
4 | Scoped.define("jsonize:JsonizeFfprobeTask", [
5 | "jsonize:AbstractJsonizeTask",
6 | "jsonize:JsonizeTaskRegistry",
7 | "betajs:Promise"
8 | ], function (Class, TaskRegistry, Promise, scoped) {
9 | var Cls = Class.extend({scoped: scoped}, {
10 |
11 | _run: function (payload) {
12 | return jsffmpeg.ffprobe(payload.source, {
13 | docker: payload.docker,
14 | timeout: payload.timeout,
15 | test_ffmpeg: payload.test_ffmpeg,
16 | test_info: payload.test_info
17 | });
18 | }
19 |
20 | });
21 |
22 | TaskRegistry.register("ffprobe", Cls);
23 |
24 | return Cls;
25 | });
26 |
27 |
28 | Scoped.define("jsonize:JsonizeFfprobeSimpleTask", [
29 | "jsonize:AbstractJsonizeTask",
30 | "jsonize:JsonizeTaskRegistry",
31 | "betajs:Promise"
32 | ], function (Class, TaskRegistry, Promise, scoped) {
33 | var Cls = Class.extend({scoped: scoped}, {
34 |
35 | _run: function (payload) {
36 | return jsffmpeg.ffprobe_simple(payload.source, {
37 | docker: payload.docker,
38 | timeout: payload.timeout,
39 | test_ffmpeg: payload.test_ffmpeg,
40 | test_info: payload.test_info
41 | });
42 | }
43 |
44 | });
45 |
46 | TaskRegistry.register("ffprobe-simple", Cls);
47 |
48 | return Cls;
49 | });
50 |
51 |
52 | Scoped.define("jsonize:JsonizeFfmpegTask", [
53 | "jsonize:AbstractJsonizeTask",
54 | "jsonize:JsonizeTaskRegistry",
55 | "betajs:Promise"
56 | ], function (Class, TaskRegistry, Promise, scoped) {
57 | var Cls = Class.extend({scoped: scoped}, {
58 |
59 | _run: function (payload) {
60 | return jsffmpeg.ffmpeg(
61 | payload.source || payload.sources,
62 | payload.options || [],
63 | payload.output,
64 | this._event,
65 | this,
66 | {
67 | docker: payload.docker,
68 | timeout: payload.timeout,
69 | test_ffmpeg: payload.test_ffmpeg,
70 | test_info: payload.test_info
71 | }
72 | );
73 | }
74 |
75 | });
76 |
77 | TaskRegistry.register("ffmpeg", Cls);
78 |
79 | return Cls;
80 | });
81 |
82 |
83 | Scoped.define("jsonize:JsonizeFfmpegMultiPassTask", [
84 | "jsonize:AbstractJsonizeTask",
85 | "jsonize:JsonizeTaskRegistry",
86 | "betajs:Promise"
87 | ], function (Class, TaskRegistry, Promise, scoped) {
88 | var Cls = Class.extend({scoped: scoped}, {
89 |
90 | _run: function (payload) {
91 | return jsffmpeg.ffmpeg_multi_pass(
92 | payload.source || payload.sources,
93 | payload.options || [],
94 | 2,
95 | payload.output,
96 | this._event,
97 | this,
98 | {
99 | docker: payload.docker,
100 | timeout: payload.timeout,
101 | test_ffmpeg: payload.test_ffmpeg,
102 | test_info: payload.test_info
103 | }
104 | );
105 | }
106 |
107 | });
108 |
109 | TaskRegistry.register("ffmpeg-multi-pass", Cls);
110 |
111 | return Cls;
112 | });
113 |
114 |
115 | Scoped.define("jsonize:JsonizeFfmpegSimpleTask", [
116 | "jsonize:AbstractJsonizeTask",
117 | "jsonize:JsonizeTaskRegistry",
118 | "betajs:Promise"
119 | ], function (Class, TaskRegistry, Promise, scoped) {
120 | var Cls = Class.extend({scoped: scoped}, {
121 |
122 | _run: function (payload) {
123 | return jsffmpeg.ffmpeg_simple(
124 | payload.source || payload.sources,
125 | payload.options || {},
126 | payload.output,
127 | this._event,
128 | this,
129 | {
130 | docker: payload.docker,
131 | timeout: payload.timeout,
132 | test_ffmpeg: payload.test_ffmpeg,
133 | test_info: payload.test_info
134 | }
135 | );
136 | }
137 |
138 | });
139 |
140 | TaskRegistry.register("ffmpeg-simple", Cls);
141 |
142 | return Cls;
143 | });
144 |
145 | Scoped.define("jsonize:JsonizeFfmpegFaststartTask", [
146 | "jsonize:AbstractJsonizeTask",
147 | "jsonize:JsonizeTaskRegistry",
148 | "betajs:Promise"
149 | ], function (Class, TaskRegistry, Promise, scoped) {
150 | var Cls = Class.extend({scoped: scoped}, {
151 |
152 | _run: function (payload) {
153 | return jsffmpeg.ffmpeg_faststart(
154 | payload.source || payload.sources,
155 | payload.output,
156 | this._event,
157 | this,
158 | {
159 | docker: payload.docker,
160 | timeout: payload.timeout,
161 | test_ffmpeg: payload.test_ffmpeg,
162 | test_info: payload.test_info
163 | }
164 | );
165 | }
166 |
167 | });
168 |
169 | TaskRegistry.register("ffmpeg-faststart", Cls);
170 |
171 | return Cls;
172 | });
173 |
174 |
175 | Scoped.define("jsonize:JsonizeFfmpegGracefulTask", [
176 | "jsonize:AbstractJsonizeTask",
177 | "jsonize:JsonizeTaskRegistry",
178 | "betajs:Promise"
179 | ], function (Class, TaskRegistry, Promise, scoped) {
180 | var Cls = Class.extend({scoped: scoped}, {
181 |
182 | _run: function (payload) {
183 | return jsffmpeg.ffmpeg_graceful(
184 | payload.source || payload.sources,
185 | payload.options || {},
186 | payload.output,
187 | this._event,
188 | this,
189 | {
190 | docker: payload.docker,
191 | timeout: payload.timeout,
192 | test_ffmpeg: payload.test_ffmpeg,
193 | test_info: payload.test_info
194 | }
195 | );
196 | }
197 |
198 | });
199 |
200 | TaskRegistry.register("ffmpeg-graceful", Cls);
201 |
202 | return Cls;
203 | });
204 |
205 |
206 | Scoped.define("jsonize:JsonizeFfmpegVolumeDetectTask", [
207 | "jsonize:AbstractJsonizeTask",
208 | "jsonize:JsonizeTaskRegistry",
209 | "betajs:Promise"
210 | ], function (Class, TaskRegistry, Promise, scoped) {
211 | var Cls = Class.extend({scoped: scoped}, {
212 |
213 | _run: function (payload) {
214 | return jsffmpeg.ffmpeg_volume_detect(payload.source, {
215 | docker: payload.docker,
216 | timeout: payload.timeout,
217 | test_ffmpeg: payload.test_ffmpeg,
218 | test_info: payload.test_info
219 | });
220 | }
221 |
222 | });
223 |
224 | TaskRegistry.register("ffmpeg-volume-detect", Cls);
225 |
226 | return Cls;
227 | });
228 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "js-ffmpeg",
3 | "description": "JS FFMpeg",
4 | "version": "0.0.38",
5 | "author": "Ziggeo",
6 | "repository": "https://github.com/jsonize/js-ffmpeg",
7 | "license": "Apache-2.0",
8 | "engines": {
9 | "node": ""
10 | },
11 | "main": "index.js",
12 | "types": "types/index.d.ts",
13 | "devDependencies": {
14 | "grunt": "^1.0.1",
15 | "grunt-contrib-jshint": "^3.0.0",
16 | "grunt-shell": "^2.1.0",
17 | "qunitjs": "^2.4.1"
18 | },
19 | "dependencies": {
20 | "betajs": "~1.0.96",
21 | "betajs-scoped": "~0.0.13",
22 | "docker-polyfill": "~0.0.1",
23 | "node-getopt": "^0.3.2",
24 | "tmp": "0.0.33"
25 | },
26 | "scripts": {
27 | "test": "qunit tests/tests/*.js"
28 | }
29 | }
--------------------------------------------------------------------------------
/src/ffmpeg-faststart.js:
--------------------------------------------------------------------------------
1 | Scoped.require([
2 | "betajs:Promise",
3 | "betajs:Types",
4 | "betajs:Objs"
5 | ], function(Promise, Types, Objs) {
6 |
7 | var ffmpeg = require(__dirname + "/ffmpeg.js");
8 | var helpers = require(__dirname + "/ffmpeg-helpers.js");
9 | module.exports = {
10 |
11 | ffmpeg_faststart: function(file, output, eventCallback, eventContext, opts) {
12 | const options = [];
13 | options.push("-c copy");
14 | options.push(helpers.paramsFastStart);
15 | return ffmpeg.ffmpeg(file, options, output, eventCallback, eventContext, opts);
16 | }
17 |
18 | };
19 |
20 | });
21 |
22 |
--------------------------------------------------------------------------------
/src/ffmpeg-graceful.js:
--------------------------------------------------------------------------------
1 | Scoped.require([
2 | "betajs:Promise",
3 | "betajs:Types",
4 | "betajs:Objs"
5 | ], function (Promise, Types, Objs) {
6 |
7 | var ffmpeg_simple = require(__dirname + "/ffmpeg-simple.js");
8 | var tmp = require('tmp');
9 |
10 | module.exports = {
11 |
12 | ffmpeg_graceful: function (files, options, output, eventCallback, eventContext, opts) {
13 | options = options || {};
14 | return ffmpeg_simple.ffmpeg_simple(files, options, output, eventCallback, eventContext, opts).mapError(function (err) {
15 | if ((Types.is_array(files) && files.length > 1) || options.output_type === 'audio' || options.output_type === "image")
16 | return err;
17 |
18 | var promise = Promise.create();
19 |
20 | tmp.file({postfix: ".aac"}, function (err, audioFile, fd, cleanupCallback) {
21 | if (err) {
22 | promise.asyncError(err);
23 | return;
24 | }
25 | promise.callback(function () {
26 | cleanupCallback();
27 | });
28 | tmp.file(function (err, videoFile, fd, cleanupCallback) {
29 | if (err) {
30 | promise.asyncError(err);
31 | return;
32 | }
33 | promise.callback(function () {
34 | cleanupCallback();
35 | });
36 | ffmpeg_simple.ffmpeg_simple(files, {output_type: "audio"}, audioFile, eventCallback, eventContext, opts).forwardError(promise).success(function () {
37 | ffmpeg_simple.ffmpeg_simple(files, {output_type: "video", remove_audio: true }, videoFile, eventCallback, eventContext, opts).forwardError(promise).success(function () {
38 | ffmpeg_simple.ffmpeg_simple([videoFile, audioFile], options, output, eventCallback, eventContext, opts).forwardCallback(promise);
39 | });
40 | });
41 | });
42 | });
43 |
44 | return promise;
45 | });
46 | }
47 |
48 | };
49 |
50 | });
51 |
52 |
--------------------------------------------------------------------------------
/src/ffmpeg-helpers.js:
--------------------------------------------------------------------------------
1 | Scoped.require([
2 | "betajs:TimeFormat"
3 | ], function (TimeFormat) {
4 |
5 | module.exports = {
6 |
7 | parseTimeCode: function (timecode) {
8 | var m = /(\d\d):(\d\d):(\d\d)\.(\d\d)/.exec(timecode);
9 | return m ? parseInt(m[1], 10) * 60 * 60 + parseInt(m[2], 10) * 60 + parseInt(m[3], 10) + parseInt(m[4], 10) / 100 : null;
10 | },
11 |
12 | formatTimeCode: function (seconds) {
13 | return TimeFormat.format("HH:MM:ss.L", Math.floor(seconds * 1000));
14 | },
15 |
16 | videoFormats: {
17 | "mp4": {
18 | bframes: true,
19 | acodec: ["libfaac", "libfdk_aac", "libvo_aacenc", "aac"],
20 | vcodec: "libx264",
21 | fmt: "mp4",
22 | passes: 2,
23 | modulus: 2,
24 | params: "-pix_fmt yuv420p"
25 | },
26 | "mp4-av1": {
27 | bframes: true,
28 | acodec: ["aac", "libfaac", "libfdk_aac", "libvo_aacenc"],
29 | vcodec: "av1",
30 | fmt: "mp4",
31 | passes: 2,
32 | modulus: 2,
33 | params: "-strict experimental"
34 | },
35 | "avi": {
36 | bframes: true,
37 | acodec: ["aac", "libfdk_aac", "libfaac", "libvo_aacenc"],
38 | vcodec: "libaom-av1",
39 | fmt: "avi",
40 | passes: 2,
41 | modulus: 2,
42 | params: "-strict experimental"
43 | },
44 | "m3u8": {
45 | bframes: true,
46 | acodec: "aac",
47 | vcodec: "h264",
48 | passes: 2,
49 | modulus: 2
50 | },
51 | "ogg": {
52 | bframes: true,
53 | acodec: "libvorbis",
54 | vcodec: "libtheora"
55 | },
56 | "webm": {
57 | bframes: true,
58 | acodec: "libvorbis",
59 | vcodec: "libvpx",
60 | fmt: "webm"
61 | },
62 | "wmv": {
63 | acodec: "wmav2",
64 | vcodec: "wmv2"
65 | },
66 | "wmv3": {
67 | acodec: "wmav3",
68 | vcodec: "wmv3"
69 | },
70 | "flv": {
71 | fmt: "flv",
72 | params: "-ar 44100"
73 | }
74 | },
75 |
76 | paramsSynchronize: "-async 1 -metadata:s:v:0 start_time=0",
77 |
78 | paramsAudioOnly: "-vn",
79 |
80 | paramsFormatImage: "-f image2",
81 |
82 | paramsVideoMap: function (index) { return "-map " + "0:" + index; },
83 |
84 | paramsAudioMap: function (index) { return "-map " + "1:" + index; },
85 |
86 | paramsVideoCodecUniversalConfig: "-refs 6 -coder 1 -sc_threshold 40 -flags +loop -me_range 16 -subq 7 -i_qfactor 0.71 -qcomp 0.6 -qdiff 4 -trellis 1",
87 |
88 | paramsTimeDuration: function (time_start, time_end, time_limit) {
89 | var args = [];
90 | if (time_start) {
91 | args.push("-ss");
92 | args.push(this.formatTimeCode(time_start));
93 | }
94 | if (time_end) {
95 | args.push("-to");
96 | args.push(this.formatTimeCode(time_end));
97 | }
98 | if (time_limit) {
99 | args.push("-t");
100 | args.push(this.formatTimeCode((time_start || 0) + time_limit));
101 | }
102 | return args.join(" ");
103 | },
104 |
105 | paramsFramerate: function (framerate, bframes, framerate_gop) {
106 | return "-r " + framerate + (bframes ? " -b_strategy 1 -bf 3 -g " + framerate_gop : "");
107 | },
108 |
109 | paramsVideoProfile: function (video_profile) {
110 | return "-profile:v " + video_profile;
111 | },
112 |
113 | paramsFastStart: "-movflags +faststart",
114 |
115 | paramsVideoFormat: function (fmt, vcodec, acodec, params) {
116 | var args = [];
117 | if (fmt) {
118 | args.push("-f");
119 | args.push(fmt);
120 | }
121 | if (vcodec) {
122 | args.push("-vcodec");
123 | args.push(vcodec);
124 | }
125 | if (acodec) {
126 | args.push("-acodec");
127 | args.push(acodec);
128 | }
129 | if (params)
130 | args.push(params);
131 | return args.join(" ");
132 | },
133 |
134 | paramsImageExtraction: function (image_position, image_percentage, duration) {
135 | var args = [];
136 | args.push("-ss");
137 | if (image_position !== null)
138 | args.push(this.formatTimeCode(image_position));
139 | else if (image_percentage !== null)
140 | args.push(this.formatTimeCode(image_percentage * duration));
141 | else
142 | args.push(this.formatTimeCode(0.5 * duration));
143 | args.push("-vframes");
144 | args.push("1");
145 | return args.join(" ");
146 | },
147 |
148 | paramsHighQualityGif: function (options) {
149 | var args = [];
150 | args.push("-filter_complex [0:v]");
151 | if (options.framerate)
152 | args.push("fps=" + options.framerate + ",");
153 | if (options.width || options.height)
154 | args.push("scale=w=" + (options.width || -1) + ":h=" + (options.height || -1) + ":flags=lanczos,");
155 | args.push("split[a][b];[a]palettegen[p];[b][p]paletteuse");
156 | return args.join("");
157 | },
158 |
159 | parseProgress: function (progress, duration) {
160 | var raw = {};
161 | if (progress.frame)
162 | raw.frame = parseInt(progress.frame, 10);
163 | if (progress.fps)
164 | raw.fps = parseFloat(progress.fps);
165 | if (progress.q)
166 | raw.q = parseFloat(progress.q);
167 | if (progress.size)
168 | raw.size_kb = parseInt(progress.size, 10);
169 | if (progress.bitrate)
170 | raw.bitrate_kbits = parseFloat(progress.bitrate);
171 | if (progress.dup)
172 | raw.dup = parseInt(progress.dup, 10);
173 | if (progress.drop)
174 | raw.drop = parseInt(progress.drop, 10);
175 | if (progress.time)
176 | raw.time = this.parseTimeCode(progress.time);
177 | raw.pass = progress.pass || 1;
178 | raw.passes = progress.passes || 1;
179 | if (duration && raw.time)
180 | raw.progress = (raw.pass - 1) / raw.passes + raw.time / duration / raw.passes;
181 | return raw;
182 | },
183 |
184 | computeDuration: function (duration, time_start, time_end, time_limit) {
185 | time_end = time_end > 0 && time_end < duration ? time_end : duration;
186 | time_start = time_start > 0 ? Math.min(time_start, duration) : 0;
187 | duration = Math.max(time_end - time_start, 0);
188 | if (time_limit)
189 | duration = Math.min(duration, time_limit);
190 | return duration;
191 | }
192 |
193 | };
194 |
195 | });
--------------------------------------------------------------------------------
/src/ffmpeg-multi-pass.js:
--------------------------------------------------------------------------------
1 | Scoped.require([
2 | "betajs:Promise",
3 | "betajs:Types",
4 | "betajs:Objs"
5 | ], function (Promise, Types, Objs) {
6 |
7 | var ffmpeg = require(__dirname + "/ffmpeg.js");
8 | var tmp = require('tmp');
9 |
10 | module.exports = {
11 |
12 | ffmpeg_multi_pass: function (files, options, passes, output, eventCallback, eventContext, opts) {
13 | options = options || [];
14 |
15 | if (passes === 1)
16 | return ffmpeg.ffmpeg(files, options, output, eventCallback, eventContext, opts);
17 |
18 | var promise = Promise.create();
19 |
20 | tmp.file(function (err, path, fd, cleanupCallback) {
21 | if (err) {
22 | promise.asyncError(err);
23 | return;
24 | }
25 | promise.callback(function () {
26 | cleanupCallback();
27 | });
28 | ffmpeg.ffmpeg(files, options.concat([
29 | '-pass',
30 | '1',
31 | '-passlogfile',
32 | path
33 | ]), output, function (progress) {
34 | progress.pass = 1;
35 | progress.passes = 2;
36 | if (eventCallback)
37 | eventCallback.call(this, progress);
38 | }, this, opts).forwardError(promise).success(function () {
39 | ffmpeg.ffmpeg(files, options.concat([
40 | '-pass',
41 | '2',
42 | '-passlogfile',
43 | path
44 | ]), output, function (progress) {
45 | progress.pass = 2;
46 | progress.passes = 2;
47 | if (eventCallback)
48 | eventCallback.call(this, progress);
49 | }, this, opts).forwardCallback(promise);
50 | }, this);
51 | });
52 |
53 | return promise;
54 | }
55 |
56 | };
57 |
58 | });
59 |
60 |
--------------------------------------------------------------------------------
/src/ffmpeg-playlist.js:
--------------------------------------------------------------------------------
1 | Scoped.require([
2 | "betajs:Promise",
3 | "betajs:Types",
4 | "betajs:Objs"
5 | ], function(Promise, Types, Objs) {
6 | const FS = require("fs");
7 |
8 | var ffmpeg_multi_pass = require(__dirname + "/ffmpeg-multi-pass.js");
9 | var ffprobe_simple = require(__dirname + "/ffprobe-simple.js");
10 | var ffmpeg_volume_detect = require(__dirname + "/ffmpeg-volume-detect.js");
11 | var helpers = require(__dirname + "/ffmpeg-helpers.js");
12 | var ffmpeg_test = require(__dirname + "/ffmpeg-test.js");
13 |
14 | module.exports = {
15 |
16 | ffmpeg_playlist: function(files, options, output, eventCallback, eventContext, opts) {
17 | return this.ffmpeg_playlist_raw(files, options, output, eventCallback, eventContext, opts).mapError(function(e) {
18 | if (e.logs) {
19 | if (e.logs.indexOf("Too many packets buffered for output stream") >= 0 && !options.maxMuxingQueueSize) {
20 | options.maxMuxingQueueSize = true;
21 | return this.ffmpeg_playlist_raw(files, options, output, eventCallback, eventContext, opts);
22 | }
23 | }
24 | return e;
25 | }, this);
26 | },
27 |
28 | ffmpeg_playlist_raw: function(files, options, output, eventCallback, eventContext, opts) {
29 | opts = opts || {};
30 | if (Types.is_string(files))
31 | files = [files];
32 | options = Objs.extend({
33 | output_type: "video", // video, audio, image
34 | synchronize: true,
35 | framerate: 25, // null
36 | framerate_gop: 250,
37 | image_percentage: null,
38 | image_position: null,
39 | time_limit: null,
40 | time_start: 0,
41 | time_end: null,
42 | video_map: null, //0,1,2,...
43 | audio_map: null, //0,1,2
44 | video_profile: "baseline",
45 | faststart: true,
46 | video_format: "m3u8",
47 |
48 | audio_bit_rate: null,
49 | video_bit_rate: null,
50 |
51 | normalize_audio: false,
52 | remove_audio: false,
53 | width: null,
54 | height: null,
55 | auto_rotate: true,
56 | rotate: null,
57 |
58 | ratio_strategy: "fixed", // "shrink", "stretch"
59 | size_strategy: "keep", // "shrink", "stretch"
60 | shrink_strategy: "shrink-pad", // "crop", "shrink-crop"
61 | stretch_strategy: "pad", // "stretch-pad", "stretch-crop"
62 | mixed_strategy: "shrink-pad", // "stretch-crop", "crop-pad"
63 |
64 | watermark: null,
65 | watermark_size: 0.25,
66 | watermark_x: 0.95,
67 | watermark_y: 0.95,
68 |
69 | watermarks: [],
70 |
71 | maxMuxingQueueSize: false,
72 |
73 | segment_target_duration: 4,
74 | max_bitrate_ratio: 1.07,
75 | rate_monitor_buffer_ratio: 1.5,
76 | key_frames_interval: 25,
77 | renditions: [
78 | {resolution: "640x360", bitrate: 800, audio_rate: 96},
79 | {resolution: "842x480", bitrate: 1400, audio_rate: 128},
80 | {resolution: "1280x720", bitrate: 2800, audio_rate: 128},
81 | {resolution: "1920x1080", bitrate: 5000, audio_rate: 192}
82 | ]
83 | }, options);
84 |
85 | var promises = files.map(function(file) {
86 | return ffprobe_simple.ffprobe_simple(file, opts);
87 | });
88 |
89 | if (options.watermark) {
90 | options.watermarks.unshift({
91 | watermark: options.watermark,
92 | watermark_size: options.watermark_size,
93 | watermark_x: options.watermark_x,
94 | watermark_y: options.watermark_y
95 | });
96 | }
97 |
98 | if (options.normalize_audio)
99 | promises.push(ffmpeg_volume_detect.ffmpeg_volume_detect(files[options.audio_map || files.length - 1], opts));
100 | options.watermarks.forEach(function(wm) {
101 | promises.push(ffprobe_simple.ffprobe_simple(wm.watermark, opts));
102 | }, this);
103 | if (opts.test_ffmpeg)
104 | promises.push(ffmpeg_test.ffmpeg_test(opts));
105 |
106 | return Promise.and(promises).mapSuccess(function(infos) {
107 |
108 | var testInfo = opts.test_info || {};
109 | if (opts.test_ffmpeg)
110 | testInfo = infos.pop();
111 |
112 | var watermarkInfos = [];
113 | options.watermarks.forEach(function() {
114 | watermarkInfos.unshift(infos.pop());
115 | });
116 |
117 | var audioNormalizationInfo = null;
118 | if (options.normalize_audio)
119 | audioNormalizationInfo = infos.pop();
120 |
121 | var isImage = infos.length === 1 && infos[0].image && !infos[0].video && !infos[0].audio;
122 |
123 | var passes = 1;
124 |
125 | var args = [];
126 |
127 | /*
128 | *
129 | * Synchronize Audio & Video
130 | *
131 | */
132 | if (options.remove_audio) {
133 | args.push("-an");
134 | } else if (options.synchronize) {
135 | args.push(helpers.paramsSynchronize);
136 | }
137 |
138 | /*
139 | *
140 | * Map Streams
141 | *
142 | */
143 | if (infos.length > 1) {
144 | var videoIdx = options.video_map || 0;
145 | args.push("-map " + videoIdx + ":" + infos[videoIdx].video.index);
146 | }
147 | if (infos.length > 1) {
148 | var audioIdx = options.audio_map || 1;
149 | args.push("-map " + audioIdx + ":" + infos[audioIdx].audio.index);
150 | }
151 |
152 | /*
153 | *
154 | * Audio Normalization?
155 | *
156 | */
157 | if (audioNormalizationInfo) {
158 | args.push("-af");
159 | args.push("volume=" + (-audioNormalizationInfo.max_volume) + "dB");
160 | }
161 |
162 | /*
163 | *
164 | * Which time region should be used?
165 | *
166 | */
167 | var duration = helpers.computeDuration(infos[0].duration, options.time_start, options.time_end,
168 | options.time_limit);
169 | if (options.time_start || options.time_end || options.time_limit)
170 | args.push(helpers.paramsTimeDuration(options.time_start, options.time_end, options.time_limit));
171 |
172 | var videoInfo = infos[0].video;
173 | var audioInfo = infos[1] ? infos[1].audio || infos[0].audio : infos[0].audio;
174 |
175 | var sourceWidth = 0;
176 | var sourceHeight = 0;
177 | var targetWidth = 0;
178 | var targetHeight = 0;
179 | //try {
180 |
181 | var source = infos[0];
182 | var sourceInfo = source.video || source.image;
183 | var requiredRotation = 0;
184 | if (options.auto_rotate && !(testInfo.capabilities && testInfo.capabilities.auto_rotate))
185 | requiredRotation = sourceInfo.rotation % 360;
186 | if (options.rotate) {
187 | requiredRotation = (requiredRotation + options.rotate) % 360;
188 | if (options.rotate % 180 === 90) {
189 | var temp = sourceInfo.rotated_width;
190 | sourceInfo.rotated_width = sourceInfo.rotated_height;
191 | sourceInfo.rotated_height = temp;
192 | }
193 | }
194 | sourceWidth = sourceInfo.rotated_width;
195 | sourceHeight = sourceInfo.rotated_height;
196 | var sourceRatio = sourceWidth / sourceHeight;
197 | targetWidth = sourceWidth;
198 | targetHeight = sourceHeight;
199 | var targetRatio = sourceRatio;
200 | var ratioSourceTarget = 0;
201 |
202 | /*
203 | *
204 | * Which sizing should be used?
205 | *
206 | */
207 |
208 | var renditionArgs = {};
209 | Objs.iter(options.renditions, function(rendition, i) {
210 | // Step 1: Fix Rotation
211 | renditionArgs[rendition.resolution] = {};
212 | var vfilters = [];
213 | var sizing = "";
214 |
215 | if (requiredRotation !== 0) {
216 | if (requiredRotation % 180 === 90) {
217 | vfilters.push("transpose=" + (requiredRotation === 90 ? 1 : 2));
218 | }
219 | if (requiredRotation === 180) {
220 | vfilters.push("hflip,vflip");
221 | }
222 | args.push("-metadata:s:v:0");
223 | args.push("rotate=0");
224 | }
225 |
226 | let widthHeight = rendition.resolution.split("x");
227 | let renditionWidth = Types.parseInt(widthHeight[0]);
228 | let renditionHeight = Types.parseInt(widthHeight[1]);
229 |
230 | var modulus = options.output_type === "video" ? helpers.videoFormats[options.video_format].modulus || 1 : 1;
231 | var modulusAdjust = function(value) {
232 | value = Math.round(value);
233 | return value % modulus === 0 ? value : (Math.round(value / modulus) * modulus);
234 | };
235 |
236 | if (modulusAdjust(sourceWidth) !== sourceWidth || modulusAdjust(sourceHeight) !== sourceHeight ||
237 | renditionWidth || renditionHeight) {
238 |
239 | // Step 2: Fix Size & Ratio
240 | targetWidth = renditionWidth || sourceWidth;
241 | targetHeight = renditionHeight || sourceHeight;
242 | targetRatio = targetWidth / targetHeight;
243 | ratioSourceTarget = Math.sign(sourceWidth * targetHeight - targetWidth * sourceHeight);
244 |
245 | if (options.ratio_strategy !== "fixed" && ratioSourceTarget !== 0) {
246 | if ((options.ratio_strategy === "stretch" && ratioSourceTarget > 0) ||
247 | (options.ratio_strategy === "shrink" && ratioSourceTarget < 0))
248 | targetWidth = targetHeight * sourceRatio;
249 | if ((options.ratio_strategy === "stretch" && ratioSourceTarget < 0) ||
250 | (options.ratio_strategy === "shrink" && ratioSourceTarget > 0))
251 | targetHeight = targetWidth / sourceRatio;
252 | targetRatio = sourceRatio;
253 | ratioSourceTarget = 0;
254 | }
255 |
256 | if (options.size_strategy === "shrink" && targetWidth > sourceWidth && targetHeight > sourceHeight) {
257 | targetWidth = ratioSourceTarget < 0 ? sourceHeight * targetRatio : sourceWidth;
258 | targetHeight = ratioSourceTarget >= 0 ? targetWidth / targetRatio : sourceHeight;
259 | } else if (options.size_strategy === "stretch" && targetWidth < sourceWidth && targetHeight <
260 | sourceHeight) {
261 | targetWidth = ratioSourceTarget >= 0 ? sourceHeight * targetRatio : sourceWidth;
262 | targetHeight = ratioSourceTarget < 0 ? targetWidth / targetRatio : sourceHeight;
263 | }
264 |
265 | var vf = [];
266 |
267 | // Step 3: Modulus
268 |
269 | targetWidth = modulusAdjust(targetWidth);
270 | targetHeight = modulusAdjust(targetHeight);
271 |
272 | var cropped = false;
273 | var addCrop = function(x, y, multi) {
274 | x = Math.round(x);
275 | y = Math.round(y);
276 | if (x === 0 && y === 0)
277 | return;
278 | cropped = true;
279 | var cropWidth = targetWidth - 2 * x;
280 | var cropHeight = targetHeight - 2 * y;
281 | vfilters.push("scale=" + [
282 | multi || ratioSourceTarget >= 0 ? cropWidth : targetWidth,
283 | !multi && ratioSourceTarget >= 0 ? targetHeight : cropHeight].join(":"));
284 | vfilters.push("crop=" + [
285 | !multi && ratioSourceTarget <= 0 ? cropWidth : targetWidth,
286 | multi || ratioSourceTarget <= 0 ? targetHeight : cropHeight,
287 | -x,
288 | -y].join(":"));
289 | };
290 | var padded = false;
291 | var addPad = function(x, y, multi) {
292 | x = Math.round(x);
293 | y = Math.round(y);
294 | if (x === 0 && y === 0)
295 | return;
296 | padded = true;
297 | var padWidth = targetWidth - 2 * x;
298 | var padHeight = targetHeight - 2 * y;
299 | vfilters.push("scale=" + [
300 | multi || ratioSourceTarget <= 0 ? padWidth : targetWidth,
301 | !multi && ratioSourceTarget <= 0 ? targetHeight : padHeight].join(":"));
302 | vfilters.push("pad=" + [
303 | !multi && ratioSourceTarget >= 0 ? padWidth : targetWidth,
304 | multi || ratioSourceTarget >= 0 ? targetHeight : padHeight,
305 | x,
306 | y].join(":"));
307 | };
308 |
309 | // Step 4: Crop & Pad
310 | if (targetWidth >= sourceWidth && targetHeight >= sourceHeight) {
311 | if (options.stretch_strategy === "pad")
312 | addPad((targetWidth - sourceWidth) / 2,
313 | (targetHeight - sourceHeight) / 2,
314 | true);
315 | else if (options.stretch_strategy === "stretch-pad")
316 | addPad(ratioSourceTarget <= 0 ? (targetWidth - targetHeight * sourceRatio) / 2 : 0,
317 | ratioSourceTarget >= 0 ? (targetHeight - targetWidth / sourceRatio) / 2 : 0);
318 | else // stretch-crop
319 | addCrop(ratioSourceTarget >= 0 ? (targetWidth - targetHeight * sourceRatio) / 2 : 0,
320 | ratioSourceTarget <= 0 ? (targetHeight - targetWidth / sourceRatio) / 2 : 0);
321 | } else if (targetWidth <= sourceWidth && targetHeight <= sourceHeight) {
322 | if (options.shrink_strategy === "crop")
323 | addCrop((targetWidth - sourceWidth) / 2,
324 | (targetHeight - sourceHeight) / 2,
325 | true);
326 | else if (options.shrink_strategy === "shrink-crop")
327 | addCrop(ratioSourceTarget >= 0 ? (targetWidth - targetHeight * sourceRatio) / 2 : 0,
328 | ratioSourceTarget <= 0 ? (targetHeight - targetWidth / sourceRatio) / 2 : 0);
329 | else // shrink-pad
330 | addPad(ratioSourceTarget <= 0 ? (targetWidth - targetHeight * sourceRatio) / 2 : 0,
331 | ratioSourceTarget >= 0 ? (targetHeight - targetWidth / sourceRatio) / 2 : 0);
332 | } else {
333 | if (options.mixed_strategy === "shrink-pad")
334 | addPad(ratioSourceTarget <= 0 ? (targetWidth - targetHeight * sourceRatio) / 2 : 0,
335 | ratioSourceTarget >= 0 ? (targetHeight - targetWidth / sourceRatio) / 2 : 0);
336 | else if (options.mixed_strategy === "stretch-crop")
337 | addCrop(ratioSourceTarget >= 0 ? (targetWidth - targetHeight * sourceRatio) / 2 : 0,
338 | ratioSourceTarget <= 0 ? (targetHeight - targetWidth / sourceRatio) / 2 : 0);
339 | else {
340 | // crop-pad
341 | cropped = true;
342 | padded = true;
343 | var direction = ratioSourceTarget >= 0;
344 | var dirX = Math.abs(Math.round((sourceWidth - targetWidth) / 2));
345 | var dirY = Math.abs(Math.round((sourceHeight - targetHeight) / 2));
346 | vfilters.push("crop=" + [
347 | direction ? targetWidth : sourceWidth,
348 | direction ? sourceHeight : targetHeight,
349 | direction ? dirX : 0,
350 | direction ? 0 : dirY].join(":"));
351 | vfilters.push(
352 | "pad=" + [targetWidth, targetHeight, direction ? 0 : dirX, direction ? dirY : 0].join(":"));
353 | }
354 | }
355 |
356 | if (!padded && !cropped)
357 | renditionArgs[rendition.resolution].sizing = targetWidth + "x" + targetHeight;
358 |
359 | }
360 |
361 | vfilters = vfilters.join(",");
362 |
363 | /*
364 | *
365 | * Watermark (depends on sizing)
366 | *
367 | */
368 |
369 | var watermarkFilters = options.watermarks.map(function(watermark, i) {
370 | var watermarkInfo = watermarkInfos[i];
371 | var watermarkMeta = watermarkInfo.image || watermarkInfo.video;
372 | var scaleWidth = watermarkMeta.width;
373 | var scaleHeight = watermarkMeta.height;
374 | var maxWidth = targetWidth * watermark.watermark_size;
375 | var maxHeight = targetHeight * watermark.watermark_size;
376 | if (scaleWidth > maxWidth || scaleHeight > maxHeight) {
377 | var watermarkRatio = maxWidth * scaleHeight >= maxHeight * scaleWidth;
378 | scaleWidth = watermarkRatio ? watermarkMeta.width * maxHeight / watermarkMeta.height : maxWidth;
379 | scaleHeight = !watermarkRatio ? watermarkMeta.height * maxWidth / watermarkMeta.width : maxHeight;
380 | }
381 | var posX = watermark.watermark_x * (targetWidth - scaleWidth);
382 | var posY = watermark.watermark_y * (targetHeight - scaleHeight);
383 |
384 | return [
385 | "[prewm" + i + "];",
386 | "movie=" + watermark.watermark + ",",
387 | "scale=" + [Math.round(scaleWidth), Math.round(scaleHeight)].join(":"),
388 | "[wm" + i + "];",
389 | "[prewm" + i + "][wm" + i + "]",
390 | "overlay=" + [Math.round(posX), Math.round(posY)].join(":")
391 | ].join("");
392 | }).join("");
393 |
394 | if (watermarkFilters) {
395 | if (vfilters)
396 | vfilters = "[in]" + vfilters + watermarkFilters + "[out]";
397 | else
398 | vfilters = watermarkFilters.substring("[prewm0];".length).replace("[prewm0]", "[in]") + "[out]";
399 | }
400 |
401 | renditionArgs[rendition.resolution].vf = vfilters;
402 |
403 | });
404 | /*
405 | *
406 | * Format
407 | *
408 | */
409 |
410 | if (options.video_profile && options.video_format === "mp4") {
411 | args.push(helpers.paramsVideoProfile(options.video_profile));
412 | }
413 | if (options.faststart && options.video_format === "mp4") {
414 | args.push(helpers.paramsFastStart);
415 | }
416 | var format = helpers.videoFormats[options.video_format];
417 | if (format && (format.fmt || format.vcodec || format.acodec || format.params)) {
418 | var acodec = format.acodec;
419 | if (Types.is_array(acodec)) {
420 | if (testInfo.encoders) {
421 | var encoders = Objs.objectify(testInfo.encoders);
422 | acodec = acodec.filter(function(codec) {
423 | return encoders[codec];
424 | });
425 | }
426 | if (acodec.length === 0)
427 | acodec = format.acodec;
428 | acodec = acodec[0];
429 | }
430 | args.push(helpers.paramsVideoFormat(format.fmt, format.vcodec, acodec, format.params));
431 | }
432 | args.push(...helpers.paramsVideoCodecUniversalConfig.split(" "));
433 | if (format && format.passes > 1)
434 | passes = format.passes;
435 |
436 | // Workaround for https://trac.ffmpeg.org/ticket/6375
437 | if (options.maxMuxingQueueSize) {
438 | args.push("-max_muxing_queue_size");
439 | args.push("9999");
440 | }
441 |
442 | let newArgs = [];
443 | const target = output;
444 | if (!FS.existsSync(target))
445 | FS.mkdirSync(target, {recursive: true});
446 | let masterPlaylist = "#EXTM3U\n#EXT-X-VERSION:3\n";
447 | const keyFramesInterval = options.key_frames_interval;
448 | let staticParams = `-g ${keyFramesInterval} -keyint_min ${keyFramesInterval} -hls_time ${options.segment_target_duration}`;
449 | staticParams += ` -hls_playlist_type vod`;
450 | Objs.iter(options.renditions, function(obj, i) {
451 | let resolution = renditionArgs[obj.resolution].sizing ? renditionArgs[obj.resolution].sizing : obj.resolution;
452 | let widthHeight = resolution.split("x");
453 | let width = Types.parseInt(widthHeight[0]);
454 | let height = Types.parseInt(widthHeight[1]);
455 | let maxRate = obj.bitrate * options.max_bitrate_ratio;
456 | let bufSize = obj.bitrate * options.rate_monitor_buffer_ratio;
457 | let bandwidth = obj.bitrate * 1000;
458 | let name = `${height}p`;
459 | newArgs.push(...args);
460 | newArgs.push(...staticParams.split(" "));
461 | if (renditionArgs[obj.resolution].vf) {
462 | newArgs.push(...`-vf ${renditionArgs[obj.resolution].vf}`.split(" "));
463 | } else {
464 | newArgs.push(...`-vf scale=w=${width}:h=${height}:force_original_aspect_ratio=decrease`.split(" "));
465 | }
466 | newArgs.push(
467 | ...`-b:v ${obj.bitrate}k -maxrate ${maxRate}k -bufsize ${bufSize}k -b:a ${obj.audio_rate}k`.split(" "));
468 | newArgs.push(...`-hls_segment_filename ${target}/${name}_%03d.ts ${target}/${name}.m3u8`.split(" "));
469 | masterPlaylist += `#EXT-X-STREAM-INF:BANDWIDTH=${bandwidth},RESOLUTION=${resolution}\n${name}.m3u8\n`;
470 | });
471 | return ffmpeg_multi_pass.ffmpeg_multi_pass(files, newArgs, passes, null, function(progress) {
472 | if (eventCallback)
473 | eventCallback.call(eventContext || this, helpers.parseProgress(progress, duration));
474 | }, this, opts).mapSuccess(function() {
475 | FS.writeFileSync(target + "/playlist.m3u8", masterPlaylist);
476 | return Promise.value({playlist: target + "/playlist.m3u8"});
477 | }, this);
478 | });
479 |
480 | }
481 |
482 | };
483 |
484 | });
485 |
486 |
--------------------------------------------------------------------------------
/src/ffmpeg-simple.js:
--------------------------------------------------------------------------------
1 | Scoped.require([
2 | "betajs:Promise",
3 | "betajs:Types",
4 | "betajs:Objs"
5 | ], function (Promise, Types, Objs) {
6 |
7 | var ffmpeg_multi_pass = require(__dirname + "/ffmpeg-multi-pass.js");
8 | var ffprobe_simple = require(__dirname + "/ffprobe-simple.js");
9 | var ffmpeg_volume_detect = require(__dirname + "/ffmpeg-volume-detect.js");
10 | var helpers = require(__dirname + "/ffmpeg-helpers.js");
11 | var ffmpeg_test = require(__dirname + "/ffmpeg-test.js");
12 |
13 |
14 | module.exports = {
15 |
16 | ffmpeg_simple: function (files, options, output, eventCallback, eventContext, opts) {
17 | return this.ffmpeg_simple_raw(files, options, output, eventCallback, eventContext, opts).mapError(function (e) {
18 | if (e.logs) {
19 | if (e.logs.indexOf("Too many packets buffered for output stream") >= 0 && !options.maxMuxingQueueSize) {
20 | options.maxMuxingQueueSize = true;
21 | return this.ffmpeg_simple_raw(files, options, output, eventCallback, eventContext, opts);
22 | }
23 | }
24 | return e;
25 | }, this);
26 | },
27 |
28 | ffmpeg_simple_raw: function (files, options, output, eventCallback, eventContext, opts) {
29 | opts = opts || {};
30 | if (Types.is_string(files))
31 | files = [files];
32 | options = Objs.extend({
33 | output_type: "video", // video, audio, image
34 | synchronize: true,
35 | framerate: 25, // null
36 | framerate_gop: 250,
37 | image_percentage: null,
38 | image_position: null,
39 | time_limit: null,
40 | time_start: 0,
41 | time_end: null,
42 | video_map: null, //0,1,2,...
43 | audio_map: null, //0,1,2
44 | video_profile: "baseline",
45 | faststart: true,
46 | video_format: "mp4",
47 |
48 | audio_bit_rate: null,
49 | video_bit_rate: null,
50 |
51 | normalize_audio: false,
52 | remove_audio: false,
53 | width: null,
54 | height: null,
55 | auto_rotate: true,
56 | rotate: null,
57 |
58 | ratio_strategy: "fixed", // "shrink", "stretch"
59 | size_strategy: "keep", // "shrink", "stretch"
60 | shrink_strategy: "shrink-pad", // "crop", "shrink-crop"
61 | stretch_strategy: "pad", // "stretch-pad", "stretch-crop"
62 | mixed_strategy: "shrink-pad", // "stretch-crop", "crop-pad"
63 |
64 | watermark: null,
65 | watermark_size: 0.25,
66 | watermark_x: 0.95,
67 | watermark_y: 0.95,
68 |
69 | watermarks: [],
70 |
71 | maxMuxingQueueSize: false
72 | }, options);
73 |
74 | var promises = files.map(function (file) {
75 | return ffprobe_simple.ffprobe_simple(file, opts);
76 | });
77 |
78 | if (options.watermark) {
79 | options.watermarks.unshift({
80 | watermark: options.watermark,
81 | watermark_size: options.watermark_size,
82 | watermark_x: options.watermark_x,
83 | watermark_y: options.watermark_y
84 | });
85 | }
86 |
87 | if (options.normalize_audio)
88 | promises.push(ffmpeg_volume_detect.ffmpeg_volume_detect(files[options.audio_map || files.length - 1], opts));
89 | options.watermarks.forEach(function (wm) {
90 | promises.push(ffprobe_simple.ffprobe_simple(wm.watermark, opts));
91 | }, this);
92 | if (opts.test_ffmpeg)
93 | promises.push(ffmpeg_test.ffmpeg_test(opts));
94 |
95 | return Promise.and(promises).mapSuccess(function (infos) {
96 |
97 | var testInfo = opts.test_info || {};
98 | if (opts.test_ffmpeg)
99 | testInfo = infos.pop();
100 |
101 | var watermarkInfos = [];
102 | options.watermarks.forEach(function () {
103 | watermarkInfos.unshift(infos.pop());
104 | });
105 |
106 | var audioNormalizationInfo = null;
107 | if (options.normalize_audio)
108 | audioNormalizationInfo = infos.pop();
109 |
110 | var isImage = infos.length === 1 && ((infos[0].image && !infos[0].video && !infos[0].audio) ||
111 | (infos[0].video && infos[0].format_default_extension.includes("_pipe")));
112 |
113 | var passes = 1;
114 |
115 | var args = [];
116 |
117 |
118 | /*
119 | *
120 | * Synchronize Audio & Video
121 | *
122 | */
123 | if (options.output_type === 'video') {
124 | if (options.remove_audio)
125 | args.push("-an");
126 | else if (options.synchronize)
127 | args.push(helpers.paramsSynchronize);
128 | }
129 |
130 |
131 | /*
132 | *
133 | * Map Streams
134 | *
135 | */
136 | if (options.output_type === 'audio') {
137 | args.push(helpers.paramsAudioOnly);
138 | } else if (options.output_type === 'video') {
139 | if (infos.length > 1) {
140 | var videoIdx = options.video_map || 0;
141 | args.push("-map " + videoIdx + ":" + infos[videoIdx].video.index);
142 | }
143 | if (infos.length > 1) {
144 | var audioIdx = options.audio_map || 1;
145 | args.push("-map " + audioIdx + ":" + infos[audioIdx].audio.index);
146 | }
147 | }
148 |
149 | /*
150 | *
151 | * Audio Normalization?
152 | *
153 | */
154 | if (audioNormalizationInfo) {
155 | args.push("-af");
156 | args.push("volume=" + (-audioNormalizationInfo.max_volume) + "dB");
157 | }
158 |
159 |
160 | /*
161 | *
162 | * Which time region should be used?
163 | *
164 | */
165 | var duration = helpers.computeDuration(infos[0].duration, options.time_start, options.time_end, options.time_limit);
166 | if (!isImage) {
167 | if (options.output_type === 'image')
168 | args.push(helpers.paramsImageExtraction(options.image_position, options.image_percentage, duration));
169 | else if (options.time_start || options.time_end || options.time_limit)
170 | args.push(helpers.paramsTimeDuration(options.time_start, options.time_end, options.time_limit));
171 | }
172 |
173 |
174 | var videoInfo = infos[0].video;
175 | if (videoInfo && infos[0].bit_rate && (!videoInfo.bit_rate || infos[0].bit_rate > videoInfo.bit_rate))
176 | videoInfo.bit_rate = infos[0].bit_rate;
177 |
178 | var audioInfo = infos[1] ? infos[1].audio || infos[0].audio : infos[0].audio;
179 |
180 |
181 | var sourceWidth = 0;
182 | var sourceHeight = 0;
183 | var targetWidth = 0;
184 | var targetHeight = 0;
185 | //try {
186 |
187 | if (options.output_type !== 'audio') {
188 | var source = infos[0];
189 | var sourceInfo = source.video || source.image;
190 | var requiredRotation = 0;
191 | if (options.auto_rotate && !(testInfo.capabilities && testInfo.capabilities.auto_rotate))
192 | requiredRotation = sourceInfo.rotation % 360;
193 | if (options.rotate) {
194 | requiredRotation = (requiredRotation + options.rotate) % 360;
195 | if (options.rotate % 180 === 90) {
196 | var temp = sourceInfo.rotated_width;
197 | sourceInfo.rotated_width = sourceInfo.rotated_height;
198 | sourceInfo.rotated_height = temp;
199 | }
200 | }
201 | sourceWidth = sourceInfo.rotated_width;
202 | sourceHeight = sourceInfo.rotated_height;
203 | var sourceRatio = sourceWidth / sourceHeight;
204 | targetWidth = sourceWidth;
205 | targetHeight = sourceHeight;
206 | var targetRatio = sourceRatio;
207 | var ratioSourceTarget = 0;
208 |
209 | /*
210 | *
211 | * Which sizing should be used?
212 | *
213 | */
214 |
215 | // Step 1: Fix Rotation
216 | var vfilters = [];
217 | var sizing = "";
218 |
219 | if (requiredRotation !== 0) {
220 | if (requiredRotation % 180 === 90) {
221 | vfilters.push("transpose=" + (requiredRotation === 90 ? 1 : 2));
222 | }
223 | if (requiredRotation === 180) {
224 | vfilters.push("hflip,vflip");
225 | }
226 | args.push("-metadata:s:v:0");
227 | args.push("rotate=0");
228 | }
229 |
230 | var modulus = options.output_type === 'video' ? helpers.videoFormats[options.video_format].modulus || 1 : 1;
231 | var modulusAdjust = function (value) {
232 | value = Math.round(value);
233 | return value % modulus === 0 ? value : (Math.round(value / modulus) * modulus);
234 | };
235 |
236 | if (modulusAdjust(sourceWidth) !== sourceWidth || modulusAdjust(sourceHeight) !== sourceHeight || options.width || options.height) {
237 |
238 | // Step 2: Fix Size & Ratio
239 | targetWidth = options.width || sourceWidth;
240 | targetHeight = options.height || sourceHeight;
241 | targetRatio = targetWidth / targetHeight;
242 | ratioSourceTarget = Math.sign(sourceWidth * targetHeight - targetWidth * sourceHeight);
243 |
244 | if (options.ratio_strategy !== "fixed" && ratioSourceTarget !== 0) {
245 | if ((options.ratio_strategy === "stretch" && ratioSourceTarget > 0) || (options.ratio_strategy === "shrink" && ratioSourceTarget < 0))
246 | targetWidth = targetHeight * sourceRatio;
247 | if ((options.ratio_strategy === "stretch" && ratioSourceTarget < 0) || (options.ratio_strategy === "shrink" && ratioSourceTarget > 0))
248 | targetHeight = targetWidth / sourceRatio;
249 | targetRatio = sourceRatio;
250 | ratioSourceTarget = 0;
251 | }
252 |
253 | if (options.size_strategy === "shrink" && targetWidth > sourceWidth && targetHeight > sourceHeight) {
254 | targetWidth = ratioSourceTarget < 0 ? sourceHeight * targetRatio : sourceWidth;
255 | targetHeight = ratioSourceTarget >= 0 ? targetWidth / targetRatio : sourceHeight;
256 | } else if (options.size_strategy === "stretch" && targetWidth < sourceWidth && targetHeight < sourceHeight) {
257 | targetWidth = ratioSourceTarget >= 0 ? sourceHeight * targetRatio : sourceWidth;
258 | targetHeight = ratioSourceTarget < 0 ? targetWidth / targetRatio : sourceHeight;
259 | }
260 |
261 | var vf = [];
262 |
263 | // Step 3: Modulus
264 |
265 | targetWidth = modulusAdjust(targetWidth);
266 | targetHeight = modulusAdjust(targetHeight);
267 |
268 | var cropped = false;
269 | var addCrop = function (x, y, multi) {
270 | x = Math.round(x);
271 | y = Math.round(y);
272 | if (x === 0 && y === 0)
273 | return;
274 | cropped = true;
275 | var cropWidth = targetWidth - 2 * x;
276 | var cropHeight = targetHeight - 2 * y;
277 | vfilters.push("scale=" + [multi || ratioSourceTarget >= 0 ? cropWidth : targetWidth, !multi && ratioSourceTarget >= 0 ? targetHeight : cropHeight].join(":"));
278 | vfilters.push("crop=" + [!multi && ratioSourceTarget <= 0 ? cropWidth : targetWidth, multi || ratioSourceTarget <= 0 ? targetHeight : cropHeight, -x, -y].join(":"));
279 | };
280 | var padded = false;
281 | var addPad = function (x, y, multi) {
282 | x = Math.round(x);
283 | y = Math.round(y);
284 | if (x === 0 && y === 0)
285 | return;
286 | padded = true;
287 | var padWidth = targetWidth - 2 * x;
288 | var padHeight = targetHeight - 2 * y;
289 | vfilters.push("scale=" + [multi || ratioSourceTarget <= 0 ? padWidth : targetWidth, !multi && ratioSourceTarget <= 0 ? targetHeight : padHeight].join(":"));
290 | vfilters.push("pad=" + [!multi && ratioSourceTarget >= 0 ? padWidth : targetWidth, multi || ratioSourceTarget >= 0 ? targetHeight : padHeight, x, y].join(":"));
291 | };
292 |
293 | // Step 4: Crop & Pad
294 | if (targetWidth >= sourceWidth && targetHeight >= sourceHeight) {
295 | if (options.stretch_strategy === "pad")
296 | addPad((targetWidth - sourceWidth) / 2,
297 | (targetHeight - sourceHeight) / 2,
298 | true);
299 | else if (options.stretch_strategy === "stretch-pad")
300 | addPad(ratioSourceTarget <= 0 ? (targetWidth - targetHeight * sourceRatio) / 2 : 0,
301 | ratioSourceTarget >= 0 ? (targetHeight - targetWidth / sourceRatio) / 2 : 0);
302 | else // stretch-crop
303 | addCrop(ratioSourceTarget >= 0 ? (targetWidth - targetHeight * sourceRatio) / 2 : 0,
304 | ratioSourceTarget <= 0 ? (targetHeight - targetWidth / sourceRatio) / 2 : 0);
305 | } else if (targetWidth <= sourceWidth && targetHeight <= sourceHeight) {
306 | if (options.shrink_strategy === "crop")
307 | addCrop((targetWidth - sourceWidth) / 2,
308 | (targetHeight - sourceHeight) / 2,
309 | true);
310 | else if (options.shrink_strategy === "shrink-crop")
311 | addCrop(ratioSourceTarget >= 0 ? (targetWidth - targetHeight * sourceRatio) / 2 : 0,
312 | ratioSourceTarget <= 0 ? (targetHeight - targetWidth / sourceRatio) / 2 : 0);
313 | else // shrink-pad
314 | addPad(ratioSourceTarget <= 0 ? (targetWidth - targetHeight * sourceRatio) / 2 : 0,
315 | ratioSourceTarget >= 0 ? (targetHeight - targetWidth / sourceRatio) / 2 : 0);
316 | } else {
317 | if (options.mixed_strategy === "shrink-pad")
318 | addPad(ratioSourceTarget <= 0 ? (targetWidth - targetHeight * sourceRatio) / 2 : 0,
319 | ratioSourceTarget >= 0 ? (targetHeight - targetWidth / sourceRatio) / 2 : 0);
320 | else if (options.mixed_strategy === "stretch-crop")
321 | addCrop(ratioSourceTarget >= 0 ? (targetWidth - targetHeight * sourceRatio) / 2 : 0,
322 | ratioSourceTarget <= 0 ? (targetHeight - targetWidth / sourceRatio) / 2 : 0);
323 | else {
324 | // crop-pad
325 | cropped = true;
326 | padded = true;
327 | var direction = ratioSourceTarget >= 0;
328 | var dirX = Math.abs(Math.round((sourceWidth - targetWidth) / 2));
329 | var dirY = Math.abs(Math.round((sourceHeight - targetHeight) / 2));
330 | vfilters.push("crop=" + [direction ? targetWidth : sourceWidth, direction ? sourceHeight : targetHeight, direction ? dirX : 0, direction ? 0 : dirY].join(":"));
331 | vfilters.push("pad=" + [targetWidth, targetHeight, direction ? 0 : dirX, direction ? dirY : 0].join(":"));
332 | }
333 | }
334 |
335 | if (!padded && !cropped)
336 | sizing = targetWidth + "x" + targetHeight;
337 |
338 | }
339 |
340 | vfilters = vfilters.join(",");
341 |
342 | /*
343 | *
344 | * Watermark (depends on sizing)
345 | *
346 | */
347 |
348 | var watermarkFilters = options.watermarks.map(function (watermark, i) {
349 | var watermarkInfo = watermarkInfos[i];
350 | var watermarkMeta = watermarkInfo.image || watermarkInfo.video;
351 | var scaleWidth = watermarkMeta.width;
352 | var scaleHeight = watermarkMeta.height;
353 | var maxWidth = targetWidth * watermark.watermark_size;
354 | var maxHeight = targetHeight * watermark.watermark_size;
355 | if (scaleWidth > maxWidth || scaleHeight > maxHeight) {
356 | var watermarkRatio = maxWidth * scaleHeight >= maxHeight * scaleWidth;
357 | scaleWidth = watermarkRatio ? watermarkMeta.width * maxHeight / watermarkMeta.height : maxWidth;
358 | scaleHeight = !watermarkRatio ? watermarkMeta.height * maxWidth / watermarkMeta.width : maxHeight;
359 | }
360 | var posX = watermark.watermark_x * (targetWidth - scaleWidth);
361 | var posY = watermark.watermark_y * (targetHeight - scaleHeight);
362 |
363 |
364 | return [
365 | "[prewm" + i + "];",
366 | "movie=" + watermark.watermark + ",",
367 | "scale=" + [Math.round(scaleWidth), Math.round(scaleHeight)].join(":"),
368 | "[wm" + i + "];",
369 | "[prewm" + i + "][wm" + i + "]",
370 | "overlay=" + [Math.round(posX), Math.round(posY)].join(":")
371 | ].join("");
372 | }).join("");
373 |
374 | if (watermarkFilters) {
375 | if (vfilters)
376 | vfilters = "[in]" + vfilters + watermarkFilters + "[out]";
377 | else
378 | vfilters = watermarkFilters.substring("[prewm0];".length).replace("[prewm0]", "[in]") + "[out]";
379 | }
380 |
381 |
382 | // Video Filters
383 | if (vfilters && options.output_type !== "gif") {
384 | args.push("-vf");
385 | args.push(vfilters);
386 | }
387 | if (sizing) {
388 | args.push("-s");
389 | args.push(sizing);
390 | }
391 |
392 |
393 | }
394 |
395 |
396 | /*
397 | *
398 | * Format
399 | *
400 | */
401 | if (options.output_type === 'image')
402 | args.push(helpers.paramsFormatImage);
403 | if (options.output_type === 'video') {
404 | if (options.video_profile && options.video_format === "mp4")
405 | args.push(helpers.paramsVideoProfile(options.video_profile));
406 | if (options.faststart && options.video_format === "mp4")
407 | args.push(helpers.paramsFastStart);
408 | var format = helpers.videoFormats[options.video_format];
409 | if (format && (format.fmt || format.vcodec || format.acodec || format.params)) {
410 | var acodec = format.acodec;
411 | if (Types.is_array(acodec)) {
412 | if (testInfo.encoders) {
413 | var encoders = Objs.objectify(testInfo.encoders);
414 | acodec = acodec.filter(function (codec) {
415 | return encoders[codec];
416 | });
417 | }
418 | if (acodec.length === 0)
419 | acodec = format.acodec;
420 | acodec = acodec[0];
421 | }
422 | args.push(helpers.paramsVideoFormat(format.fmt, format.vcodec, acodec, format.params));
423 | }
424 | if (options.framerate)
425 | args.push(helpers.paramsFramerate(options.framerate, format.bframes, options.framerate_gop));
426 | args.push(helpers.paramsVideoCodecUniversalConfig);
427 | if (format && format.passes > 1)
428 | passes = format.passes;
429 | }
430 | if (options.output_type === "gif") {
431 | args.push(helpers.paramsHighQualityGif({
432 | "width": options.width,
433 | "height": options.height,
434 | "framerate": options.framerate
435 | }));
436 | }
437 |
438 |
439 | /*
440 | *
441 | * Bit rate (depends on watermark + sizing)
442 | *
443 | */
444 | if (options.output_type === "video") {
445 | args.push("-b:v");
446 | var video_bit_rate = options.video_bit_rate;
447 | if (!video_bit_rate && videoInfo.bit_rate)
448 | video_bit_rate = videoInfo.bit_rate * Math.min(Math.max(targetWidth * targetHeight / sourceWidth / sourceHeight, 0.25), 4);
449 | if (!video_bit_rate)
450 | video_bit_rate = Math.round(1000 * (targetWidth + targetHeight) / 2);
451 | args.push(Math.round(video_bit_rate / 1000) + "k");
452 | if (audioInfo) {
453 | args.push("-b:a");
454 | var audio_bit_rate = options.audio_bit_rate || Math.max(audioInfo.bit_rate || 64000, 64000);
455 | args.push(Math.round(audio_bit_rate / 1000) + "k");
456 | }
457 | }
458 |
459 | // Workaround for https://trac.ffmpeg.org/ticket/6375
460 | if (options.maxMuxingQueueSize) {
461 | args.push("-max_muxing_queue_size");
462 | args.push("9999");
463 | }
464 |
465 | //} catch(e) {console.log(e);}
466 |
467 | //console.log(files, args, passes, output);
468 | return ffmpeg_multi_pass.ffmpeg_multi_pass(files, args, passes, output, function (progress) {
469 | if (eventCallback)
470 | eventCallback.call(eventContext || this, helpers.parseProgress(progress, duration));
471 | }, this, opts).mapSuccess(function () {
472 | return ffprobe_simple.ffprobe_simple(output, opts);
473 | }, this);
474 | });
475 |
476 |
477 | }
478 |
479 | };
480 |
481 | });
482 |
483 |
--------------------------------------------------------------------------------
/src/ffmpeg-test.js:
--------------------------------------------------------------------------------
1 | Scoped.require([
2 | "betajs:Promise"
3 | ], function (Promise) {
4 |
5 | var DockerPolyfill = require("docker-polyfill");
6 |
7 | var VersionRegex = /^ffmpeg version (\d+)\.(\d+)\.(\d+).*$/i;
8 | var ConfigurationRegex = /^\s+configuration:\s(.*)$/i;
9 | var CodecRegexDecoders = /^\s*(.)(.)(.)(.)(.)(.)\s+(\w+)\s+(.+)\s*\(decoders:\s([^)]+)\s\)\s*(?:\(encoders:\s([^)]+)\s\))?\s*$/i;
10 | var CodecRegexEncoders = /^\s*(.)(.)(.)(.)(.)(.)\s+(\w+)\s+(.+)\s*\(encoders:\s([^)]+)\s\)\s*$/i;
11 | var CodecRegexNone = /^\s*(.)(.)(.)(.)(.)(.)\s+(\w+)\s+(.+)\s*$/i;
12 |
13 | module.exports = {
14 |
15 | ffmpeg_test: function (options) {
16 | options = options || {};
17 | var promise = Promise.create();
18 | var cmd = 'ffmpeg';
19 | var args = ['-codecs'];
20 | var file = DockerPolyfill.polyfillRun({
21 | command: options.ffmpeg_binary || "ffmpeg",
22 | argv: ["-codecs"],
23 | docker: options.docker,
24 | timeout: options.timeout
25 | });
26 | var stderr = "";
27 | file.stderr.on("data", function (data) {
28 | stderr += data;
29 | });
30 | var stdout = "";
31 | file.stdout.on("data", function (data) {
32 | stdout += data;
33 | });
34 | var timeouted = false;
35 | file.on("timeout", function () {
36 | timeouted = true;
37 | promise.asyncError("Timeout reached");
38 | });
39 | file.on("close", function (status) {
40 | if (timeouted)
41 | return;
42 | if (status !== 0) {
43 | promise.asyncError("Cannot execute ffmpeg");
44 | return;
45 | }
46 | var result = {
47 | version: {},
48 | codecs: {},
49 | capabilities: {
50 | auto_rotate: false
51 | },
52 | encoders: [],
53 | decoders: []
54 | };
55 | stderr.split("\n").forEach(function (line) {
56 | var versionMatch = VersionRegex.exec(line);
57 | if (versionMatch) {
58 | result.version.major = parseInt(versionMatch[1], 10);
59 | result.version.minor = parseInt(versionMatch[2], 10);
60 | result.version.revision = parseInt(versionMatch[3], 10);
61 | }
62 | var configurationMatch = ConfigurationRegex.exec(line);
63 | if (configurationMatch)
64 | result.configuration = configurationMatch[1].split(" ");
65 | });
66 | stdout.split("\n").forEach(function (line) {
67 | var decodersIdx = 9;
68 | var encodersIdx = 10;
69 | var codecMatch = CodecRegexDecoders.exec(line);
70 | if (!codecMatch) {
71 | decodersIdx = 10;
72 | encodersIdx = 9;
73 | codecMatch = CodecRegexEncoders.exec(line);
74 | if (!codecMatch)
75 | codecMatch = CodecRegexNone.exec(line);
76 | }
77 | if (codecMatch) {
78 | var codec = {
79 | support: {
80 | decoding: codecMatch[1] === 'D',
81 | encoding: codecMatch[2] === 'E',
82 | video: codecMatch[3] === 'V',
83 | audio: codecMatch[3] === 'A',
84 | intra: codecMatch[4] === 'I',
85 | lossy: codecMatch[5] === 'L',
86 | lossless: codecMatch[6] === 'S'
87 | },
88 | short_name: codecMatch[7],
89 | long_name: codecMatch[8],
90 | decoders: codecMatch[decodersIdx] ? codecMatch[decodersIdx].split(" ") : [],
91 | encoders: codecMatch[encodersIdx] ? codecMatch[encodersIdx].split(" ") : []
92 | };
93 | result.codecs[codecMatch[7]] = codec;
94 | result.decoders = result.decoders.concat(codec.decoders);
95 | result.encoders = result.encoders.concat(codec.encoders);
96 | }
97 | });
98 | if (result.version.major >= 3)
99 | result.capabilities.auto_rotate = true;
100 | promise.asyncSuccess(result);
101 | });
102 | return promise;
103 | }
104 |
105 | };
106 |
107 | });
108 |
109 |
--------------------------------------------------------------------------------
/src/ffmpeg-volume-detect.js:
--------------------------------------------------------------------------------
1 | Scoped.require([
2 | "betajs:Promise",
3 | "betajs:Types"
4 | ], function (Promise, Types) {
5 |
6 | var DockerPolyfill = require("docker-polyfill");
7 |
8 | var mean_volume_regex = /.*mean_volume:\s*([^[\s]+)\s*/g;
9 | var max_volume_regex = /.*max_volume:\s*([^[\s]+)\s*/g;
10 |
11 | module.exports = {
12 |
13 | ffmpeg_volume_detect: function (inputFile, options) {
14 | options = options || {};
15 | var promise = Promise.create();
16 | var file = DockerPolyfill.polyfillRun({
17 | command: options.ffmpeg_binary || "ffmpeg",
18 | argv: [
19 | "-i",
20 | inputFile,
21 | "-vn",
22 | "-af",
23 | "volumedetect",
24 | "-f",
25 | "null",
26 | "/dev/null"
27 | ],
28 | docker: options.docker,
29 | timeout: options.timeout
30 | });
31 | var lines = "";
32 | file.stderr.on("data", function (data) {
33 | var line = data.toString();
34 | lines += line;
35 | });
36 | file.stderr.on("end", function (data) {
37 | lines += data;
38 | });
39 | var timeouted = false;
40 | file.on("timeout", function () {
41 | timeouted = true;
42 | promise.asyncError("Timeout reached");
43 | });
44 | file.on("close", function (status) {
45 | if (timeouted)
46 | return;
47 | if (status !== 0) {
48 | promise.asyncError("Cannot read file");
49 | return;
50 | }
51 | lines = lines.split("\n");
52 | if (status === 0) {
53 | var mean_volume = null;
54 | var max_volume = null;
55 | lines.forEach(function (line) {
56 | var mean_volume_match = mean_volume_regex.exec(line);
57 | if (mean_volume_match)
58 | mean_volume = parseFloat(mean_volume_match[1]);
59 | var max_volume_match = max_volume_regex.exec(line);
60 | if (max_volume_match)
61 | max_volume = parseFloat(max_volume_match[1]);
62 | });
63 | promise.asyncSuccess({
64 | mean_volume: mean_volume,
65 | max_volume: max_volume
66 | });
67 | } else {
68 | promise.asyncError(lines[lines.length - 2]);
69 | }
70 | });
71 | return promise;
72 | }
73 |
74 | };
75 |
76 | });
77 |
78 |
--------------------------------------------------------------------------------
/src/ffmpeg.js:
--------------------------------------------------------------------------------
1 | Scoped.require([
2 | "betajs:Promise",
3 | "betajs:Types"
4 | ], function(Promise, Types) {
5 |
6 | var DockerPolyfill = require("docker-polyfill");
7 | var FS = require("fs");
8 |
9 | var progress_regex = /\s*([^[=\s]+)\s*=\s*([^[=\s]+)/g;
10 |
11 | module.exports = {
12 |
13 | ffmpeg: function(files, options, output, eventCallback, eventContext, opts) {
14 | opts = opts || {};
15 | var promise = Promise.create();
16 | var args = [];
17 | if (Types.is_string(files))
18 | files = [files];
19 | files.forEach(function(file) {
20 | args.push("-i");
21 | args.push(file);
22 | });
23 | args = args.concat(options);
24 | if (output) { //when running the playlist script the output won't be used
25 | args.push("-y");
26 | args.push(output);
27 | // Touch file so docker keeps the right owner
28 | FS.writeFileSync(output, "");
29 | // console.log(args.join(" "));
30 | }
31 | var file = DockerPolyfill.polyfillRun({
32 | command: opts.ffmpeg_binary || "ffmpeg",
33 | argv: args.join(" ").split(" "),
34 | docker: opts.docker,
35 | timeout: opts.timeout
36 | });
37 | var lines = "";
38 | file.stderr.on("data", function(data) {
39 | var line = data.toString();
40 | lines += line;
41 | if (line.indexOf("frame=") === 0) {
42 | var progress = line.trim();
43 | var result = {};
44 | while (true) {
45 | var m = progress_regex.exec(progress);
46 | if (!m)
47 | break;
48 | result[m[1]] = m[2];
49 | }
50 | if (eventCallback)
51 | eventCallback.call(eventContext || this, result);
52 | }
53 | });
54 | file.stderr.on("end", function(data) {
55 | lines += data;
56 | });
57 | var timeouted = false;
58 | file.on("timeout", function() {
59 | timeouted = true;
60 | promise.asyncError({
61 | message: "Timeout reached",
62 | command: args.join(" ")
63 | });
64 | });
65 | file.on("close", function(status) {
66 | if (timeouted)
67 | return;
68 | if (status === 0) {
69 | promise.asyncSuccess();
70 | } else {
71 | var errlines = lines.split("\n");
72 | promise.asyncError({
73 | message: errlines[errlines.length - 2],
74 | logs: lines,
75 | command: args.join(" ")
76 | });
77 | }
78 | });
79 | return promise;
80 | }
81 |
82 | };
83 |
84 | });
85 |
86 |
--------------------------------------------------------------------------------
/src/ffprobe-simple.js:
--------------------------------------------------------------------------------
1 | Scoped.require([
2 | "betajs:Promise",
3 | "betajs:Strings"
4 | ], function (Promise, Strings) {
5 |
6 | module.exports = {
7 |
8 | ffprobe_simple: function (file, options) {
9 | var parseIntUndefined = function (source, key) {
10 | return key in source ? parseInt(source[key], 10) : undefined;
11 | };
12 |
13 | if (!Strings.STRICT_URL_REGEX.test(file) && !require('fs').existsSync(file))
14 | return Promise.error("File does not exist");
15 | return require(__dirname + "/ffprobe.js").ffprobe(file, options).mapSuccess(function (json) {
16 | if (!json.format || !json.streams)
17 | return Promise.error("Cannot read file");
18 | var result = {
19 | filename: json.format.filename,
20 | stream_count: json.format.nb_streams,
21 | size: parseInt(json.format.size, 10),
22 | bit_rate: parseInt(json.format.bit_rate, 10),
23 | start_time: parseFloat(json.format.start_time),
24 | duration: parseFloat(json.format.duration),
25 | format_name: json.format.format_long_name,
26 | format_extensions: json.format.format_name.split(","),
27 | format_default_extension: Strings.splitFirst(json.format.format_name, ",").head
28 | };
29 | json.streams.forEach(function (stream) {
30 | if (stream.codec_type === 'video') {
31 | var rotation = stream.tags && stream.tags.rotate ? parseInt(stream.tags.rotate, 10) : 0;
32 | var video = {
33 | index: stream.index,
34 | rotation: rotation,
35 | width: stream.width,
36 | height: stream.height,
37 | rotated_width: rotation % 180 === 0 ? stream.width : stream.height,
38 | rotated_height: rotation % 180 === 0 ? stream.height : stream.width,
39 | codec_name: stream.codec_tag_string,
40 | codec_long_name: stream.codec_long_name,
41 | codec_profile: stream.profile,
42 | bit_rate: parseIntUndefined(stream, "bit_rate"),
43 | frames: parseIntUndefined(stream, "nb_frames")
44 | };
45 | if (json.format.format_name === "image" || json.format.format_name === "image2" || stream.codec_name === 'png')
46 | result.image = video;
47 | else
48 | result.video = video;
49 | } else if (stream.codec_type === 'audio') {
50 | result.audio = {
51 | index: stream.index,
52 | codec_name: stream.codec_name,
53 | codec_long_name: stream.codec_long_name,
54 | codec_profile: stream.profile,
55 | audio_channels: stream.channels,
56 | sample_rate: parseIntUndefined(stream, "sample_rate"),
57 | bit_rate: parseIntUndefined(stream, "bit_rate")
58 | };
59 | }
60 | });
61 | return result;
62 | });
63 | }
64 |
65 | };
66 |
67 | });
68 |
--------------------------------------------------------------------------------
/src/ffprobe.js:
--------------------------------------------------------------------------------
1 | Scoped.require([
2 | "betajs:Promise"
3 | ], function (Promise) {
4 |
5 | var DockerPolyfill = require("docker-polyfill");
6 |
7 | module.exports = {
8 |
9 | ffprobe: function (fileName, options) {
10 | options = options || {};
11 | var promise = Promise.create();
12 | var file = DockerPolyfill.polyfillRun({
13 | command: options.ffprobe_binary || "ffprobe",
14 | argv: ['-v', 'quiet', '-print_format', 'json', '-show_format', '-show_streams', fileName],
15 | docker: options.docker,
16 | timeout: options.timeout
17 | });
18 | /*
19 | var stderr = "";
20 | file.stderr.on("data", function (data) {
21 | stderr += data;
22 | });
23 | */
24 | var stdout = "";
25 | file.stdout.on("data", function (data) {
26 | stdout += data;
27 | });
28 | var timeouted = false;
29 | file.on("timeout", function () {
30 | timeouted = true;
31 | promise.asyncError("Timeout reached");
32 | });
33 | file.on("close", function (status) {
34 | if (timeouted)
35 | return;
36 | if (status !== 0) {
37 | promise.asyncError("Cannot read file");
38 | return;
39 | }
40 | try {
41 | var success = JSON.parse(stdout);
42 | promise.asyncSuccess(success);
43 | } catch (e) {
44 | promise.asyncError("FFProbe Parse error: " + stdout);
45 | }
46 | });
47 | return promise;
48 | }
49 |
50 | };
51 |
52 | });
53 |
54 |
--------------------------------------------------------------------------------
/tests/assets/audio.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jsonize/js-ffmpeg/612440a4ed055e7b06b154976124c015ee85f731/tests/assets/audio.mp3
--------------------------------------------------------------------------------
/tests/assets/etc_passwd_xbin.avi:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jsonize/js-ffmpeg/612440a4ed055e7b06b154976124c015ee85f731/tests/assets/etc_passwd_xbin.avi
--------------------------------------------------------------------------------
/tests/assets/iphone_rotated.mov:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jsonize/js-ffmpeg/612440a4ed055e7b06b154976124c015ee85f731/tests/assets/iphone_rotated.mov
--------------------------------------------------------------------------------
/tests/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jsonize/js-ffmpeg/612440a4ed055e7b06b154976124c015ee85f731/tests/assets/logo.png
--------------------------------------------------------------------------------
/tests/assets/novideo.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jsonize/js-ffmpeg/612440a4ed055e7b06b154976124c015ee85f731/tests/assets/novideo.mp4
--------------------------------------------------------------------------------
/tests/assets/video-640-360.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jsonize/js-ffmpeg/612440a4ed055e7b06b154976124c015ee85f731/tests/assets/video-640-360.mp4
--------------------------------------------------------------------------------
/tests/tests/ffmpeg-simple-gif.js:
--------------------------------------------------------------------------------
1 | var ffmpeg = require(__dirname + "/../../index.js");
2 | var settings = require(__dirname + "/settings.js");
3 |
4 | var TEMP_GIF = __dirname + "/../../temp/output-test.gif";
5 | var VIDEO_FILE = __dirname + "/../assets/video-640-360.mp4";
6 |
7 | QUnit.test("ffmpeg-simple gif transcoding", function(assert) {
8 | var done = assert.async();
9 | ffmpeg.ffmpeg_simple(VIDEO_FILE, {
10 | output_type: "gif"
11 | }, TEMP_GIF, null, null, settings).callback(function (error, value) {
12 | assert.ok(!error);
13 | done();
14 | });
15 | });
16 |
17 | QUnit.test("ffmpeg-simple gif with 12 fps", function(assert) {
18 | var done = assert.async();
19 | ffmpeg.ffmpeg_simple(VIDEO_FILE, {
20 | output_type: "gif",
21 | framerate: 12
22 | }, TEMP_GIF, null, null, settings).callback(function (error, value) {
23 | assert.ok(!error);
24 | assert.equal(value.video.frames, 12);
25 | done();
26 | });
27 | });
28 |
29 | QUnit.test("ffmpeg-simple gif with 320 width", function(assert) {
30 | var done = assert.async();
31 | ffmpeg.ffmpeg_simple(VIDEO_FILE, {
32 | output_type: "gif",
33 | width: 320
34 | }, TEMP_GIF, null, null, settings).callback(function (error, value) {
35 | assert.ok(!error);
36 | assert.equal(value.video.width, 320);
37 | done();
38 | });
39 | });
40 |
41 | QUnit.test("ffmpeg-simple gif with 240 height", function(assert) {
42 | var done = assert.async();
43 | ffmpeg.ffmpeg_simple(VIDEO_FILE, {
44 | output_type: "gif",
45 | height: 240
46 | }, TEMP_GIF, null, null, settings).callback(function (error, value) {
47 | assert.ok(!error);
48 | assert.equal(value.video.height, 240);
49 | done();
50 | });
51 | });
52 |
53 | QUnit.test("ffmpeg-simple gif with 0.8 second duration", function(assert) {
54 | var done = assert.async();
55 | ffmpeg.ffmpeg_simple(VIDEO_FILE, {
56 | output_type: "gif",
57 | time_end: 0.8
58 | }, TEMP_GIF, null, null, settings).callback(function (error, value) {
59 | assert.ok(!error);
60 | assert.equal(value.duration, 0.8);
61 | done();
62 | });
63 | });
64 |
65 |
--------------------------------------------------------------------------------
/tests/tests/ffmpeg-simple-image.js:
--------------------------------------------------------------------------------
1 | var ffmpeg = require(__dirname + "/../../index.js");
2 | var settings = require(__dirname + "/settings.js");
3 |
4 | var TEMP_IMAGE = __dirname + "/../../temp/output-test-watermark.png";
5 | var WATERMARK_FILE = __dirname + "/../assets/logo.png";
6 | var EXPLOIT_FILE = __dirname + "/../assets/etx_passwd_xbin.avi";
7 |
8 | QUnit.test("ffmpeg-simple with logo", function(assert) {
9 | var done = assert.async();
10 | ffmpeg.ffmpeg_simple(WATERMARK_FILE, {
11 | output_type: "image"
12 | }, TEMP_IMAGE, null, null, settings).callback(function (error, value) {
13 | assert.ok(!error);
14 | done();
15 | });
16 | });
17 |
18 |
19 | QUnit.test("ffmpeg-simple exploit", function(assert) {
20 | var done = assert.async();
21 | ffmpeg.ffmpeg_simple(EXPLOIT_FILE, {
22 | output_type: "image"
23 | }, TEMP_IMAGE, null, null, settings).callback(function (error, value) {
24 | assert.ok(error);
25 | done();
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/tests/tests/ffmpeg-simple-rotation.js:
--------------------------------------------------------------------------------
1 | var ffmpeg = require(__dirname + "/../../index.js");
2 | var settings = require(__dirname + "/settings.js");
3 |
4 | var ROTATED_MOV_VIDEO = __dirname + "/../assets/iphone_rotated.mov";
5 | var TEMP_MP4_VIDEO = __dirname + "/../../temp/output-test.mp4";
6 | var STANDARD_MP4 = __dirname + "/../assets/video-640-360.mp4";
7 |
8 |
9 | QUnit.test("ffmpeg-simple mov to mp4", function(assert) {
10 | var done = assert.async();
11 | ffmpeg.ffmpeg_simple(ROTATED_MOV_VIDEO, {}, TEMP_MP4_VIDEO, null, null, settings).callback(function (error, value) {
12 | assert.ok(!error);
13 | assert.equal(value.video.rotation, 0);
14 | assert.equal(value.video.width, 320);
15 | assert.equal(value.video.height, 568);
16 | done();
17 | });
18 | });
19 |
20 | QUnit.test("ffmpeg-simple mov to mp4 2", function(assert) {
21 | var done = assert.async();
22 | ffmpeg.ffmpeg_simple(ROTATED_MOV_VIDEO, {rotate: 90}, TEMP_MP4_VIDEO, null, null, settings).callback(function (error, value) {
23 | assert.ok(!error);
24 | assert.equal(value.video.rotation, 0);
25 | assert.equal(value.video.width, 568);
26 | assert.equal(value.video.height, 320);
27 | done();
28 | });
29 | });
30 |
31 | QUnit.test("ffmpeg-simple mp4 to mp4", function(assert) {
32 | var done = assert.async();
33 | ffmpeg.ffmpeg_simple(STANDARD_MP4, {}, TEMP_MP4_VIDEO, null, null, settings).callback(function (error, value) {
34 | assert.ok(!error);
35 | assert.equal(value.video.rotation, 0);
36 | assert.equal(value.video.width, 640);
37 | assert.equal(value.video.height, 360);
38 | done();
39 | });
40 | });
41 |
42 | QUnit.test("ffmpeg-simple mp4 to mp4 2", function(assert) {
43 | var done = assert.async();
44 | ffmpeg.ffmpeg_simple(STANDARD_MP4, {rotate: 90}, TEMP_MP4_VIDEO, null, null, settings).callback(function (error, value) {
45 | assert.ok(!error);
46 | assert.equal(value.video.rotation, 0);
47 | assert.equal(value.video.width, 360);
48 | assert.equal(value.video.height, 640);
49 | done();
50 | });
51 | });
52 |
--------------------------------------------------------------------------------
/tests/tests/ffmpeg-simple-sizing.js:
--------------------------------------------------------------------------------
1 | var ffmpeg = require(__dirname + "/../../index.js");
2 | var settings = require(__dirname + "/settings.js");
3 |
4 | var STANDARD_MP4 = __dirname + "/../assets/video-640-360.mp4";
5 | var TEMP_MP4_VIDEO = __dirname + "/../../temp/output-test.mp4";
6 |
7 | QUnit.test("ffmpeg-simple shrink same fixed ratio", function(assert) {
8 | var done = assert.async();
9 | ffmpeg.ffmpeg_simple(STANDARD_MP4, {
10 | width: 320,
11 | height: 180,
12 | ratio_strategy: "fixed"
13 | }, TEMP_MP4_VIDEO, null, null, settings).callback(function (error, value) {
14 | assert.ok(!error);
15 | assert.equal(value.video.width, 320);
16 | assert.equal(value.video.height, 180);
17 | done();
18 | });
19 | });
20 |
21 | QUnit.test("ffmpeg-simple stretch same fixed ratio", function(assert) {
22 | var done = assert.async();
23 | ffmpeg.ffmpeg_simple(STANDARD_MP4, {
24 | width: 960,
25 | height: 540,
26 | ratio_strategy: "fixed",
27 | stretch_strategy: "stretch-crop"
28 | }, TEMP_MP4_VIDEO, null, null, settings).callback(function (error, value) {
29 | assert.ok(!error);
30 | assert.equal(value.video.width, 960);
31 | assert.equal(value.video.height, 540);
32 | done();
33 | });
34 | });
35 |
36 | QUnit.test("ffmpeg-simple shrink smaller ratio, shrink ratio", function(assert) {
37 | var done = assert.async();
38 | ffmpeg.ffmpeg_simple(STANDARD_MP4, {
39 | width: 320,
40 | height: 200,
41 | ratio_strategy: "shrink"
42 | }, TEMP_MP4_VIDEO, null, null, settings).callback(function (error, value) {
43 | assert.ok(!error);
44 | assert.equal(value.video.width, 320);
45 | assert.equal(value.video.height, 180);
46 | done();
47 | });
48 | });
49 |
50 | QUnit.test("ffmpeg-simple shrink greater ratio, shrink ratio", function(assert) {
51 | var done = assert.async();
52 | ffmpeg.ffmpeg_simple(STANDARD_MP4, {
53 | width: 400,
54 | height: 180,
55 | ratio_strategy: "shrink"
56 | }, TEMP_MP4_VIDEO, null, null, settings).callback(function (error, value) {
57 | assert.ok(!error);
58 | assert.equal(value.video.width, 320);
59 | assert.equal(value.video.height, 180);
60 | done();
61 | });
62 | });
63 |
64 |
65 |
66 | QUnit.test("ffmpeg-simple shrink smaller ratio, stretch ratio", function(assert) {
67 | var done = assert.async();
68 | ffmpeg.ffmpeg_simple(STANDARD_MP4, {
69 | width: 300,
70 | height: 180,
71 | ratio_strategy: "stretch"
72 | }, TEMP_MP4_VIDEO, null, null, settings).callback(function (error, value) {
73 | assert.ok(!error);
74 | assert.equal(value.video.width, 320);
75 | assert.equal(value.video.height, 180);
76 | done();
77 | });
78 | });
79 |
80 | QUnit.test("ffmpeg-simple shrink greater ratio, stretch ratio", function(assert) {
81 | var done = assert.async();
82 | ffmpeg.ffmpeg_simple(STANDARD_MP4, {
83 | width: 320,
84 | height: 140,
85 | ratio_strategy: "stretch"
86 | }, TEMP_MP4_VIDEO, null, null, settings).callback(function (error, value) {
87 | assert.ok(!error);
88 | assert.equal(value.video.width, 320);
89 | assert.equal(value.video.height, 180);
90 | done();
91 | });
92 | });
93 |
94 | QUnit.test("ffmpeg-simple shrink smaller ratio, fixed ratio, shrink-pad", function (assert) {
95 | var done = assert.async();
96 | ffmpeg.ffmpeg_simple(STANDARD_MP4, {
97 | width: 240,
98 | height: 180,
99 | ratio_strategy: "fixed",
100 | shrink_strategy: "shrink-pad"
101 | }, TEMP_MP4_VIDEO, null, null, settings).callback(function (error, value) {
102 | assert.ok(!error);
103 | assert.equal(value.video.width, 240);
104 | assert.equal(value.video.height, 180);
105 | done();
106 | });
107 | });
108 |
109 | QUnit.test("ffmpeg-simple shrink greater ratio, fixed ratio, shrink-pad", function (assert) {
110 | var done = assert.async();
111 | ffmpeg.ffmpeg_simple(STANDARD_MP4, {
112 | width: 360,
113 | height: 140,
114 | ratio_strategy: "fixed",
115 | shrink_strategy: "shrink-pad"
116 | }, TEMP_MP4_VIDEO, null, null, settings).callback(function (error, value) {
117 | assert.ok(!error);
118 | assert.equal(value.video.width, 360);
119 | assert.equal(value.video.height, 140);
120 | done();
121 | });
122 | });
123 |
124 | QUnit.test("ffmpeg-simple shrink smaller ratio, fixed ratio, shrink-crop", function (assert) {
125 | var done = assert.async();
126 | ffmpeg.ffmpeg_simple(STANDARD_MP4, {
127 | width: 240,
128 | height: 180,
129 | ratio_strategy: "fixed",
130 | shrink_strategy: "shrink-crop"
131 | }, TEMP_MP4_VIDEO, null, null, settings).callback(function (error, value) {
132 | assert.ok(!error);
133 | assert.equal(value.video.width, 240);
134 | assert.equal(value.video.height, 180);
135 | done();
136 | });
137 | });
138 |
139 | QUnit.test("ffmpeg-simple shrink greater ratio, fixed ratio, shrink-crop", function (assert) {
140 | var done = assert.async();
141 | ffmpeg.ffmpeg_simple(STANDARD_MP4, {
142 | width: 360,
143 | height: 140,
144 | ratio_strategy: "fixed",
145 | shrink_strategy: "shrink-crop"
146 | }, TEMP_MP4_VIDEO, null, null, settings).callback(function (error, value) {
147 | assert.ok(!error);
148 | assert.equal(value.video.width, 360);
149 | assert.equal(value.video.height, 140);
150 | done();
151 | });
152 | });
153 |
154 | QUnit.test("ffmpeg-simple shrink smaller ratio, fixed ratio, crop", function (assert) {
155 | var done = assert.async();
156 | ffmpeg.ffmpeg_simple(STANDARD_MP4, {
157 | width: 240,
158 | height: 180,
159 | ratio_strategy: "fixed",
160 | shrink_strategy: "crop"
161 | }, TEMP_MP4_VIDEO, null, null, settings).callback(function (error, value) {
162 | assert.ok(!error);
163 | assert.equal(value.video.width, 240);
164 | assert.equal(value.video.height, 180);
165 | done();
166 | });
167 | });
168 |
169 | QUnit.test("ffmpeg-simple shrink greater ratio, fixed ratio, crop", function (assert) {
170 | var done = assert.async();
171 | ffmpeg.ffmpeg_simple(STANDARD_MP4, {
172 | width: 360,
173 | height: 140,
174 | ratio_strategy: "fixed",
175 | shrink_strategy: "crop"
176 | }, TEMP_MP4_VIDEO, null, null, settings).callback(function (error, value) {
177 | assert.ok(!error);
178 | assert.equal(value.video.width, 360);
179 | assert.equal(value.video.height, 140);
180 | done();
181 | });
182 | });
183 |
184 |
185 |
186 | QUnit.test("ffmpeg-simple stretch smaller ratio, fixed ratio, stretch-pad", function (assert) {
187 | var done = assert.async();
188 | ffmpeg.ffmpeg_simple(STANDARD_MP4, {
189 | width: 960,
190 | height: 600,
191 | ratio_strategy: "fixed",
192 | stretch_strategy: "stretch-pad"
193 | }, TEMP_MP4_VIDEO, null, null, settings).callback(function (error, value) {
194 | assert.ok(!error);
195 | assert.equal(value.video.width, 960);
196 | assert.equal(value.video.height, 600);
197 | done();
198 | });
199 | });
200 |
201 |
202 |
203 | QUnit.test("ffmpeg-simple stretch greater ratio, fixed ratio, stretch-pad", function (assert) {
204 | var done = assert.async();
205 | ffmpeg.ffmpeg_simple(STANDARD_MP4, {
206 | width: 960,
207 | height: 500,
208 | ratio_strategy: "fixed",
209 | stretch_strategy: "stretch-pad"
210 | }, TEMP_MP4_VIDEO, null, null, settings).callback(function (error, value) {
211 | assert.ok(!error);
212 | assert.equal(value.video.width, 960);
213 | assert.equal(value.video.height, 500);
214 | done();
215 | });
216 | });
217 |
218 | QUnit.test("ffmpeg-simple stretch smaller ratio, fixed ratio, stretch-crop", function (assert) {
219 | var done = assert.async();
220 | ffmpeg.ffmpeg_simple(STANDARD_MP4, {
221 | width: 960,
222 | height: 600,
223 | ratio_strategy: "fixed",
224 | stretch_strategy: "stretch-crop"
225 | }, TEMP_MP4_VIDEO, null, null, settings).callback(function (error, value) {
226 | assert.ok(!error);
227 | assert.equal(value.video.width, 960);
228 | assert.equal(value.video.height, 600);
229 | done();
230 | });
231 | });
232 |
233 | QUnit.test("ffmpeg-simple stretch greater ratio, fixed ratio, stretch-crop", function (assert) {
234 | var done = assert.async();
235 | ffmpeg.ffmpeg_simple(STANDARD_MP4, {
236 | width: 960,
237 | height: 500,
238 | ratio_strategy: "fixed",
239 | stretch_strategy: "stretch-crop"
240 | }, TEMP_MP4_VIDEO, null, null, settings).callback(function (error, value) {
241 | assert.ok(!error);
242 | assert.equal(value.video.width, 960);
243 | assert.equal(value.video.height, 500);
244 | done();
245 | });
246 | });
247 |
248 |
249 | QUnit.test("ffmpeg-simple stretch smaller ratio, fixed ratio, pad", function (assert) {
250 | var done = assert.async();
251 | ffmpeg.ffmpeg_simple(STANDARD_MP4, {
252 | width: 960,
253 | height: 600,
254 | ratio_strategy: "fixed",
255 | stretch_strategy: "pad"
256 | }, TEMP_MP4_VIDEO, null, null, settings).callback(function (error, value) {
257 | assert.ok(!error);
258 | assert.equal(value.video.width, 960);
259 | assert.equal(value.video.height, 600);
260 | done();
261 | });
262 | });
263 |
264 | QUnit.test("ffmpeg-simple stretch greater ratio, fixed ratio, pad", function (assert) {
265 | var done = assert.async();
266 | ffmpeg.ffmpeg_simple(STANDARD_MP4, {
267 | width: 960,
268 | height: 500,
269 | ratio_strategy: "fixed",
270 | stretch_strategy: "pad"
271 | }, TEMP_MP4_VIDEO, null, null, settings).callback(function (error, value) {
272 | assert.ok(!error);
273 | assert.equal(value.video.width, 960);
274 | assert.equal(value.video.height, 500);
275 | done();
276 | });
277 | });
278 |
279 | QUnit.test("ffmpeg-simple mixed smaller ratio, fixed ratio, stretch-crop", function (assert) {
280 | var done = assert.async();
281 | ffmpeg.ffmpeg_simple(STANDARD_MP4, {
282 | width: 600,
283 | height: 400,
284 | ratio_strategy: "fixed",
285 | mixed_strategy: "stretch-crop"
286 | }, TEMP_MP4_VIDEO, null, null, settings).callback(function (error, value) {
287 | assert.ok(!error);
288 | assert.equal(value.video.width, 600);
289 | assert.equal(value.video.height, 400);
290 | done();
291 | });
292 | });
293 |
294 |
295 | QUnit.test("ffmpeg-simple mixed greater ratio, fixed ratio, stretch-crop", function (assert) {
296 | var done = assert.async();
297 | ffmpeg.ffmpeg_simple(STANDARD_MP4, {
298 | width: 700,
299 | height: 300,
300 | ratio_strategy: "fixed",
301 | mixed_strategy: "stretch-crop"
302 | }, TEMP_MP4_VIDEO, null, null, settings).callback(function (error, value) {
303 | assert.ok(!error);
304 | assert.equal(value.video.width, 700);
305 | assert.equal(value.video.height, 300);
306 | done();
307 | });
308 | });
309 |
310 | QUnit.test("ffmpeg-simple mixed smaller ratio, fixed ratio, shrink-pad", function (assert) {
311 | var done = assert.async();
312 | ffmpeg.ffmpeg_simple(STANDARD_MP4, {
313 | width: 600,
314 | height: 400,
315 | ratio_strategy: "fixed",
316 | mixed_strategy: "shrink-pad"
317 | }, TEMP_MP4_VIDEO, null, null, settings).callback(function (error, value) {
318 | assert.ok(!error);
319 | assert.equal(value.video.width, 600);
320 | assert.equal(value.video.height, 400);
321 | done();
322 | });
323 | });
324 |
325 | QUnit.test("ffmpeg-simple mixed greater ratio, fixed ratio, shrink-crop", function (assert) {
326 | var done = assert.async();
327 | ffmpeg.ffmpeg_simple(STANDARD_MP4, {
328 | width: 700,
329 | height: 300,
330 | ratio_strategy: "fixed",
331 | mixed_strategy: "shrink-pad"
332 | }, TEMP_MP4_VIDEO, null, null, settings).callback(function (error, value) {
333 | assert.ok(!error);
334 | assert.equal(value.video.width, 700);
335 | assert.equal(value.video.height, 300);
336 | done();
337 | });
338 | });
339 |
340 |
341 | QUnit.test("ffmpeg-simple mixed smaller ratio, fixed ratio, crop-pad", function (assert) {
342 | var done = assert.async();
343 | ffmpeg.ffmpeg_simple(STANDARD_MP4, {
344 | width: 600,
345 | height: 400,
346 | ratio_strategy: "fixed",
347 | mixed_strategy: "crop-pad"
348 | }, TEMP_MP4_VIDEO, null, null, settings).callback(function (error, value) {
349 | assert.ok(!error);
350 | assert.equal(value.video.width, 600);
351 | assert.equal(value.video.height, 400);
352 | done();
353 | });
354 | });
355 |
356 | QUnit.test("ffmpeg-simple mixed greater ratio, fixed ratio, crop-pad", function (assert) {
357 | var done = assert.async();
358 | ffmpeg.ffmpeg_simple(STANDARD_MP4, {
359 | width: 700,
360 | height: 300,
361 | ratio_strategy: "fixed",
362 | mixed_strategy: "crop-pad"
363 | }, TEMP_MP4_VIDEO, null, null, settings).callback(function (error, value) {
364 | assert.ok(!error);
365 | assert.equal(value.video.width, 700);
366 | assert.equal(value.video.height, 300);
367 | done();
368 | });
369 | });
370 |
371 |
372 |
--------------------------------------------------------------------------------
/tests/tests/ffmpeg-simple-watermarks.js:
--------------------------------------------------------------------------------
1 | var ffmpeg = require(__dirname + "/../../index.js");
2 | var settings = require(__dirname + "/settings.js");
3 |
4 | var STANDARD_MP4 = __dirname + "/../assets/video-640-360.mp4";
5 | var TEMP_MP4_VIDEO = __dirname + "/../../temp/output-test-watermark.mp4";
6 | var WATERMARK_FILE = __dirname + "/../assets/logo.png";
7 |
8 | QUnit.test("ffmpeg-simple with logo", function(assert) {
9 | var done = assert.async();
10 | ffmpeg.ffmpeg_simple(STANDARD_MP4, {
11 | watermark: WATERMARK_FILE
12 | }, TEMP_MP4_VIDEO, null, null, settings).callback(function (error, value) {
13 | assert.ok(!error);
14 | done();
15 | });
16 | });
17 |
18 |
19 |
20 | QUnit.test("ffmpeg-simple with double logo", function(assert) {
21 | var done = assert.async();
22 | ffmpeg.ffmpeg_simple(STANDARD_MP4, {
23 | watermarks: [{
24 | watermark: WATERMARK_FILE,
25 | watermark_size: 0.25,
26 | watermark_x: 0.95,
27 | watermark_y: 0.95
28 | }, {
29 | watermark: WATERMARK_FILE,
30 | watermark_size: 0.25,
31 | watermark_x: 0.05,
32 | watermark_y: 0.05
33 | }]
34 | }, TEMP_MP4_VIDEO, null, null, settings).callback(function (error, value) {
35 | assert.ok(!error);
36 | done();
37 | });
38 | });
39 |
--------------------------------------------------------------------------------
/tests/tests/ffmpeg-volume-detect.js:
--------------------------------------------------------------------------------
1 | var ffmpeg = require(__dirname + "/../../index.js");
2 | var settings = require(__dirname + "/settings.js");
3 |
4 | var STANDARD_MP4 = __dirname + "/../assets/video-640-360.mp4";
5 | var TEMP_MP4_VIDEO = __dirname + "/../../temp/output-test-volume.mp4";
6 |
7 | QUnit.test("ffmpeg volume detect", function(assert) {
8 | var done = assert.async();
9 | ffmpeg.ffmpeg_volume_detect(STANDARD_MP4, settings).callback(function (error, value) {
10 | assert.equal(value.mean_volume, -36.5);
11 | assert.equal(value.max_volume, -25.8);
12 | assert.ok(!error);
13 | done();
14 | });
15 | });
16 |
17 |
18 | QUnit.test("ffmpeg volume normalization", function(assert) {
19 | var done = assert.async();
20 | ffmpeg.ffmpeg_simple(STANDARD_MP4, {
21 | normalize_audio: true
22 | }, TEMP_MP4_VIDEO, null, null, settings).callback(function (error, value) {
23 | assert.ok(!error);
24 | done();
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/tests/tests/ffmpeg.js:
--------------------------------------------------------------------------------
1 | var ffmpeg = require(__dirname + "/../../index.js");
2 | var settings = require(__dirname + "/settings.js");
3 |
4 | var ROTATED_MOV_VIDEO = __dirname + "/../assets/iphone_rotated.mov";
5 | var TEMP_MP4_VIDEO = __dirname + "/../../temp/output-test.mp4";
6 | var EXPLOIT_FILE = __dirname + "/../assets/etx_passwd_xbin.avi";
7 |
8 |
9 | QUnit.test("ffmpeg mov to mp4", function(assert) {
10 | var done = assert.async();
11 | ffmpeg.ffmpeg(ROTATED_MOV_VIDEO, [], TEMP_MP4_VIDEO, null, null, settings).callback(function (error, value) {
12 | assert.ok(!error);
13 | done();
14 | });
15 | });
16 |
17 | QUnit.test("ffmpeg timeout", function (assert) {
18 | var done = assert.async();
19 | ffmpeg.ffmpeg(ROTATED_MOV_VIDEO, [], TEMP_MP4_VIDEO, null, null, BetaJS.Objs.extend({timeout: 1}, settings)).callback(function (error, value) {
20 | assert.equal(error.message, "Timeout reached");
21 | done();
22 | });
23 | });
24 |
25 | QUnit.test("ffmpeg exploit", function (assert) {
26 | var done = assert.async();
27 | ffmpeg.ffmpeg(EXPLOIT_FILE, [], TEMP_MP4_VIDEO, null, null, settings).callback(function (error, value) {
28 | assert.ok(error);
29 | done();
30 | });
31 | });
--------------------------------------------------------------------------------
/tests/tests/ffprobe-simple.js:
--------------------------------------------------------------------------------
1 | var ffmpeg = require(__dirname + "/../../index.js");
2 | var settings = require(__dirname + "/settings.js");
3 |
4 | var NOT_EXISTING_VIDEO = __dirname + "/notexisting.mp4";
5 | var ROTATED_MOV_VIDEO = __dirname + "/../assets/iphone_rotated.mov";
6 | var NO_VIDEO = __dirname + "/../assets/novideo.mp4";
7 | var EXPLOIT_FILE = __dirname + "/../assets/etx_passwd_xbin.avi";
8 |
9 | QUnit.test("ffprobe-simple not existing", function(assert) {
10 | var done = assert.async();
11 | ffmpeg.ffprobe_simple(NOT_EXISTING_VIDEO, settings).callback(function(error, value) {
12 | assert.equal(error, 'File does not exist');
13 | done();
14 | });
15 | });
16 |
17 | QUnit.test("ffprobe-simple no video", function(assert) {
18 | var done = assert.async();
19 | ffmpeg.ffprobe_simple(NO_VIDEO, settings).callback(function(error, value) {
20 | assert.equal(error, 'Cannot read file');
21 | done();
22 | });
23 | });
24 |
25 | QUnit.test("ffprobe-simple rotated mov", function(assert) {
26 | var done = assert.async();
27 | ffmpeg.ffprobe_simple(ROTATED_MOV_VIDEO, settings).callback(function(error, value) {
28 | delete value.filename;
29 | delete value.format_name;
30 | delete value.audio.codec_long_name;
31 | delete value.audio.codec_profile;
32 | delete value.video.codec_long_name;
33 | delete value.video.codec_profile;
34 | assert.deepEqual(value, {
35 | //filename : ROTATED_MOV_VIDEO,
36 | stream_count : 2,
37 | size : 159993,
38 | bit_rate : 581352,
39 | start_time : 0,
40 | duration : 2.201667,
41 | //format_name : 'QuickTime / MOV',
42 | format_extensions : [ 'mov', 'mp4', 'm4a', '3gp', '3g2', 'mj2' ],
43 | format_default_extension : 'mov',
44 | audio : {
45 | index : 0,
46 | codec_name : 'aac',
47 | //codec_long_name : 'AAC (Advanced Audio Coding)',
48 | //codec_profile : 'LC',
49 | audio_channels : 1,
50 | sample_rate : 44100,
51 | bit_rate : 61893
52 | },
53 | video : {
54 | index : 1,
55 | rotation : 90,
56 | width : 568,
57 | height : 320,
58 | rotated_width : 320,
59 | rotated_height : 568,
60 | codec_name : 'avc1',
61 | //codec_long_name : 'H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10',
62 | //codec_profile : 'Baseline',
63 | bit_rate : 507677,
64 | frames: 66
65 | }
66 | });
67 | done();
68 | });
69 | });
70 |
71 | QUnit.test("ffprobe-simple exploit", function (assert) {
72 | var done = assert.async();
73 | ffmpeg.ffprobe_simple(EXPLOIT_FILE, settings).callback(function (error, value) {
74 | assert.ok(error);
75 | done();
76 | });
77 | });
--------------------------------------------------------------------------------
/tests/tests/ffprobe.js:
--------------------------------------------------------------------------------
1 | var ffmpeg = require(__dirname + "/../../index.js");
2 | var settings = require(__dirname + "/settings.js");
3 |
4 | var ROTATED_MOV_VIDEO = __dirname + "/../assets/iphone_rotated.mov";
5 | var IMAGE_FILE = __dirname + "/../assets/logo.png";
6 |
7 | var EXPLOIT_FILE = __dirname + "/../assets/etx_passwd_xbin.avi";
8 |
9 | QUnit.test("ffprobe rotated mov", function (assert) {
10 | var done = assert.async();
11 | ffmpeg.ffprobe(ROTATED_MOV_VIDEO, settings).callback(function (error, value) {
12 | assert.deepEqual(value.format.nb_streams, 2);
13 | done();
14 | });
15 | });
16 |
17 | QUnit.test("ffprobe image", function (assert) {
18 | var done = assert.async();
19 | ffmpeg.ffprobe(IMAGE_FILE, settings).callback(function(error, value) {
20 | assert.deepEqual(value.format.nb_streams, 1);
21 | done();
22 | });
23 | });
24 |
25 |
26 | QUnit.test("ffprobe exploit", function (assert) {
27 | var done = assert.async();
28 | ffmpeg.ffprobe(EXPLOIT_FILE, settings).callback(function(error, value) {
29 | assert.ok(error);
30 | done();
31 | });
32 | });
--------------------------------------------------------------------------------
/tests/tests/settings.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | test_ffmpeg: true
3 | /*
4 | test_info: {
5 | capabilities: {
6 | auto_rotate: true
7 | }
8 | },
9 | */
10 | /*
11 | docker: {
12 | "container": "jrottenberg/ffmpeg",
13 | "proxy": "localhost:1234",
14 | "replaceArguments": {
15 | "libfaac": "libfdk_aac",
16 | "^/var": "/private/var"
17 | },
18 | "preprocessFiles": {
19 | "chown": "TODO",
20 | "chmod": 666,
21 | "mkdirs": true
22 | },
23 | "postprocessFiles": {
24 | "chown": "daemon",
25 | "chmod": 666,
26 | "recoverChown": true,
27 | "recoverChmod": true
28 | }
29 | }*/
30 | };
--------------------------------------------------------------------------------
/types/betajs.d.ts:
--------------------------------------------------------------------------------
1 | export type BetaJSPromise = {
2 | toNativePromise: () => Promise
3 | }
4 |
--------------------------------------------------------------------------------
/types/ffmpeg-faststart.d.ts:
--------------------------------------------------------------------------------
1 | import { BetaJSPromise } from "./betajs";
2 | import { TFFmpegOpts } from "./opts";
3 | import { TFFmpegProgress } from "./ffmpeg";
4 |
5 | export function ffmpeg_faststart(
6 | file: string | string[],
7 | output?: string,
8 | eventCallback?: (progress: TFFmpegProgress) => unknown,
9 | eventContext?: Record,
10 | opts?: TFFmpegOpts
11 | ): BetaJSPromise;
12 |
--------------------------------------------------------------------------------
/types/ffmpeg-graceful.d.ts:
--------------------------------------------------------------------------------
1 | import { BetaJSPromise } from "./betajs";
2 | import { TFFmpegSimpleOptions, TFFmpegSimpleProgress, TFFmpegSimpleResponse } from "./ffmpeg-simple";
3 | import { TOpts } from "./opts";
4 |
5 | export function ffmpeg_graceful(
6 | files: string | string[],
7 | options?: TFFmpegSimpleOptions,
8 | output?: string,
9 | eventCallback?: (progress: TFFmpegSimpleProgress) => unknown,
10 | eventContext?: Record,
11 | opts?: TOpts
12 | ): BetaJSPromise;
13 |
--------------------------------------------------------------------------------
/types/ffmpeg-multi-pass.d.ts:
--------------------------------------------------------------------------------
1 | import { BetaJSPromise } from "./betajs";
2 | import { TFFmpegProgress } from "./ffmpeg";
3 |
4 | export function ffmpeg_multi_pass(
5 | files: string | string[],
6 | options?: string[],
7 | passes?: number,
8 | output?: string,
9 | eventCallback?: (progress: TFFmpegProgress) => unknown,
10 | eventContext?: Record
11 | ): BetaJSPromise;
12 |
--------------------------------------------------------------------------------
/types/ffmpeg-playlist.d.ts:
--------------------------------------------------------------------------------
1 | import { BetaJSPromise } from "./betajs";
2 | import { TFFmpegSimpleOptions, TFFmpegSimpleProgress } from "./ffmpeg-simple"
3 | import { TOpts } from "./opts";
4 |
5 | export function ffmpeg_playlist(
6 | files: string | string[],
7 | options?: TFFmpegPlaylistOptions,
8 | output?: string,
9 | eventCallback?: (progress: TFFmpegSimpleProgress) => unknown,
10 | eventContext?: Record,
11 | opts?: TOpts
12 | ): BetaJSPromise;
13 |
14 | export function ffmpeg_playlist_raw(
15 | files: string | string[],
16 | options?: TFFmpegPlaylistOptions,
17 | output?: string,
18 | eventCallback?: (progress: TFFmpegSimpleProgress) => unknown,
19 | eventContext?: Record,
20 | opts?: TOpts
21 | ): BetaJSPromise;
22 |
23 | export type TFFmpegPlaylistReponse = {playlist: `${string}/playlist.m3u8`};
24 |
25 | export type TFFmpegPlaylistOptions = TFFmpegSimpleOptions & {
26 | segment_target_duration?: number,
27 | max_bitrate_ratio?: number,
28 | rate_monitor_buffer_ratio?: number,
29 | key_frames_interval?: number,
30 | renditions: TRendition[]
31 | };
32 |
33 | export type TRendition = {
34 | resolution: `${number}x${number}`,
35 | bitrate: number,
36 | audio_rate: number
37 | };
38 |
--------------------------------------------------------------------------------
/types/ffmpeg-simple.d.ts:
--------------------------------------------------------------------------------
1 | import { BetaJSPromise } from "./betajs";
2 | import { TFFProbeSimpleResponse } from "./ffprobe-simple";
3 | import { TOpts } from "./opts";
4 |
5 | export function ffmpeg_simple_raw(
6 | files: string | string[],
7 | options?: TFFmpegSimpleOptions,
8 | output?: string,
9 | eventCallback?: (progress: TFFmpegSimpleProgress) => unknown,
10 | eventContext?: Record,
11 | opts?: TOpts
12 | ): BetaJSPromise;
13 |
14 | export function ffmpeg_simple(
15 | files: string | string[],
16 | options?: TFFmpegSimpleOptions,
17 | output?: string,
18 | eventCallback?: (progress: TFFmpegSimpleProgress) => unknown,
19 | eventContext?: Record,
20 | opts?: TOpts
21 | ): BetaJSPromise;
22 |
23 | export type TFFmpegSimpleResponse = TFFProbeSimpleResponse;
24 |
25 | export type TFFmpegSimpleProgress = {
26 | frame?: number,
27 | fps?: number,
28 | q?: number,
29 | size_kb?: number,
30 | bitrate_kbits?: number,
31 | dup?: number,
32 | drop?: number,
33 | time?: number,
34 | pass: number,
35 | passes: number,
36 | progress?: number
37 | };
38 |
39 | export type TFFmpegSimpleOptions = {
40 | output_type?: "video" | "audio" | "image" | "gif",
41 | synchronize?: boolean,
42 | framerate?: number,
43 | framerate_gop?: number,
44 | image_percentage?: number,
45 | image_position?: number,
46 | time_limit?: number,
47 | time_start?: number,
48 | time_end?: number,
49 | video_map?: number,
50 | audio_map?: number,
51 | video_profile?: string,
52 | faststart?: boolean,
53 | video_format?: string,
54 | audio_bit_rate?: number,
55 | video_bit_rate?: number,
56 | normalize_audio?: boolean,
57 | remove_audio?: boolean,
58 | width?: number,
59 | height?: number,
60 | auto_rotate?: boolean,
61 | rotate?: number,
62 | ratio_strategy?: "fixed" | "shrink" | "stretch",
63 | size_strategy: "keep" | "shrink" | "stretch",
64 | shrink_strategy?: "shrink-pad" | "crop" | "shrink-crop",
65 | stretch_strategy?: "pad" | "stretch-pad" | "stretch-crop",
66 | mixed_strategy?: "shrink-pad" | "stretch-crop" | "crop-pad",
67 | watermarks?: TWatermark[],
68 | maxMuxingQueueSize?: boolean,
69 | } & Partial;
70 |
71 | export type TWatermark = {
72 | watermark: string,
73 | watermark_size: number,
74 | watermark_x: number,
75 | watermark_y: number,
76 | };
77 |
--------------------------------------------------------------------------------
/types/ffmpeg-test.d.ts:
--------------------------------------------------------------------------------
1 | import { BetaJSPromise } from "./betajs";
2 | import { TFFmpegOpts } from "./opts";
3 |
4 | export function ffmpeg_test(
5 | options: TFFmpegOpts
6 | ): BetaJSPromise;
7 |
8 | export type TFFmpegTestResponse = {
9 | version: TVersion,
10 | configuration?: string[],
11 | codecs: TCodec,
12 | capabilities: TCapabilities,
13 | encoders: string[],
14 | decoders: string[]
15 | };
16 |
17 | type TVersion = {
18 | major: number,
19 | minor: number,
20 | revision: number
21 | } | Record;
22 |
23 | type TCapabilities = {
24 | auto_rotate: boolean
25 | };
26 |
27 | type TCodec = {
28 | support: {
29 | decoding: boolean,
30 | encoding: boolean,
31 | video: boolean,
32 | audio: boolean,
33 | intra: boolean,
34 | lossy: boolean,
35 | lossless: boolean
36 | },
37 | short_name: string,
38 | long_name: string,
39 | decoders: string[],
40 | encoders: string[]
41 | } | Record;
42 |
--------------------------------------------------------------------------------
/types/ffmpeg-volume-detect.d.ts:
--------------------------------------------------------------------------------
1 | import { BetaJSPromise } from "./betajs";
2 | import { TFFmpegOpts } from "./opts";
3 |
4 | export function ffmpeg_volume_detect(
5 | file: string,
6 | options?: TFFmpegOpts
7 | ): BetaJSPromise;
8 |
9 | export type TFFmpegVolumeDetectResponse = {
10 | mean_volume: number,
11 | max_volume: number
12 | };
13 |
--------------------------------------------------------------------------------
/types/ffmpeg.d.ts:
--------------------------------------------------------------------------------
1 | import { BetaJSPromise } from "./betajs";
2 | import { TFFmpegOpts } from "./opts";
3 |
4 | export function ffmpeg(
5 | files: string | string[],
6 | options?: string[],
7 | output?: string,
8 | eventCallback?: (progress: TFFmpegProgress) => unknown,
9 | eventContext?: Record,
10 | opts?: TFFmpegOpts
11 | ): BetaJSPromise;
12 |
13 | export type TFFmpegProgress = {
14 | // TODO
15 | };
16 |
--------------------------------------------------------------------------------
/types/ffprobe-simple.d.ts:
--------------------------------------------------------------------------------
1 | import { BetaJSPromise } from "./betajs";
2 | import { TFFProbeOpts } from "./opts";
3 |
4 | export function ffprobe_simple(
5 | file: string,
6 | options?: TFFProbeOpts
7 | ): BetaJSPromise;
8 |
9 | export type TFFProbeSimpleResponse = {
10 | filename: string,
11 | stream_count: number,
12 | size: number,
13 | bit_rate: number,
14 | start_time: number,
15 | duration: number,
16 | format_name: string,
17 | format_extensions: string[],
18 | format_default_extension: string,
19 | audio?: TFFProbeSimpleAudio,
20 | image?: TFFProbeSimpleImage,
21 | video?: TFFProbeSimpleVideo
22 | };
23 |
24 | export type TFFProbeSimpleMedia = {
25 | index: number,
26 | codec_name: string,
27 | codec_long_name: string,
28 | codec_profile: string,
29 | bit_rate?: number
30 | }
31 |
32 | export type TFFProbeSimpleAudio = TFFProbeSimpleMedia & {
33 | audio_channels: number,
34 | sample_rate?: number,
35 | };
36 |
37 | export type TFFProbeSimpleImage = TFFProbeSimpleMedia & {
38 | rotation: number,
39 | width: number,
40 | height: number,
41 | rotated_width: number,
42 | rotated_height: number,
43 | frames?: number
44 | };
45 |
46 | export type TFFProbeSimpleVideo = TFFProbeSimpleImage;
47 |
--------------------------------------------------------------------------------
/types/ffprobe.d.ts:
--------------------------------------------------------------------------------
1 | import { BetaJSPromise } from "./betajs";
2 | import { TFFProbeOpts } from "./opts";
3 |
4 | export function ffprobe(
5 | file: string,
6 | options?: TFFProbeOpts
7 | ): BetaJSPromise;
8 |
9 | export type TFFProbeResponse = {
10 | streams: TFFProbeStream[],
11 | format: TFFProbeFormat
12 | };
13 |
14 | export type TFFProbeFormat = TFFProbeAudioFormat | TFFProbeImageFormat | TFFProbeVideoFormat;
15 |
16 | type TBaseFormat = {
17 | filename: string,
18 | nb_streams: number,
19 | nb_programs: number,
20 | format_name: string,
21 | format_long_name: string,
22 | size: string,
23 | probe_score: number,
24 | tags: TTags
25 | };
26 |
27 | export type TFFProbeAudioFormat = TBaseFormat & {
28 | start_time: string,
29 | duration: string,
30 | bit_rate: string,
31 | };
32 |
33 | export type TFFProbeImageFormat = TBaseFormat & {
34 | start_time?: string,
35 | duration?: string,
36 | bit_rate?: string
37 | };
38 |
39 | export type TFFProbeVideoFormat = TBaseFormat & {
40 | start_time: string,
41 | duration: string,
42 | bit_rate: string
43 | }
44 |
45 | export type TFFProbeStream = TFFProbeAudioStream | TFFProbeImageStream | TFFProbeVideoStream;
46 |
47 | type TBaseStream = {
48 | index: number,
49 | codec_name: string,
50 | codec_long_name: string,
51 | profile?: string,
52 | codec_type: string,
53 | codec_tag_string: string,
54 | codec_tag: string,
55 | id?: string,
56 | tags: TTags,
57 | disposition: TDisposition
58 | };
59 |
60 | export type TFFProbeAudioStream = TBaseStream & {
61 | sample_fmt: string,
62 | sample_rate: string,
63 | channels: number,
64 | channel_layout: string,
65 | bits_per_sample: number,
66 | r_frame_rate: string,
67 | avg_frame_rate: string,
68 | time_base: string,
69 | start_pts: number,
70 | start_time: string,
71 | duration_ts: number,
72 | duration: string,
73 | bit_rate: string,
74 | nb_frames?: string,
75 | extradata_size?: number
76 | }
77 |
78 | export type TFFProbeImageStream = TBaseStream & {
79 | width: number,
80 | height: number,
81 | coded_width: number,
82 | coded_height: number,
83 | closed_captions: number,
84 | film_grain: number,
85 | has_b_frames: number,
86 | sample_aspect_ratio?: string,
87 | display_aspect_ratio?: string,
88 | pix_fmt: string,
89 | level: number,
90 | color_range: string,
91 | color_space?: string,
92 | chroma_location?: string,
93 | refs: number,
94 | r_frame_rate: string,
95 | avg_frame_rate: string,
96 | time_base: string,
97 | start_pts?: number,
98 | start_time?: string,
99 | duration_ts?: number,
100 | duration?: string,
101 | bits_per_raw_sample?: string
102 | }
103 |
104 | export type TFFProbeVideoStream = TBaseStream & {
105 | width: number,
106 | height: number,
107 | coded_width: number,
108 | coded_height: number,
109 | closed_captions: number,
110 | film_grain: number,
111 | has_b_frames: number,
112 | pix_fmt: string,
113 | level: number,
114 | color_range?: string,
115 | color_space?: string,
116 | color_transfer?: string,
117 | color_primaries?: string,
118 | chroma_location: string,
119 | field_order: string,
120 | refs: number,
121 | is_avc: string,
122 | nal_length_size: string,
123 | r_frame_rate: string,
124 | avg_frame_rate: string,
125 | time_base: string,
126 | start_pts: number,
127 | start_time: string,
128 | duration_ts: number,
129 | duration: string,
130 | bit_rate: string,
131 | bits_per_raw_sample: string,
132 | nb_frames: string,
133 | extradata_size: number,
134 | side_data_list: TSideDataList[]
135 | };
136 |
137 | type TTags = {
138 | major_brand?: string,
139 | minor_version?: string,
140 | compatible_brands?: string,
141 | creation_time?: string,
142 | make?: string,
143 | language?: string,
144 | handler_name?: string,
145 | vendor_id?: string,
146 | encoder: string,
147 | "encoder-eng"?: string,
148 | date?: string,
149 | "date-eng"?: string,
150 | location?: string,
151 | "location-eng"?: string,
152 | model?: string,
153 | [id: string]: unknown
154 | };
155 |
156 | type TSideDataList = {
157 | side_data_type: string,
158 | displaymatrix: string,
159 | rotation: number
160 | };
161 |
162 | type TDisposition = {
163 | default: number,
164 | dub: number,
165 | original: number,
166 | comment: number,
167 | lyrics: number,
168 | karaoke: number,
169 | forced: number,
170 | hearing_impaired: number,
171 | visual_impaired: number,
172 | clean_effects: number,
173 | attached_pic: number,
174 | timed_thumbnails: number,
175 | captions: number,
176 | descriptions: number,
177 | metadata: number,
178 | dependent: number,
179 | still_image: number
180 | };
181 |
--------------------------------------------------------------------------------
/types/index.d.ts:
--------------------------------------------------------------------------------
1 | export * from "./ffmpeg-faststart";
2 | export * from "./ffmpeg-graceful";
3 | export * from "./ffmpeg-multi-pass";
4 | export * from "./ffmpeg-playlist";
5 | export * from "./ffmpeg-simple";
6 | export * from "./ffmpeg-test";
7 | export * from "./ffmpeg-volume-detect";
8 | export * from "./ffmpeg";
9 | export * from "./ffprobe-simple";
10 | export * from "./ffprobe";
11 | export * from "./opts";
12 |
--------------------------------------------------------------------------------
/types/opts.d.ts:
--------------------------------------------------------------------------------
1 | import { TFFmpegTestResponse } from "./ffmpeg-test";
2 |
3 | export type TBaseOpts = {
4 | docker?: string | {
5 | container: string,
6 | proxy?: string,
7 | replaceArguments?: {
8 | libfaac?: string,
9 | "^/var": string
10 | },
11 | preprocessFiles?: {
12 | chown?: string,
13 | chmod?: number,
14 | mkdirs?: boolean
15 | },
16 | postprocessFiles?: {
17 | chown?: string,
18 | chmod?: number,
19 | recordChown?: boolean,
20 | recordChmod?: boolean
21 | }
22 | },
23 | timeout?: number
24 | };
25 |
26 | export type TFFmpegOpts = TBaseOpts & { ffmpeg_binary?: string };
27 | export type TFFProbeOpts = TBaseOpts & { ffprobe_binary?: string };
28 |
29 | export type TOpts = TFFProbeOpts & TFFmpegOpts & {
30 | test_ffmpeg?: true,
31 | test_info?: TFFmpegTestResponse
32 | };
33 |
--------------------------------------------------------------------------------