Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c0d30550a6 | |||
| 2735d7b0c6 | |||
| 41989579f2 | |||
| 3f57e8b399 | |||
| 67c85f5df8 | |||
| dee9eed566 |
@@ -1,19 +1,76 @@
|
|||||||
# js-vmaf: Simple, but effective VMAF comparison tool
|
# js-vmaf: Simple, but effective VMAF comparison tool
|
||||||
This is a simple wrapper around FFmpeg and VMAF to handle comparison of files.
|
This is a simple wrapper around FFmpeg and VMAF to handle comparison of files.
|
||||||
|
|
||||||
|
```
|
||||||
|
$node . --help
|
||||||
|
usage: js-vmaf [--help] [--hide_banner] [-q] [-v] --ffmpeg FFMPEG --ffprobe FFPROBE [--vmaf VMAF] -r
|
||||||
|
REFERENCE [-o OUTPUT] [--flip] [-cs COLOR_SPACE] [-cp COLOR_PRIMARIES] [-ct COLOR_TRC]
|
||||||
|
[-cr COLOR_RANGE] [-p FORMAT] [-w WIDTH] [-h HEIGHT] [--fps FPS] [-f FEATURE] [-m MODEL]
|
||||||
|
[-t THREADS]
|
||||||
|
Path [Path ...]
|
||||||
|
|
||||||
|
A simple, yet effective tool to quickly compare one or more videos using VMAF.
|
||||||
|
|
||||||
|
positional arguments:
|
||||||
|
Path One or more paths to a distorted file or a directory containing distorted files.
|
||||||
|
|
||||||
|
optional arguments:
|
||||||
|
--help show this help message and exit
|
||||||
|
--hide_banner Hide license banner.
|
||||||
|
-q, --quiet Be quiet.
|
||||||
|
-v, --verbose Be verbose.
|
||||||
|
--ffmpeg FFMPEG Path to the FFmpeg binary to use.
|
||||||
|
--ffprobe FFPROBE Path to the FFprobe binary to use.
|
||||||
|
--vmaf VMAF Path to the VMAF binary to use. Will fall back to FFmpeg if not provided
|
||||||
|
-r REFERENCE, --reference REFERENCE
|
||||||
|
Reference file
|
||||||
|
-o OUTPUT, --output OUTPUT
|
||||||
|
The file name, including formatters, for the output log file.
|
||||||
|
--flip Scale, convert and resample to distorted file instead of reference file.
|
||||||
|
-cs COLOR_SPACE, --color_space COLOR_SPACE
|
||||||
|
Define the color space of the reference file.
|
||||||
|
-cp COLOR_PRIMARIES, --color_primaries COLOR_PRIMARIES
|
||||||
|
Define the color primaries of the reference file.
|
||||||
|
-ct COLOR_TRC, --color_trc COLOR_TRC
|
||||||
|
Define the color transfer characteristics of the reference file.
|
||||||
|
-cr COLOR_RANGE, --color_range COLOR_RANGE
|
||||||
|
Define the color range of the reference file.
|
||||||
|
-p FORMAT, --format FORMAT
|
||||||
|
Define the format for comparison.
|
||||||
|
-w WIDTH, --width WIDTH
|
||||||
|
Define the width for the comparision.
|
||||||
|
-h HEIGHT, --height HEIGHT
|
||||||
|
Define the height for the comparision.
|
||||||
|
--fps FPS Define the FPS for comparison.
|
||||||
|
-f FEATURE, --feature FEATURE
|
||||||
|
Enable (and configure) a feature
|
||||||
|
-m MODEL, --model MODEL
|
||||||
|
Enable (and configure) a model
|
||||||
|
-t THREADS, --threads THREADS
|
||||||
|
Number of threads to use.
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## Installing
|
## Installing
|
||||||
```
|
```
|
||||||
npm install
|
npm install
|
||||||
npm run build
|
npm run build
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage
|
## Examples
|
||||||
|
|
||||||
|
##### Compare all files in a directory
|
||||||
```
|
```
|
||||||
node . --help
|
$(prog) --ffmpeg ffmpeg --ffprobe ffprobe -r /mnt/usb0/ref.mp4 /mnt/usb1/
|
||||||
```
|
```
|
||||||
|
|
||||||
## Examples
|
##### Use the distorted files as the target size
|
||||||
### Compare all files in a directory
|
The following command converts the reference to the same format, resolution, framerate and color as the distorted files. Ideal for bitrate optimization.
|
||||||
```
|
```
|
||||||
node . --ffmpeg ./ffmpeg --ffprobe ./ffprobe -r /mnt/usb0/reference.mp4 /mnt/usb1/
|
$(prog) --ffmpeg ffmpeg --ffprobe ffprobe --flip -r /mnt/usb0/ref.mp4 /mnt/usb1/
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Resize everything to 1080p and convert to yuv420p
|
||||||
|
```
|
||||||
|
$(prog) --ffmpeg ffmpeg --ffprobe ffprobe -h 1080 -p yuv420p -r /mnt/usb0/ref.mp4 /mnt/usb1/
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"folders": [
|
||||||
|
{
|
||||||
|
"path": "."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {}
|
||||||
|
}
|
||||||
Generated
+8
-8
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "js-vmaf",
|
"name": "js-vmaf",
|
||||||
"version": "1.0.1",
|
"version": "1.1.2",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "js-vmaf",
|
"name": "js-vmaf",
|
||||||
"version": "1.0.1",
|
"version": "1.1.2",
|
||||||
"license": "BSD 3-Clause \"New\" or \"Revised\" License",
|
"license": "BSD 3-Clause \"New\" or \"Revised\" License",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"argparse": "^2.0.1",
|
"argparse": "^2.0.1",
|
||||||
@@ -1573,9 +1573,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/word-wrap": {
|
"node_modules/word-wrap": {
|
||||||
"version": "1.2.3",
|
"version": "1.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz",
|
||||||
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
|
"integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
@@ -2694,9 +2694,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"word-wrap": {
|
"word-wrap": {
|
||||||
"version": "1.2.3",
|
"version": "1.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz",
|
||||||
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
|
"integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"wrappy": {
|
"wrappy": {
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "js-vmaf",
|
"name": "js-vmaf",
|
||||||
"version": "1.1.0",
|
"version": "1.1.2",
|
||||||
"description": "A simple tool to quickly and correctly compare a reference file with a distorted file using VMAF.",
|
"description": "A simple tool to quickly and correctly compare a reference file with a distorted file using VMAF.",
|
||||||
"main": "generated/index.js",
|
"main": "generated/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
+43
-32
@@ -253,8 +253,28 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AN
|
|||||||
chain.push("setpts=PTS-STARTPTS");
|
chain.push("setpts=PTS-STARTPTS");
|
||||||
|
|
||||||
// Convert to the correct framerate.
|
// Convert to the correct framerate.
|
||||||
if (this._args.fps || (this._ref.video[0].r_framerate !== fref.video[0].r_framerate)) {
|
if (this._args.fps || (this._ref.video[0].r_frame_rate !== fref.video[0].r_frame_rate)) {
|
||||||
chain.push(`fps=${valueOrDefault(this._args.fps, fref.video[0].r_framerate)}`);
|
chain.push(`fps=${valueOrDefault(this._args.fps, fref.video[0].r_frame_rate)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert color.
|
||||||
|
if (this._args.color_space
|
||||||
|
|| this._args.color_primaries
|
||||||
|
|| this._args.color_trc
|
||||||
|
|| this._args.color_range
|
||||||
|
|| (this._ref.video[0].color_space !== fref.video[0].color_space)
|
||||||
|
|| (this._ref.video[0].color_primaries !== fref.video[0].color_primaries)
|
||||||
|
|| (this._ref.video[0].color_transfer !== fref.video[0].color_transfer)
|
||||||
|
|| (this._ref.video[0].color_range !== fref.video[0].color_range)) {
|
||||||
|
chain.push("colorspace=dither=fsb" +
|
||||||
|
`:ispace=${valueOrDefault(this._ref.video[0].color_space, "bt709")}` +
|
||||||
|
`:iprimaries=${valueOrDefault(this._ref.video[0].color_primaries, "bt709")}` +
|
||||||
|
`:itrc=${valueOrDefault(this._ref.video[0].color_transfer, "bt709")}` +
|
||||||
|
`:irange=${valueOrDefault(this._ref.video[0].color_range, "tv")}` +
|
||||||
|
`:space=${valueOrDefault(this._args.color_space, valueOrDefault(fref.video[0].color_space, "bt709"))}` +
|
||||||
|
`:primaries=${valueOrDefault(this._args.color_primaries, valueOrDefault(fref.video[0].color_primaries, "bt709"))}` +
|
||||||
|
`:trc=${valueOrDefault(this._args.color_trc, valueOrDefault(fref.video[0].color_transfer, "bt709"))}` +
|
||||||
|
`:range=${valueOrDefault(this._args.color_range, valueOrDefault(fref.video[0].color_range, "tv"))}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scale
|
// Scale
|
||||||
@@ -276,26 +296,9 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AN
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert format and color.
|
// Convert format.
|
||||||
if (this._args.color_space
|
if (this._args.format || (this._ref.video[0].pix_fmt !== fref.video[0].pix_fmt)) {
|
||||||
|| this._args.color_primaries
|
chain.push(`format=pix_fmts=${valueOrDefault(this._args.format, valueOrDefault(fref.video[0].pix_fmt, "yuv420p"))}`);
|
||||||
|| this._args.color_trc
|
|
||||||
|| this._args.color_range
|
|
||||||
|| (this._ref.video[0].pix_fmt !== fref.video[0].pix_fmt)
|
|
||||||
|| (this._ref.video[0].color_space !== fref.video[0].color_space)
|
|
||||||
|| (this._ref.video[0].color_primaries !== fref.video[0].color_primaries)
|
|
||||||
|| (this._ref.video[0].color_transfer !== fref.video[0].color_transfer)
|
|
||||||
|| (this._ref.video[0].color_range !== fref.video[0].color_range)) {
|
|
||||||
chain.push("colorspace=dither=fsb" +
|
|
||||||
`:ispace=${valueOrDefault(this._ref.video[0].color_space, "bt709")}` +
|
|
||||||
`:iprimaries=${valueOrDefault(this._ref.video[0].color_primaries, "bt709")}` +
|
|
||||||
`:itrc=${valueOrDefault(this._ref.video[0].color_transfer, "bt709")}` +
|
|
||||||
`:irange=${valueOrDefault(this._ref.video[0].color_range, "tv")}` +
|
|
||||||
`:space=${valueOrDefault(this._args.color_space, valueOrDefault(fref.video[0].color_space, "bt709"))}` +
|
|
||||||
`:primaries=${valueOrDefault(this._args.color_primaries, valueOrDefault(fref.video[0].color_primaries, "bt709"))}` +
|
|
||||||
`:trc=${valueOrDefault(this._args.color_trc, valueOrDefault(fref.video[0].color_transfer, "bt709"))}` +
|
|
||||||
`:range=${valueOrDefault(this._args.color_range, valueOrDefault(fref.video[0].color_range, "tv"))}` +
|
|
||||||
`:format=${valueOrDefault(this._args.format, valueOrDefault(fref.video[0].pix_fmt, "yuv420p"))}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
filters.push(`[0:v:0]${chain.join(",")}[ref]`);
|
filters.push(`[0:v:0]${chain.join(",")}[ref]`);
|
||||||
@@ -307,8 +310,8 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AN
|
|||||||
chain.push("setpts=PTS-STARTPTS");
|
chain.push("setpts=PTS-STARTPTS");
|
||||||
|
|
||||||
// Convert to the correct framerate.
|
// Convert to the correct framerate.
|
||||||
if (this._args.fps || (cmp.video[0].r_framerate !== fref.video[0].r_framerate)) {
|
if (this._args.fps || (cmp.video[0].r_frame_rate !== fref.video[0].r_frame_rate)) {
|
||||||
chain.push(`fps=${valueOrDefault(this._args.fps, fref.video[0].r_framerate)}`);
|
chain.push(`fps=${valueOrDefault(this._args.fps, fref.video[0].r_frame_rate)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert format and color.
|
// Convert format and color.
|
||||||
@@ -316,7 +319,6 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AN
|
|||||||
|| this._args.color_primaries
|
|| this._args.color_primaries
|
||||||
|| this._args.color_trc
|
|| this._args.color_trc
|
||||||
|| this._args.color_range
|
|| this._args.color_range
|
||||||
|| (cmp.video[0].pix_fmt !== fref.video[0].pix_fmt)
|
|
||||||
|| (cmp.video[0].color_space !== fref.video[0].color_space)
|
|| (cmp.video[0].color_space !== fref.video[0].color_space)
|
||||||
|| (cmp.video[0].color_primaries !== fref.video[0].color_primaries)
|
|| (cmp.video[0].color_primaries !== fref.video[0].color_primaries)
|
||||||
|| (cmp.video[0].color_transfer !== fref.video[0].color_transfer)
|
|| (cmp.video[0].color_transfer !== fref.video[0].color_transfer)
|
||||||
@@ -329,8 +331,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AN
|
|||||||
`:space=${valueOrDefault(this._args.color_space, valueOrDefault(fref.video[0].color_space, "bt709"))}` +
|
`:space=${valueOrDefault(this._args.color_space, valueOrDefault(fref.video[0].color_space, "bt709"))}` +
|
||||||
`:primaries=${valueOrDefault(this._args.color_primaries, valueOrDefault(fref.video[0].color_primaries, "bt709"))}` +
|
`:primaries=${valueOrDefault(this._args.color_primaries, valueOrDefault(fref.video[0].color_primaries, "bt709"))}` +
|
||||||
`:trc=${valueOrDefault(this._args.color_trc, valueOrDefault(fref.video[0].color_transfer, "bt709"))}` +
|
`:trc=${valueOrDefault(this._args.color_trc, valueOrDefault(fref.video[0].color_transfer, "bt709"))}` +
|
||||||
`:range=${valueOrDefault(this._args.color_range, valueOrDefault(fref.video[0].color_range, "tv"))}` +
|
`:range=${valueOrDefault(this._args.color_range, valueOrDefault(fref.video[0].color_range, "tv"))}`);
|
||||||
`:format=${valueOrDefault(this._args.format, valueOrDefault(fref.video[0].pix_fmt, "yuv420p"))}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scale
|
// Scale
|
||||||
@@ -352,6 +353,11 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AN
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convert format.
|
||||||
|
if (this._args.format || (cmp.video[0].pix_fmt !== fref.video[0].pix_fmt)) {
|
||||||
|
chain.push(`format=pix_fmts=${valueOrDefault(this._args.format, valueOrDefault(fref.video[0].pix_fmt, "yuv420p"))}`);
|
||||||
|
}
|
||||||
|
|
||||||
filters.push(`[1:v:0]${chain.join(",")}[dst]`);
|
filters.push(`[1:v:0]${chain.join(",")}[dst]`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -368,22 +374,25 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AN
|
|||||||
chain.push(`feature=${this._args.feature.join("|").replace(/([\\"'`:]{1,1})/g, "\\\\$1")}`);
|
chain.push(`feature=${this._args.feature.join("|").replace(/([\\"'`:]{1,1})/g, "\\\\$1")}`);
|
||||||
}
|
}
|
||||||
chain.push(`n_threads=${this._args.threads}`);
|
chain.push(`n_threads=${this._args.threads}`);
|
||||||
filters.push(`[ref][dst]libvmaf=${chain.join(":")}`);
|
filters.push(`[dst][ref]libvmaf=${chain.join(":")}`);
|
||||||
|
|
||||||
const proc = this.FFmpeg([
|
const proc = this.FFmpeg([
|
||||||
"-hide_banner",
|
"-hide_banner",
|
||||||
"-v", "quiet",
|
"-v", "quiet",
|
||||||
"-stats",
|
"-stats",
|
||||||
"-hwaccel", "auto",
|
|
||||||
|
|
||||||
"-threads", this._args.threads,
|
"-threads", this._args.threads,
|
||||||
"-strict", "strict",
|
"-strict", "strict",
|
||||||
|
"-hwaccel", "auto",
|
||||||
"-hwaccel_flags", "+allow_high_depth",
|
"-hwaccel_flags", "+allow_high_depth",
|
||||||
|
"-r", this._ref.video[0].r_frame_rate,
|
||||||
"-i", this._args.reference,
|
"-i", this._args.reference,
|
||||||
|
|
||||||
"-threads", this._args.threads,
|
"-threads", this._args.threads,
|
||||||
"-strict", "strict",
|
"-strict", "strict",
|
||||||
|
"-hwaccel", "auto",
|
||||||
"-hwaccel_flags", "+allow_high_depth",
|
"-hwaccel_flags", "+allow_high_depth",
|
||||||
|
"-r", cmp.video[0].r_frame_rate,
|
||||||
"-i", path,
|
"-i", path,
|
||||||
|
|
||||||
"-sws_flags", "bicubic+full_chroma_inp+full_chroma_int",
|
"-sws_flags", "bicubic+full_chroma_inp+full_chroma_int",
|
||||||
@@ -395,7 +404,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AN
|
|||||||
|
|
||||||
"-f", "null", OS.platform() === "win32" ? "NUL" : "/dev/null"
|
"-f", "null", OS.platform() === "win32" ? "NUL" : "/dev/null"
|
||||||
]);
|
]);
|
||||||
await new Promise((resolve, reject) => {
|
const result : any = await new Promise((resolve, reject) => {
|
||||||
const sout : Array<string> = [];
|
const sout : Array<string> = [];
|
||||||
const serr : Array<string> = [];
|
const serr : Array<string> = [];
|
||||||
proc.addListener("exit", (code) => {
|
proc.addListener("exit", (code) => {
|
||||||
@@ -427,6 +436,9 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AN
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
if (result.code !== 0) {
|
||||||
|
throw new Error("Unexpected failure running FFmpeg");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PATH.extname(log) === ".json") {
|
if (PATH.extname(log) === ".json") {
|
||||||
@@ -460,9 +472,8 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AN
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async run() : Promise<number> {
|
public async run() : Promise<number> {
|
||||||
this._args = this._argparse.parse_known_args();
|
|
||||||
this.license();
|
|
||||||
this._args = this._argparse.parse_args();
|
this._args = this._argparse.parse_args();
|
||||||
|
this.license();
|
||||||
|
|
||||||
//if (!this._args.quiet) console.dir(this._args);
|
//if (!this._args.quiet) console.dir(this._args);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user