Commit | Line | Data |
---|---|---|
2ba45a60 DM |
1 | #!/usr/bin/env perl |
2 | ||
3 | # Copyright (c) 2007-2013 Stefano Sabatini | |
4 | # | |
5 | # This file is part of FFmpeg. | |
6 | # | |
7 | # FFmpeg is free software; you can redistribute it and/or | |
8 | # modify it under the terms of the GNU Lesser General Public | |
9 | # License as published by the Free Software Foundation; either | |
10 | # version 2.1 of the License, or (at your option) any later version. | |
11 | # | |
12 | # FFmpeg is distributed in the hope that it will be useful, | |
13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | |
15 | # See the GNU Lesser General Public License for more details. | |
16 | # | |
17 | # You should have received a copy of the GNU Lesser General Public License | |
18 | # along with FFmpeg; if not, write to the Free Software Foundation, Inc., | |
19 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
20 | ||
21 | =head1 NAME | |
22 | ||
23 | plotframes - Plot video frame sizes using ffprobe and gnuplot | |
24 | ||
25 | =head1 SYNOPSIS | |
26 | ||
27 | plotframes [I<options>] [I<input>] | |
28 | ||
29 | =head1 DESCRIPTION | |
30 | ||
31 | plotframes reads a multimedia files with ffprobe, and plots the | |
32 | collected video sizes with gnuplot. | |
33 | ||
34 | =head1 OPTIONS | |
35 | ||
36 | =over 4 | |
37 | ||
38 | =item B<--input|-i> I<infile> | |
39 | ||
40 | Specify multimedia file to read. This is the file passed to the | |
41 | ffprobe command. If not specified it is the first argument passed to | |
42 | the script. | |
43 | ||
44 | =item B<--help|--usage|-h|-?> | |
45 | ||
46 | Print a brief help message and exit. | |
47 | ||
48 | =item B<--manpage|-m> | |
49 | ||
50 | Print the man page. | |
51 | ||
52 | =item B<--output|-o> I<outfile> | |
53 | ||
54 | Set the name of the output used by gnuplot. If not specified no output | |
55 | is created. Must be used in conjunction with the B<terminal> option. | |
56 | ||
57 | =item B<--stream|--s> I<stream_specifier> | |
58 | ||
59 | Specify stream. The value must be a string containing a stream | |
60 | specifier. Default value is "v". | |
61 | ||
62 | =item B<--terminal|-t> I<terminal> | |
63 | ||
64 | Set the name of the terminal used by gnuplot. By default it is | |
65 | "x11". Must be used in conjunction with the B<output> option. Check | |
66 | the gnuplot manual for the valid values. | |
67 | ||
68 | =back | |
69 | ||
70 | =cut | |
71 | ||
72 | =head1 SEE ALSO | |
73 | ||
74 | ffprobe(1), gnuplot(1) | |
75 | ||
76 | =cut | |
77 | ||
78 | use warnings; | |
79 | use strict; | |
80 | ||
81 | use File::Temp; | |
82 | use JSON -support_by_pp; | |
83 | use Getopt::Long; | |
84 | use Pod::Usage; | |
85 | ||
86 | my $input = $ARGV[0]; | |
87 | my $stream_specifier = "v"; | |
88 | my $gnuplot_terminal = "x11"; | |
89 | my $gnuplot_output; | |
90 | ||
91 | GetOptions ( | |
92 | 'input|i=s' => \$input, | |
93 | 'help|usage|?|h' => sub { pod2usage ( { -verbose => 1, -exitval => 0 }) }, | |
94 | 'manpage|m' => sub { pod2usage ( { -verbose => 2, -exitval => 0 }) }, | |
95 | 'stream|s=s' => \$stream_specifier, | |
96 | 'terminal|t=s' => \$gnuplot_terminal, | |
97 | 'output|o=s' => \$gnuplot_output, | |
98 | ) or pod2usage( { -message=> "Parsing error", -verbose => 1, -exitval => 1 }); | |
99 | ||
100 | die "You must specify an input file\n" unless $input; | |
101 | ||
102 | # fetch data | |
103 | my @cmd = (qw{ffprobe -show_entries frame -select_streams}, $stream_specifier, "-of", "json", $input); | |
104 | print STDERR "Executing command: @cmd\n"; | |
105 | my $json_struct; | |
106 | { | |
107 | open(FH, "-|", @cmd) or die "ffprobe command failed: $!\n"; | |
108 | local $/; | |
109 | my $json_text = <FH>; | |
110 | close FH; | |
111 | die "ffprobe command failed" if $?; | |
112 | eval { $json_struct = decode_json($json_text); }; | |
113 | die "JSON parsing error: $@\n" if $@; | |
114 | } | |
115 | ||
116 | # collect and print frame statistics per pict_type | |
117 | my %stats; | |
118 | my $frames = $json_struct->{frames}; | |
119 | my $frame_count = 0; | |
120 | foreach my $frame (@{$frames}) { | |
121 | my $type = $frame->{pict_type}; | |
122 | $frame->{count} = $frame_count++; | |
123 | if (not $stats{$type}) { | |
124 | $stats{$type}->{tmpfile} = File::Temp->new(SUFFIX => '.dat'); | |
125 | my $fn = $stats{$type}->{tmpfile}->filename; | |
126 | open($stats{$type}->{fh}, ">", $fn) or die "Can't open $fn"; | |
127 | } | |
128 | ||
129 | print { $stats{$type}->{fh} } | |
130 | "$frame->{count} ", $frame->{pkt_size} * 8 / 1000, "\n"; | |
131 | } | |
132 | foreach (keys %stats) { close $stats{$_}->{fh}; } | |
133 | ||
134 | # write gnuplot script | |
135 | my %type_color_map = ( | |
136 | "I" => "red", | |
137 | "P" => "green", | |
138 | "B" => "blue" | |
139 | ); | |
140 | ||
141 | my $gnuplot_script_tmpfile = File::Temp->new(SUFFIX => '.gnuplot'); | |
142 | my $fn = $gnuplot_script_tmpfile->filename; | |
143 | open(FH, ">", $fn) or die "Couldn't open $fn: $!"; | |
144 | print FH << "EOF"; | |
145 | set title "video frame sizes" | |
146 | set xlabel "frame time" | |
147 | set ylabel "frame size (Kbits)" | |
148 | set grid | |
149 | set terminal "$gnuplot_terminal" | |
150 | EOF | |
151 | ||
152 | print FH "set output \"$gnuplot_output\"\n" if $gnuplot_output; | |
153 | print FH "plot"; | |
154 | my $sep = ""; | |
155 | foreach my $type (keys %stats) { | |
156 | my $fn = $stats{$type}->{tmpfile}->filename; | |
157 | print FH "$sep\"$fn\" title \"$type frames\" with impulses"; | |
158 | print FH " linecolor rgb \"$type_color_map{$type}\"" if $type_color_map{$type}; | |
159 | $sep = ", "; | |
160 | } | |
161 | close FH; | |
162 | ||
163 | # launch gnuplot with the generated script | |
164 | system ("gnuplot", "--persist", $gnuplot_script_tmpfile->filename); |