1
0
mirror of https://github.com/danog/plotframes.git synced 2024-11-26 11:54:47 +01:00

now based on bitrate

This commit is contained in:
Rodrigo Polo MPW 2015-10-14 11:07:34 -06:00
parent 8fd063561f
commit 587dbf0e98
3 changed files with 140 additions and 51 deletions

View File

@ -2,7 +2,8 @@ plotframes
===========
A Node.js CLI frame plotter inspired on [FFmpeg plotframes](https://github.com/FFmpeg/FFmpeg/blob/master/tools/plotframes) but 350x faster!
![plot](http://i.imgur.com/i0iIg8D.png "plot")
![Frame Based](http://i.imgur.com/M4gT0eX.png "Frame Based")
![Time Based](http://i.imgur.com/J3l0Y7h.png "Time Based")
Requirements:
@ -25,18 +26,20 @@ options:
file passed to the ffprobe command. If not
specified it is the first argument passed to
the script.
-s v, --stream=v Specify stream. The value must be a string
-s 0, --stream=0 Specify stream. The value must be a string
containing a stream specifier. Default value
is "v".
is "0".
-o FILE.png, --output=FILE.png Set the name of the output used by gnuplot.
If not specified no output is created. Must
be used in conjunction with the terminal
option.
-t png, --terminal=png Set the name of the terminal used by
gnuplot. By default it is "x11". Must be
gnuplot. By default it is "windows". Must be
used in conjunction with the output option.
Check the gnuplot manual for the valid
values.
-f, --frames Create a plot based on frame number instead
of frame time.
```
>**WARNING:** This is **NOT** a node.js module for inclusion in other Node.js scripts, it is just a CLI for use in the terminal/console, maybe in the future I'll see to integrate it somehow.

176
cli.js
View File

@ -1,5 +1,6 @@
#!/usr/bin/env node
var child = require('child_process'),
path = require('path'),
dashdash = require('dashdash'),
temp = require('temp'),
split = require("split"),
@ -21,9 +22,9 @@ var options = [
}, {
names: ['stream', 's'],
type: 'string',
help: 'Specify stream. The value must be a string containing a stream specifier. Default value is "v".',
helpArg: 'v',
default: 'v'
help: 'Specify stream. The value must be a string containing a stream specifier. Default value is "0".',
helpArg: '0',
default: '0'
}, {
names: ['output', 'o'],
type: 'string',
@ -35,6 +36,10 @@ var options = [
help: 'Set the name of the terminal used by gnuplot. By default it is "'+defterminal+'". Must be used in conjunction with the output option. Check the gnuplot manual for the valid values.',
helpArg: 'png',
default: defterminal
}, {
names: ['frames', 'f'],
type: 'bool',
help: 'Create a plot based on frame number instead of frame time.'
}
];
@ -66,7 +71,7 @@ if (!opts.input) {
}
}
// Automatically track and cleanup files at exit
// Cleanup stream files on exit
temp.track();
var clnl = false;
@ -79,13 +84,14 @@ function cutelog(str, nl){
}
}
// Pad number
function pad(num, size) {
var str = num + "";
while (str.length < size) str = "0" + str;
return str;
}
// Seconds to time format
function toHHMMSS(n) {
var sep = ':',
n = parseFloat(n),
@ -97,28 +103,67 @@ function toHHMMSS(n) {
return pad(hh,2)+sep+pad(mm,2)+sep+pad(ss,2)+'.'+pad(sss,3);
}
// Get file duration
function getDuration(input, cb){
var r_duration = /Duration: ((\d{2}):(\d{2}):(\d{2}).(\d{2})), /;
var r;
// Get average in array
function getAvg(arr) {
return arr.reduce(function (p, c) {return p + c;}) / arr.length;
}
// Bits into human readable units
function bandWidth(bits) {
bits = bits* 1000;
var unit = 1000;
if (bits < unit) return (bits % 1 === 0 ? bits : bits.toFixed(2)) + "B";
var exp = parseInt(Math.log(bits) / Math.log(unit));
var pre = "KMGTPE"[exp-1] + 'bps';
var n = bits / Math.pow(unit, exp);
return (n % 1 === 0 ? n : n.toFixed(2))+pre;
}
// Get file Details
function getDetails(input, cb){
var rd,
rf,
frame_rate,
duration,
seconds,
r_frame_rate = /avg_frame_rate\=(\d+)\/(\d+)/,
r_duration = /Duration: ((\d{2}):(\d{2}):(\d{2}).(\d{2})), /;
var cli = child.spawn(
'ffprobe', [
'-show_entries',
'stream',
'-select_streams',
opts.stream,
input
],[]
);
cli.stderr.on('data', function (data) {
if(r = r_duration.exec(data)){
cb({
duration: r[1],
seconds: ((((r[2]*60)+r[3])*60)+parseInt(r[4]))+parseFloat(r[5]/100)
});
cli.stdout.pipe(split()).on('data', function (data) {
if(rf = r_frame_rate.exec(data)){
frame_rate = (rf[1]/rf[2]) || 1;
}
});
cli.stderr.pipe(split()).on('data', function (data) {
if(rd = r_duration.exec(data)){
duration = rd[1];
seconds = ((((rd[2]*60)+rd[3])*60)+parseInt(rd[4]))+parseFloat(rd[5]/100);
}
});
cli.on('close', function (code) {
if (code !== 0) {
cutelog('Error trying to get the file duration.',false);
cutelog('Error trying to get the file details.',false);
}
if(frame_rate && duration){
cb({
frame_rate: frame_rate,
duration: duration,
seconds: seconds
});
}
});
@ -127,18 +172,24 @@ function getDuration(input, cb){
});
cli.on('error', function() {
console.log('Error running FFprobe, check if it is installed correctly and if it is included in the system environment path.');
cutelog('Error running FFprobe, check if it is installed correctly and if it is included in the system environment path.',false);
process.exit(1);
});
}
// Get frame bitrate
function getBitrate(input, time, cb){
function getBitrate(input, details, cb){
var frame_count = 0,
kbps_count = 0,
peak = 0,
min = 0,
start= true,
frame_bitrate,
frame_zbits,
frame_time,
frame_type,
time_sec,
times = [],
streams = [],
r,
r_frame = /(?:media_type\=(\w+)\r?\n)(?:stream_index\=(\w+)\r?\n)(?:pkt_pts_time\=(\d*.?\d*)\r?\n)(?:pkt_size\=(\d+)\r?\n)(?:pict_type\=(\w+))?/;
@ -154,23 +205,39 @@ function getBitrate(input, time, cb){
);
cli.stdout.pipe(split(/\[\/FRAME\]\r?\n/)).on('data', function (data){
if(r = r_frame.exec(data)){
r[4] = (r[4]*8)/1000;
// Cleaning the data
frame_zbits = (r[4]*8)/1000;
frame_time = parseFloat(r[3]);
frame_type = r[5]?r[5]:'A';
// Create stream if not exists
if(!streams[frame_type]){streams[frame_type] = temp.createWriteStream();}
// Counters
frame_count++;
kbps_count += r[4];
kbps_count += frame_zbits;
if(start){min = r[4]; start=null;}
peak = peak < r[4] ? r[4] : peak;
min = min > r[4] ? r[4] : min;
r[5] = r[5]?r[5]:'A';
if(!streams[r[5]]){streams[r[5]] = temp.createWriteStream();}
r[3] = parseFloat(r[3]);
frame_bitrate = frame_zbits * details.frame_rate;
streams[r[5]].write(frame_count+' '+r[4]+'\n');
cutelog('Analyzing '+toHHMMSS(r[3])+' / '+time.duration+' '+((r[3]/time.seconds)*100).toFixed(2)+'%',true);
if(opts.frames){
if(start){min = frame_bitrate; start=null;}
peak = peak < frame_bitrate ? frame_bitrate : peak;
min = min > frame_bitrate ? frame_bitrate : min;
streams[frame_type].write(frame_count+' '+frame_bitrate+'\n');
}else{
time_sec = parseInt(frame_time);
if(times[time_sec]){
times[time_sec]+=frame_zbits;
}else{
times[time_sec] = frame_zbits;
}
streams[frame_type].write(frame_time+' '+frame_bitrate+'\n');
}
cutelog('Analyzing '+toHHMMSS(frame_time)+' / '+details.duration+' '+((frame_time/details.seconds)*100).toFixed(2)+'%',true);
}
});
@ -178,14 +245,28 @@ function getBitrate(input, time, cb){
if (code !== 0) {
cutelog('Error trying to get the file bitrate.',false);
}
cutelog('Analysis complete.',false);
cb({
streams: streams,
avg: kbps_count/frame_count,
peak: peak,
min: min
});
cutelog('Analysis complete.',false);
if(opts.frames){
cb({
streams: streams,
avg: kbps_count/details.seconds,
peak: peak,
min: min,
frames: frame_count,
seconds: details.seconds
});
}else{
cb({
streams: streams,
avg: getAvg(times),
peak: Math.max.apply(Math, times),
min: Math.min.apply(Math, times),
frames: frame_count,
seconds: details.seconds
});
}
});
process.on('exit', function() {
@ -193,7 +274,7 @@ function getBitrate(input, time, cb){
});
cli.on('error', function() {
console.log('Error running FFprobe, check if it is installed correctly and if it is included in the system environment path.');
cutelog('Error running FFprobe, check if it is installed correctly and if it is included in the system environment path.',false);
process.exit(1);
});
}
@ -209,7 +290,14 @@ function createPlot(data, cb){
};
var sep='';
var scr='set title "Frames Sizes in Kbits"\nset xlabel "Average: '+parseInt(data.avg)+', Peak: '+parseInt(data.peak)+', Min: '+parseInt(data.min)+'."\nset ylabel "Kbits"\nset grid\nset terminal "'+opts.terminal+'"\n';
var scr='set title "Frames Bitrates for \\"'+path.basename(opts.input)+':'+opts.stream+'\\" "\n';
if(opts.frames){
scr+='set xlabel "Avg Bitrate: '+bandWidth(data.avg)+'. '+data.frames+' Frames; Peak: '+bandWidth(data.peak)+' Min: '+bandWidth(data.min)+'."\n';
}else{
scr+='set xlabel " Avg Bitrate: '+bandWidth(data.avg)+'. '+parseInt(data.seconds)+' Seconds; Max: '+bandWidth(data.peak)+' Min: '+bandWidth(data.min)+'."\n';
}
scr+='set ylabel "Frames Kbps"\nset grid\nset terminal "'+opts.terminal+'"\n';
if(opts.output){
scr += 'set output "'+opts.output+'"\n';
}
@ -227,7 +315,6 @@ function createPlot(data, cb){
gnuplot.write(scr);
gnuplot.end();
// Run gnuplot
var cli = child.spawn(
'gnuplot', [
'--persist',
@ -252,16 +339,15 @@ function createPlot(data, cb){
});
cli.on('error', function() {
console.log('Error running gnuplot, check if it is installed correctly and if it is included in the system environment path.');
cutelog('Error running gnuplot, check if it is installed correctly and if it is included in the system environment path.',false);
process.exit(1);
});
}
// Run
getDuration(opts.input, function(time){
getBitrate(opts.input, time, function(data){
getDetails(opts.input, function(details){
getBitrate(opts.input, details, function(data){
createPlot(data, function(){
});
});
});

View File

@ -1,6 +1,6 @@
{
"name": "plotframes",
"version": "1.0.4",
"version": "1.5.0",
"description": "A Node.js frame plotter inspired by FFmpeg plotframes",
"main": "cli.js",
"preferGlobal": true,
@ -31,5 +31,5 @@
"split": "1.0.x",
"temp": "0.8.x"
},
"readme": "plotframes\n===========\nA Node.js CLI frame plotter inspired on [FFmpeg plotframes](https://github.com/FFmpeg/FFmpeg/blob/master/tools/plotframes) but 350x faster!\n\n![plot](http://i.imgur.com/i0iIg8D.png \"plot\")\n\n\nRequirements:\n* [gnuplot](http://www.gnuplot.info/) \n* [FFmpeg >= 1.2 with the ffprobe command](https://www.ffmpeg.org/)\n\n\nInstallation and running\n```\nnpm install plotframes -g\nplotframes input.mkv\n```\n\nUsage\n```\nUsage: plotframes [OPTIONS]\noptions:\n -h, --help Print this help and exit.\n -i FILE, --input=FILE Specify multimedia file to read. This is the\n file passed to the ffprobe command. If not\n specified it is the first argument passed to\n the script.\n -s v, --stream=v Specify stream. The value must be a string\n containing a stream specifier. Default value\n is \"v\".\n -o FILE.png, --output=FILE.png Set the name of the output used by gnuplot.\n If not specified no output is created. Must\n be used in conjunction with the terminal\n option.\n -t png, --terminal=png Set the name of the terminal used by\n gnuplot. By default it is \"x11\". Must be\n used in conjunction with the output option.\n Check the gnuplot manual for the valid\n values.\n```\n\n>**WARNING:** This is **NOT** a node.js module for inclusion in other Node.js scripts, it is just a CLI for use in the terminal/console, maybe in the future I'll see to integrate it somehow.\n\n### Installing dependencies\n\n#### Windows\n1. [Download](https://nodejs.org) and install Node.js \n2. [Download](http://ffmpeg.zeranoe.com/builds/) a FFmpeg build, uncompress it into a directory that is included in the system `%path%`.\n3. [Download](http://sourceforge.net/projects/gnuplot/) and install `gnpuplot`: \n There are [other downloads](http://sourceforge.net/projects/gnuplot/files/gnuplot/) in case you want to download a different version. \n4. Open your `Command Promt` and run `npm install plotframes`.\n\n\n#### OS X\n* Install Xcode from the App Store\n* Install [Homebrew](http://brew.sh)\n\nThen, using Hombrew intall FFmpeg, XQuartz (needed to render to x11) and gnuplot.\n```\nbrew install ffmpeg\nbrew install Caskroom/cask/xquartz\nbrew install gnuplot --with-x11\n```\n\n#### Ubuntu\n\nInstall Node.js\n```\ncurl -sL https://deb.nodesource.com/setup | sudo bash -\nsudo apt-get install nodejs\n```\n\nInstall FFmpeg\n```\nsudo ppa-purge ppa:mc3man/trusty-media\nsudo add-apt-repository ppa:mc3man/trusty-media\nsudo apt-get update\nsudo apt-get install ffmpeg\n```\n\nInstall gnuplot\n```\nsudo apt-get install gnuplot-x11\n```\n\nOr do all at once\n```\ncurl -sL https://deb.nodesource.com/setup | sudo bash -\nsudo ppa-purge ppa:mc3man/trusty-media\nsudo add-apt-repository ppa:mc3man/trusty-media\nsudo apt-get update\nsudo apt-get install ffmpeg gnuplot-x11 nodejs\n```\n\nAfter that you can install `plotframes`, if it gives you a permision error, run `sudo` command before:\n```\nsudo npm install plotframes\n```\n\n\n## License\n\n(The MIT License)\n\nCopyright (c) by Rodrigo Polo http://RodrigoPolo.com\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE."
"readme": "plotframes\n===========\nA Node.js CLI frame plotter inspired on [FFmpeg plotframes](https://github.com/FFmpeg/FFmpeg/blob/master/tools/plotframes) but 350x faster!\n\n![Frame Based](http://i.imgur.com/M4gT0eX.png \"Frame Based\")\n![Time Based](http://i.imgur.com/J3l0Y7h.png \"Time Based\")\n\n\nRequirements:\n* [gnuplot](http://www.gnuplot.info/) \n* [FFmpeg >= 1.2 with the ffprobe command](https://www.ffmpeg.org/)\n\n\nInstallation and running\n```\nnpm install plotframes -g\nplotframes input.mkv\n```\n\nUsage\n```\nUsage: plotframes [OPTIONS]\noptions:\n -h, --help Print this help and exit.\n -i FILE, --input=FILE Specify multimedia file to read. This is the\n file passed to the ffprobe command. If not\n specified it is the first argument passed to\n the script.\n -s 0, --stream=0 Specify stream. The value must be a string\n containing a stream specifier. Default value\n is \"0\".\n -o FILE.png, --output=FILE.png Set the name of the output used by gnuplot.\n If not specified no output is created. Must\n be used in conjunction with the terminal\n option.\n -t png, --terminal=png Set the name of the terminal used by\n gnuplot. By default it is \"windows\". Must be\n used in conjunction with the output option.\n Check the gnuplot manual for the valid\n values.\n -f, --frames Create a plot based on frame number instead\n of frame time.\n```\n\n>**WARNING:** This is **NOT** a node.js module for inclusion in other Node.js scripts, it is just a CLI for use in the terminal/console, maybe in the future I'll see to integrate it somehow.\n\n### Installing dependencies\n\n#### Windows\n1. [Download](https://nodejs.org) and install Node.js \n2. [Download](http://ffmpeg.zeranoe.com/builds/) a FFmpeg build, uncompress it into a directory that is included in the system `%path%`.\n3. [Download](http://sourceforge.net/projects/gnuplot/) and install `gnpuplot`: \n There are [other downloads](http://sourceforge.net/projects/gnuplot/files/gnuplot/) in case you want to download a different version. \n4. Open your `Command Promt` and run `npm install plotframes`.\n\n\n#### OS X\n* Install Xcode from the App Store\n* Install [Homebrew](http://brew.sh)\n\nThen, using Hombrew intall FFmpeg, XQuartz (needed to render to x11) and gnuplot.\n```\nbrew install ffmpeg\nbrew install Caskroom/cask/xquartz\nbrew install gnuplot --with-x11\n```\n\n#### Ubuntu\n\nInstall Node.js\n```\ncurl -sL https://deb.nodesource.com/setup | sudo bash -\nsudo apt-get install nodejs\n```\n\nInstall FFmpeg\n```\nsudo ppa-purge ppa:mc3man/trusty-media\nsudo add-apt-repository ppa:mc3man/trusty-media\nsudo apt-get update\nsudo apt-get install ffmpeg\n```\n\nInstall gnuplot\n```\nsudo apt-get install gnuplot-x11\n```\n\nOr do all at once\n```\ncurl -sL https://deb.nodesource.com/setup | sudo bash -\nsudo ppa-purge ppa:mc3man/trusty-media\nsudo add-apt-repository ppa:mc3man/trusty-media\nsudo apt-get update\nsudo apt-get install ffmpeg gnuplot-x11 nodejs\n```\n\nAfter that you can install `plotframes`, if it gives you a permision error, run `sudo` command before:\n```\nsudo npm install plotframes\n```\n\n\n## License\n\n(The MIT License)\n\nCopyright (c) by Rodrigo Polo http://RodrigoPolo.com\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE."
}