mirror of
https://github.com/danog/plotframes.git
synced 2024-11-26 11:54:47 +01:00
code import
This commit is contained in:
parent
ade1f70828
commit
3feb549405
4
.gitignore
vendored
4
.gitignore
vendored
@ -25,3 +25,7 @@ build/Release
|
||||
# Dependency directory
|
||||
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
|
||||
node_modules
|
||||
|
||||
.DS_Store
|
||||
sync.ffs_db
|
||||
[Tt]humbs.db
|
||||
|
79
README.md
Normal file
79
README.md
Normal file
@ -0,0 +1,79 @@
|
||||
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")
|
||||
|
||||
|
||||
Requirements:
|
||||
* [gnuplot](http://www.gnuplot.info/)
|
||||
* [FFmpeg >= 1.2 with the ffprobe command](https://www.ffmpeg.org/)
|
||||
|
||||
|
||||
Installation and running
|
||||
```
|
||||
npm install plotframes -g
|
||||
plotframes input.mkv
|
||||
```
|
||||
|
||||
Usage
|
||||
```
|
||||
Usage: plotframes [OPTIONS]
|
||||
options:
|
||||
-h, --help Print this help and exit.
|
||||
-i FILE, --input=FILE Specify multimedia file to read. This is the
|
||||
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
|
||||
containing a stream specifier. Default value
|
||||
is "v".
|
||||
-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
|
||||
used in conjunction with the output option.
|
||||
Check the gnuplot manual for the valid
|
||||
values.
|
||||
```
|
||||
|
||||
>**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.
|
||||
|
||||
|
||||
|
||||
#####OS X requirements installation
|
||||
* Install Xcode
|
||||
* Install [Homebrew](http://brew.sh)
|
||||
|
||||
```
|
||||
brew install Caskroom/cask/xquartz
|
||||
brew install gnuplot --with-x11
|
||||
```
|
||||
> More setup for other OSes coming soon
|
||||
|
||||
|
||||
## License
|
||||
|
||||
(The MIT License)
|
||||
|
||||
Copyright (c) by Rodrigo Polo http://RodrigoPolo.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
247
cli.js
Executable file
247
cli.js
Executable file
@ -0,0 +1,247 @@
|
||||
#!/usr/bin/env node
|
||||
var child = require('child_process');
|
||||
var dashdash = require('dashdash');
|
||||
var temp = require('temp');
|
||||
var split = require("split");
|
||||
|
||||
var options = [
|
||||
{
|
||||
names: ['help', 'h'],
|
||||
type: 'bool',
|
||||
help: 'Print this help and exit.'
|
||||
}, {
|
||||
names: ['input', 'i'],
|
||||
type: 'string',
|
||||
help: 'Specify multimedia file to read. This is the file passed to the ffprobe command. If not specified it is the first argument passed to the script.',
|
||||
helpArg: 'FILE',
|
||||
takesArg: false
|
||||
}, {
|
||||
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'
|
||||
}, {
|
||||
names: ['output', 'o'],
|
||||
type: 'string',
|
||||
help: '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.',
|
||||
helpArg: 'FILE.png'
|
||||
}, {
|
||||
names: ['terminal', 't'],
|
||||
type: 'string',
|
||||
help: 'Set the name of the terminal used by gnuplot. By default it is "x11". Must be used in conjunction with the output option. Check the gnuplot manual for the valid values.',
|
||||
helpArg: 'png',
|
||||
default: 'x11'
|
||||
}
|
||||
];
|
||||
|
||||
var parser = dashdash.createParser({options: options});
|
||||
|
||||
function showHelp(code){
|
||||
var help = parser.help({includeEnv: true}).trimRight();
|
||||
console.error('Usage: plotframes [OPTIONS]\n'+ 'options:\n'+ help);
|
||||
process.exit(code);
|
||||
}
|
||||
|
||||
try {
|
||||
var opts = parser.parse(process.argv);
|
||||
} catch (e) {
|
||||
console.error('Error! Unknown option.');
|
||||
showHelp(1);
|
||||
}
|
||||
|
||||
if (opts.help) {
|
||||
showHelp(0);
|
||||
}
|
||||
|
||||
if (!opts.input) {
|
||||
if(opts._args[0]){
|
||||
opts.input = opts._args[0];
|
||||
}else{
|
||||
console.error('Error! No input defined.');
|
||||
showHelp(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Automatically track and cleanup files at exit
|
||||
temp.track();
|
||||
|
||||
var clnl = false;
|
||||
function cutelog(str, nl){
|
||||
if(nl){
|
||||
if(process.stdin.isTTY){
|
||||
str = ''+str;
|
||||
str = nl?str+'\r':str;
|
||||
process.stderr.write(str);
|
||||
clnl = true;
|
||||
}
|
||||
}else{
|
||||
if(clnl){
|
||||
if(process.stdin.isTTY){
|
||||
process.stdout.clearLine(); // clear current text
|
||||
process.stdout.cursorTo(0); // move cursor to beginning of line
|
||||
clnl = false;
|
||||
}
|
||||
}
|
||||
process.stdout.write(str+'\n');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function pad(num, size) {
|
||||
var str = num + "";
|
||||
while (str.length < size) str = "0" + str;
|
||||
return str;
|
||||
}
|
||||
|
||||
function toHHMMSS(n) {
|
||||
var sep = ':',
|
||||
n = parseFloat(n),
|
||||
sss = parseInt((n % 1)*1000),
|
||||
hh = parseInt(n / 3600);
|
||||
n %= 3600;
|
||||
var mm = parseInt(n / 60),
|
||||
ss = parseInt(n % 60);
|
||||
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;
|
||||
var cli = child.spawn(
|
||||
'ffprobe', [
|
||||
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.on('close', function (code) {
|
||||
if (code !== 0) {
|
||||
cutelog('Error trying to get the file duration.',false);
|
||||
}
|
||||
});
|
||||
|
||||
process.on('exit', function() {
|
||||
cli.kill();
|
||||
});
|
||||
}
|
||||
|
||||
// Get frame bitrate
|
||||
function getBitrate(input, time, cb){
|
||||
var frame_count = 0;
|
||||
var streams = [];
|
||||
var r;
|
||||
var r_frame = /(?:media_type\=(\w+)\n)(?:stream_index\=(\w+)\n)(?:pkt_pts_time\=(\d*.?\d*)\n)(?:pkt_size\=(\d+)\n)(?:pict_type\=(\w+))?/;
|
||||
|
||||
var cli = child.spawn(
|
||||
'ffprobe', [
|
||||
'-show_entries',
|
||||
'frame=stream_index,media_type,pict_type,pkt_size,pkt_pts_time',
|
||||
'-select_streams',
|
||||
opts.stream,
|
||||
input
|
||||
],[]
|
||||
);
|
||||
|
||||
cli.stdout.pipe(split(/\[\/FRAME\]\n/)).on('data', function (data){
|
||||
if(r = r_frame.exec(data)){
|
||||
frame_count++;
|
||||
r[4] = (r[4]*8)/1000;
|
||||
r[3] = parseFloat(r[3]);
|
||||
r[5] = r[5]?r[5]:'A';
|
||||
|
||||
if(!streams[r[5]]){
|
||||
streams[r[5]] = temp.createWriteStream();
|
||||
}
|
||||
streams[r[5]].write(frame_count+' '+r[4]+'\n');
|
||||
cutelog('Analyzing '+toHHMMSS(r[3])+' / '+time.duration+' '+((r[3]/time.seconds)*100).toFixed(2)+'%',true);
|
||||
}
|
||||
});
|
||||
|
||||
cli.on('close', function (code) {
|
||||
if (code !== 0) {
|
||||
cutelog('Error trying to get the file bitrate.',false);
|
||||
}
|
||||
cutelog('Analysis complete.',false);
|
||||
cb(streams);
|
||||
|
||||
});
|
||||
|
||||
process.on('exit', function() {
|
||||
cli.kill();
|
||||
});
|
||||
}
|
||||
|
||||
// Get file duration
|
||||
function createPlot(streams, cb){
|
||||
|
||||
var cm = {
|
||||
P: 'green',
|
||||
I: 'red',
|
||||
B: 'blue',
|
||||
A: 'blue'
|
||||
};
|
||||
var sep='';
|
||||
|
||||
var scr='set title "Frames Sizes"\nset xlabel "Frames"\nset ylabel "Kbits"\nset grid\nset terminal "'+opts.terminal+'"\n';
|
||||
if(opts.output){
|
||||
scr += 'set output "'+opts.output+'"\n';
|
||||
}
|
||||
scr += 'plot';
|
||||
|
||||
for(var k in cm){
|
||||
if(streams[k]){
|
||||
streams[k].end();
|
||||
scr += sep+'"'+streams[k].path+'" title "'+k+' frames" with impulses linecolor rgb "'+cm[k]+'"';
|
||||
sep = ', ';
|
||||
}
|
||||
}
|
||||
|
||||
var gnuplot = temp.createWriteStream();
|
||||
gnuplot.write(scr);
|
||||
gnuplot.end();
|
||||
|
||||
// Run gnuplot
|
||||
var cli = child.spawn(
|
||||
'gnuplot', [
|
||||
'--persist',
|
||||
gnuplot.path
|
||||
],[]
|
||||
);
|
||||
|
||||
cli.stderr.on('data', function (data) {
|
||||
cutelog(data,false);
|
||||
});
|
||||
|
||||
cli.on('close', function (code) {
|
||||
if (code !== 0) {
|
||||
cutelog('Error trying to run gnuplot.',false);
|
||||
}else{
|
||||
cutelog('All tasks completed.',false);
|
||||
}
|
||||
});
|
||||
|
||||
process.on('exit', function() {
|
||||
cli.kill();
|
||||
});
|
||||
}
|
||||
|
||||
// Run
|
||||
getDuration(opts.input, function(time){
|
||||
getBitrate(opts.input, time, function(streams){
|
||||
createPlot(streams, function(){
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
34
package.json
Normal file
34
package.json
Normal file
@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "plotframes",
|
||||
"version": "1.0.0",
|
||||
"description": "A Node.js frame plotter inspired by FFmpeg plotframes",
|
||||
"main": "cli.js",
|
||||
"preferGlobal": true,
|
||||
"bin": {
|
||||
"plotframes": "./cli.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/rodrigopolo/plotframes.git"
|
||||
},
|
||||
"keywords": [
|
||||
"plot",
|
||||
"frames",
|
||||
"ffprobe",
|
||||
"ffmpeg",
|
||||
"gnuplot",
|
||||
"bitrate"
|
||||
],
|
||||
"author": "Rodrigo Polo <rodrigo.polo@gmail.com> (http://rodrigopolo.com)",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/rodrigopolo/plotframes/issues"
|
||||
},
|
||||
"homepage": "https://github.com/rodrigopolo/plotframes#readme",
|
||||
"dependencies": {
|
||||
"dashdash": "1.10.x",
|
||||
"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\n\n#####OS X requirements installation\n* Install Xcode\n* Install [Homebrew](http://brew.sh)\n\n```\nbrew install Caskroom/cask/xquartz\nbrew install gnuplot --with-x11\n```\n> More setup for other OSes coming soon\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."
|
||||
}
|
Loading…
Reference in New Issue
Block a user