Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dee9eed566 | |||
| 9431689ac8 |
@@ -0,0 +1,19 @@
|
|||||||
|
# js-vmaf: Simple, but effective VMAF comparison tool
|
||||||
|
This is a simple wrapper around FFmpeg and VMAF to handle comparison of files.
|
||||||
|
|
||||||
|
## Installing
|
||||||
|
```
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
```
|
||||||
|
node . --help
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
### Compare all files in a directory
|
||||||
|
```
|
||||||
|
node . --ffmpeg ./ffmpeg --ffprobe ./ffprobe -r /mnt/usb0/reference.mp4 /mnt/usb1/
|
||||||
|
```
|
||||||
Generated
+14
-3
@@ -1,15 +1,16 @@
|
|||||||
{
|
{
|
||||||
"name": "js-vmaf",
|
"name": "js-vmaf",
|
||||||
"version": "0.0.0",
|
"version": "1.0.1",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "js-vmaf",
|
"name": "js-vmaf",
|
||||||
"version": "0.0.0",
|
"version": "1.0.1",
|
||||||
"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",
|
||||||
|
"percentile": "^1.6.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/argparse": "^2.0.10",
|
"@types/argparse": "^2.0.10",
|
||||||
@@ -1266,6 +1267,11 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/percentile": {
|
||||||
|
"version": "1.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/percentile/-/percentile-1.6.0.tgz",
|
||||||
|
"integrity": "sha512-8vSyjdzwxGDHHwH+cSGch3A9Uj2On3UpgOWxWXMKwUvoAbnujx6DaqmV1duWXNiH/oEWpyVd6nSQccix6DM3Ng=="
|
||||||
|
},
|
||||||
"node_modules/picomatch": {
|
"node_modules/picomatch": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||||
@@ -2499,6 +2505,11 @@
|
|||||||
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
|
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"percentile": {
|
||||||
|
"version": "1.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/percentile/-/percentile-1.6.0.tgz",
|
||||||
|
"integrity": "sha512-8vSyjdzwxGDHHwH+cSGch3A9Uj2On3UpgOWxWXMKwUvoAbnujx6DaqmV1duWXNiH/oEWpyVd6nSQccix6DM3Ng=="
|
||||||
|
},
|
||||||
"picomatch": {
|
"picomatch": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||||
|
|||||||
+3
-2
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "js-vmaf",
|
"name": "js-vmaf",
|
||||||
"version": "1.0.1",
|
"version": "1.1.1",
|
||||||
"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": {
|
||||||
@@ -21,6 +21,7 @@
|
|||||||
"typescript": "^4.9.4"
|
"typescript": "^4.9.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"argparse": "^2.0.1"
|
"argparse": "^2.0.1",
|
||||||
|
"percentile": "^1.6.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+55
-14
@@ -8,6 +8,7 @@ import CHILD_PROCESS from "node:child_process";
|
|||||||
import PROCESS from "node:process";
|
import PROCESS from "node:process";
|
||||||
import PATH from "node:path";
|
import PATH from "node:path";
|
||||||
import FS from "node:fs";
|
import FS from "node:fs";
|
||||||
|
import PERCENTILE from "percentile";
|
||||||
|
|
||||||
function valueOrDefault(value : any, fallback : any) : any {
|
function valueOrDefault(value : any, fallback : any) : any {
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
@@ -119,7 +120,7 @@ class App {
|
|||||||
this._argparse.add_argument("-m", "--model", {
|
this._argparse.add_argument("-m", "--model", {
|
||||||
type: "str",
|
type: "str",
|
||||||
action: "append",
|
action: "append",
|
||||||
default: [ "version=vmaf_v0.6.1" ],
|
default: [],
|
||||||
help: "Enable (and configure) a model"
|
help: "Enable (and configure) a model"
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -224,9 +225,15 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AN
|
|||||||
}
|
}
|
||||||
|
|
||||||
async compare(path : string) {
|
async compare(path : string) {
|
||||||
if (!this._args.quiet) console.log(`'${path}' Comparing...`);
|
if (!this._args.quiet) console.log(`Comparing '${path}'...`);
|
||||||
|
|
||||||
const cmp = this.FFprobe([path]);
|
let cmp;
|
||||||
|
try {
|
||||||
|
cmp = this.FFprobe([path]);
|
||||||
|
} catch {
|
||||||
|
console.error("Not a video file.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (cmp.video.length == 0) {
|
if (cmp.video.length == 0) {
|
||||||
console.error(`'${path}' Missing video track.`);
|
console.error(`'${path}' Missing video track.`);
|
||||||
return;
|
return;
|
||||||
@@ -250,12 +257,11 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AN
|
|||||||
chain.push(`fps=${valueOrDefault(this._args.fps, fref.video[0].r_framerate)}`);
|
chain.push(`fps=${valueOrDefault(this._args.fps, fref.video[0].r_framerate)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert format and color.
|
// Convert color.
|
||||||
if (this._args.color_space
|
if (this._args.color_space
|
||||||
|| this._args.color_primaries
|
|| this._args.color_primaries
|
||||||
|| this._args.color_trc
|
|| this._args.color_trc
|
||||||
|| this._args.color_range
|
|| 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_space !== fref.video[0].color_space)
|
||||||
|| (this._ref.video[0].color_primaries !== fref.video[0].color_primaries)
|
|| (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_transfer !== fref.video[0].color_transfer)
|
||||||
@@ -268,8 +274,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
|
||||||
@@ -291,6 +296,11 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AN
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convert format.
|
||||||
|
if (this._args.format || (this._ref.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(`[0:v:0]${chain.join(",")}[ref]`);
|
filters.push(`[0:v:0]${chain.join(",")}[ref]`);
|
||||||
}
|
}
|
||||||
{ // Adjust distorted to the expected format.
|
{ // Adjust distorted to the expected format.
|
||||||
@@ -309,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)
|
||||||
@@ -322,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
|
||||||
@@ -345,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]`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -361,11 +374,11 @@ 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", "info",
|
"-v", "quiet",
|
||||||
"-stats",
|
"-stats",
|
||||||
"-hwaccel", "auto",
|
"-hwaccel", "auto",
|
||||||
|
|
||||||
@@ -421,12 +434,40 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AN
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (PATH.extname(log) === ".json") {
|
||||||
|
// Post-process the JSON file so we get more useful metrics.
|
||||||
|
const data = JSON.parse(FS.readFileSync(log).toString("utf-8"));
|
||||||
|
|
||||||
|
const prc = [50, 75, 90, 99, 99.9];
|
||||||
|
|
||||||
|
const frames : any = {};
|
||||||
|
for (const metric in data.pooled_metrics) {
|
||||||
|
frames[metric] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const frame of data.frames) {
|
||||||
|
for (const metric in frame.metrics) {
|
||||||
|
frames[metric].push(frame.metrics[metric]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const metric in data.pooled_metrics) {
|
||||||
|
for (const p in prc) {
|
||||||
|
data.pooled_metrics[metric][`${prc[p]}th %ile`] = PERCENTILE(
|
||||||
|
100 - prc[p],
|
||||||
|
frames[metric]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FS.writeFileSync(log, JSON.stringify(data, undefined, "\t"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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