File Coverage

blib/lib/FLV/File.pm
Criterion Covered Total %
statement 168 172 97.6
branch 46 62 74.1
condition 17 30 56.6
subroutine 24 24 100.0
pod 14 14 100.0
total 269 302 89.0


line stmt bran cond sub pod time code
1             package FLV::File;
2              
3 6     6   34 use warnings;
  6         11  
  6         191  
4 6     6   31 use strict;
  6         12  
  6         164  
5 6     6   258 use 5.008;
  6         21  
  6         216  
6 6     6   32 use Carp;
  6         9  
  6         433  
7 6     6   5655 use English qw(-no_match_vars);
  6         15492  
  6         41  
8              
9 6     6   2863 use base 'FLV::Base';
  6         13  
  6         3633  
10 6     6   12170 use FLV::Header;
  6         17  
  6         155  
11 6     6   13987 use FLV::Body;
  6         21  
  6         237  
12 6     6   47 use FLV::MetaTag;
  6         14  
  6         127  
13 6     6   28 use FLV::Util;
  6         13  
  6         12128  
14              
15             our $VERSION = '0.24';
16              
17             =for stopwords zeroth
18              
19             =head1 NAME
20              
21             FLV::File - Parse Flash Video files
22              
23             =head1 LICENSE
24              
25             See L
26              
27             =head1 METHODS
28              
29             This is a subclass of FLV::Base.
30              
31             =head2 READ/WRITE METHODS
32              
33             =over
34              
35             =item $self->empty()
36              
37             Prepare an empty FLV. This is only needed if you do not plan to call
38             the parse() method.
39              
40             =cut
41              
42             sub empty
43             {
44 19     19 1 38 my $self = shift;
45              
46 19         121 $self->{header} = FLV::Header->new();
47 19         117 $self->{body} = FLV::Body->new();
48 19         75 $self->{body}->{tags} = [];
49 19         54 return;
50             }
51              
52             =item $self->parse($filename)
53              
54             =item $self->parse($filehandle)
55              
56             Reads the specified file. If the file does not exist or is an invalid
57             FLV stream, an exception will be thrown via croak().
58              
59             There is no return value.
60              
61             =cut
62              
63             sub parse
64             {
65 42     42 1 161 my $self = shift;
66 42         87 my $input = shift;
67 42         72 my $opts = shift;
68 42   100     330 $opts ||= {};
69              
70 42         160 $self->{header} = undef;
71 42         128 $self->{body} = undef;
72 42         117 $self->{filename} = undef;
73 42         93 $self->{filehandle} = undef;
74 42         119 $self->{pos} = 0;
75              
76 42         91 my $eval_result = eval {
77 42 100       146 if (ref $input)
78             {
79 2         6 $self->{filehandle} = $input;
80             }
81             else
82             {
83 40         93 $self->{filename} = $input;
84             ## no critic (RequireBriefOpen)
85 40 100       3295 open my $fh, '<', $self->{filename} or croak q{} . $OS_ERROR;
86 37 50       176 binmode $fh or croak 'Failed to set binary mode on file';
87 37         124 $self->{filehandle} = $fh;
88             }
89              
90 39         285 $self->{header} = FLV::Header->new();
91 39         246 $self->{header}->parse($self); # might throw exception
92              
93 32         234 $self->{body} = FLV::Body->new();
94 32         199 $self->{body}->parse($self, $opts); # might throw exception
95 31         117 1;
96             };
97 42 100       296 if (!$eval_result)
98             {
99 11         62 die 'Failed to read FLV file: ' . $EVAL_ERROR;
100             }
101              
102 31         90 $self->{filehandle} = undef; # implicitly close the filehandle
103 31         1325 $self->{pos} = 0;
104              
105 31         174 return;
106             }
107              
108             =item $self->clone()
109              
110             Create an independent copy of this instance.
111              
112             =cut
113              
114             sub clone
115             {
116 8     8 1 187 my $self = shift;
117              
118 8         72 my $copy = FLV::File->new;
119 8         65 $copy->{header} = $self->{header}->clone;
120 8         53 $copy->{body} = $self->{body}->clone;
121 8         46 return $copy;
122             }
123              
124             =item $self->populate_meta()
125              
126             Fill in various C fields if they are not already present.
127              
128             =cut
129              
130             sub populate_meta ## no critic(ProhibitExcessComplexity)
131             {
132 17     17 1 52 my $self = shift;
133              
134 17   33     94 $self->{body} ||= FLV::Body->new();
135 17         119 $self->{body}->merge_meta();
136              
137 17         174 my %info = (
138             vidtags => 0,
139             audtags => 0,
140             vidbytes => 0,
141             audbytes => 0,
142             lasttime => 0, # millisec
143             keyframetimes => [], # millisec
144             );
145              
146 17         44 my $invalid = '-1';
147 17         123 for my $tag ($self->{body}->get_tags())
148             {
149 9447 100       52822 if ($tag->isa('FLV::VideoTag'))
    100          
150             {
151 3237         4348 $info{vidtags}++;
152 3237         6370 $info{vidbytes} += length $tag->{data};
153 3237         9105 my $time = $tag->get_time;
154 3237 100       7995 if ($info{lasttime} < $time)
155             {
156 3223         4646 $info{lasttime} = $time;
157             }
158 3237         4622 for my $key (qw(width height type codec))
159             {
160 12948 100       47314 if (!defined $info{ 'vid' . $key })
    100          
161             {
162 956         2070 $info{ 'vid' . $key } = $tag->{$key};
163             }
164             elsif ($tag->{$key} != $info{ 'vid' . $key })
165             {
166 3146         6144 $info{ 'vid' . $key } = $invalid;
167             }
168             }
169 3237 100       9754 if ($tag->is_keyframe())
170             {
171 90         142 push @{ $info{keyframetimes} }, $time;
  90         518  
172             }
173             }
174             elsif ($tag->isa('FLV::AudioTag'))
175             {
176 6193         9186 $info{audtags}++;
177 6193         13805 $info{audbytes} += length $tag->{data};
178 6193         31705 for my $key (qw(format rate codec type size))
179             {
180 30965 100       108903 if (!defined $info{ 'aud' . $key })
    50          
181             {
182 6261         13203 $info{ 'aud' . $key } = $tag->{$key};
183             }
184             elsif ($tag->{$key} != $info{ 'aud' . $key })
185             {
186 0         0 $info{ 'aud' . $key } = $invalid;
187             }
188             }
189             }
190             }
191 17         1103 my $lasttime = $info{lasttime} * 0.001;
192 17 50       122 my $duration
193             = 1 < $info{vidtags}
194             ? $lasttime * $info{vidtags} / ($info{vidtags} - 1)
195             : 0;
196              
197 17 50 33     419 my $audrate = defined $info{audrate}
198             && $info{audrate} ne $invalid ? $AUDIO_RATES{ $info{audrate} } : 0;
199 17         527 $audrate =~ s/\D//gxms;
200              
201 17         504 my %meta = (
202             canSeekToEnd => 1,
203             metadatacreator => __PACKAGE__ . " v$VERSION",
204             metadatadate => scalar gmtime,
205             filesize => 0,
206             );
207              
208 17 50       232 if (0 < $duration)
209             {
210 17         48 $meta{duration} = $duration;
211 17 50       71 if ($info{vidbytes})
212             {
213 17         57 my $kbps = $info{vidbytes} * 8 / (1024 * $duration);
214              
215 17         44 $meta{videodatarate} = $kbps;
216 17         215 $meta{framerate} = $info{vidtags} / $duration;
217 17         85 $meta{videosize} = $info{vidbytes};
218             }
219 17 50       67 if ($info{audbytes})
220             {
221 17         46 my $kbps = $info{audbytes} * 8 / (1024 * $duration);
222              
223 17         38 $meta{audiodatarate} = $kbps;
224 17         46 $meta{audiosize} = $info{audbytes};
225             }
226             }
227 17 50       51 if ($audrate)
228             {
229 17         51 $meta{audiosamplerate} = $audrate;
230             }
231 17 50 33     134 if (defined $info{audformat} && $info{audformat} ne $invalid)
232             {
233 17         68 $meta{audiocodecid} = $info{audformat};
234             }
235 17 50 33     122 if (defined $info{vidcodec} && $info{vidcodec} ne $invalid)
236             {
237 17         56 $meta{videocodecid} = $info{vidcodec};
238             }
239 17 100 66     147 if (defined $info{vidwidth} && $info{vidwidth} ne $invalid)
240             {
241 14         50 $meta{width} = $info{vidwidth};
242             }
243 17 100 66     122 if (defined $info{vidheight} && $info{vidheight} ne $invalid)
244             {
245 14         36 $meta{height} = $info{vidheight};
246             }
247 17 50       51 if ($lasttime)
248             {
249 17         78 $meta{lasttimestamp} = $lasttime;
250             }
251 17 50       34 if (@{ $info{keyframetimes} })
  17         67  
252             {
253 90         310 $meta{keyframes} = {
254 17         38 times => [map { $_ * 0.001 } @{ $info{keyframetimes} }],
  17         57  
255             millis => $info{keyframetimes},
256             };
257             }
258              
259 17         190 $self->set_meta(%meta);
260              
261 17         311 return;
262             }
263              
264             =item $self->serialize($filehandle)
265              
266             Serializes the in-memory FLV data. If that representation is not
267             complete, this throws an exception via croak(). Returns a boolean
268             indicating whether writing to the file handle was successful.
269              
270             =cut
271              
272             sub serialize
273             {
274 19     19 1 14091 my $self = shift;
275 19   66     359 my $filehandle = shift || croak 'Please specify a filehandle';
276              
277 18 50       91 if (!$self->{body})
278             {
279 0         0 die 'Missing FLV body';
280             }
281 18         190 $self->{header} = FLV::Header->create_from_body($self->{body});
282 18         206 my $headersize = $self->{header}->serialize($filehandle);
283 18 100       64 return if !$headersize;
284 17         124 return $self->{body}->serialize($filehandle, $headersize);
285             }
286              
287             =back
288              
289             =head2 ACCESSORS
290              
291             =over
292              
293             =item $self->get_info()
294              
295             Returns a hash of FLV metadata. See File::Info for more details.
296              
297             =cut
298              
299             sub get_info
300             {
301 4     4 1 9 my $self = shift;
302              
303 4         193 my %info = (
304             filename => $self->{filename},
305             filesize => -s $self->{filename},
306             $self->{body}->get_info(),
307             );
308 4         106 return %info;
309             }
310              
311             =item $self->get_filename()
312              
313             Returns the filename, if any.
314              
315             =cut
316              
317             sub get_filename
318             {
319 2     2 1 14 my $self = shift;
320 2         14 return $self->{filename};
321             }
322              
323             =item $self->get_meta($key);
324              
325             =item $self->set_meta($key, $value, ...);
326              
327             These are convenience functions for interacting with an C
328             tag at time 0, which is a common convention in FLV files. If the zeroth
329             tag is not an L instance, one is created and prepended
330             to the tag list.
331              
332             See also C and C in L.
333              
334             =cut
335              
336             sub get_meta
337             {
338 77     77 1 11437 my $self = shift;
339 77         118 my $key = shift;
340              
341 77 100       309 return if (!$self->{body});
342 70         254 return $self->{body}->get_meta($key);
343             }
344              
345             sub set_meta
346             {
347 58     58 1 1563 my ($self, @args) = @_;
348              
349 58   66     246 $self->{body} ||= FLV::Body->new();
350 58         463 $self->{body}->set_meta(@args);
351 58         194 return;
352             }
353              
354             =item $self->get_header()
355              
356             =item $self->get_body()
357              
358             These methods return the FLV::Header and FLV::Body instance,
359             respectively. Those will be C until you call either empty() or
360             parse().
361              
362             =cut
363              
364             sub get_header
365             {
366 37     37 1 62 my $self = shift;
367 37         239 return $self->{header};
368             }
369              
370             sub get_body
371             {
372 4757     4757 1 13828 my $self = shift;
373 4757         17580 return $self->{body};
374             }
375              
376             =back
377              
378             =head2 PARSING UTILITIES
379              
380             The following methods are only used during the parsing phase.
381              
382             =over
383              
384             =item $self->get_bytes($n)
385              
386             Reads C<$n> bytes off the active filehandle and returns them as a
387             string. Throws an exception if the filehandle is closed or hits EOF
388             before all the bytes can be read.
389              
390             =cut
391              
392             sub get_bytes
393             {
394 65037     65037 1 91159 my $self = shift;
395 65037   50     138288 my $n = shift || 0;
396              
397 65037 50       153685 return q{} if ($n <= 0);
398              
399 65037         106276 my $fh = $self->{filehandle};
400 65037 50       122340 if (!$fh)
401             {
402 0         0 die 'Internal error: attempt to read a closed filehandle';
403             }
404              
405 65037         68894 my $buf;
406 65037         144619 my $bytes = read $fh, $buf, $n;
407 65037 100       128890 if ($bytes != $n)
408             {
409 4         41 die "Unexpected end of file (byte $self->{pos} + $bytes)";
410             }
411 65033         108415 $self->{pos} += $bytes;
412 65033         260737 return $buf;
413             }
414              
415             =item $self->get_pos()
416              
417             =item $self->get_pos($offset)
418              
419             Returns a string representing the current position in the filehandle.
420             This is intended for use in debugging or exceptions. An example of
421             use: indicate that an input value five bytes behind the read head is
422             erroneous.
423              
424             die 'Error parsing version number at byte '.$self->get_pos(-5);
425              
426             =cut
427              
428             sub get_pos
429             {
430 6535     6535 1 8629 my $self = shift;
431 6535   100     21051 my $offset = shift || 0;
432              
433 6535         10120 my $pos = $self->{pos} + $offset;
434 6535         29702 return sprintf '%d (0x%x)', $pos, $pos;
435             }
436              
437             =item $self->at_end()
438              
439             Returns a boolean indicating if the FLV stream is exhausted. Throws
440             an exception if the filehandle is closed.
441              
442             =cut
443              
444             sub at_end
445             {
446 16280     16280 1 25348 my $self = shift;
447              
448 16280         22231 my $fh = $self->{filehandle};
449 16280 50       44762 if (!$fh)
450             {
451 0         0 die 'Internal error: attempt to read a closed filehandle';
452             }
453 16280         57825 return eof $fh;
454             }
455              
456             1;
457              
458             __END__