File Coverage

blib/lib/FLV/Cut.pm
Criterion Covered Total %
statement 102 108 94.4
branch 18 24 75.0
condition 10 17 58.8
subroutine 14 14 100.0
pod 4 4 100.0
total 148 167 88.6


line stmt bran cond sub pod time code
1             package FLV::Cut;
2              
3 1     1   51985 use warnings;
  1         2  
  1         36  
4 1     1   5 use strict;
  1         2  
  1         31  
5 1     1   23 use 5.008;
  1         21  
  1         36  
6              
7 1     1   525 use FLV::File;
  1         4  
  1         37  
8 1     1   44 use FLV::Util;
  1         2  
  1         246  
9 1     1   8 use List::MoreUtils qw(any);
  1         2  
  1         49  
10 1     1   7 use English qw(-no_match_vars);
  1         2  
  1         9  
11 1     1   500 use Carp;
  1         2  
  1         106  
12 1     1   7 use Readonly;
  1         2  
  1         1331  
13              
14             our $VERSION = '0.24';
15              
16             =for stopwords FLVs undef keyframes keyframe
17              
18             =head1 NAME
19              
20             FLV::Cut - Extract FLV segments into new files
21              
22             =head1 LICENSE
23              
24             See L
25              
26             =head1 SYNOPSIS
27              
28             use FLV::Cut;
29             my $converter = FLV::Cut->new();
30             $converter->add_output('first_ten_sec.flv', undef, 10_000);
31             $converter->add_output('middle_ten_sec.flv', 20_000, 30_000);
32             $converter->add_output('tail.flv', 40_000, undef);
33             $converter->parse_flv('input.flv');
34             $converter->save_all;
35              
36             =head1 DESCRIPTION
37              
38             Efficiently extracts segments of an FLV into separate files.
39              
40             WARNING: this tool does not help you find keyframes! If you pick a
41             cut time that is not on a keyframe, you will see unpleasant video
42             artifacts in the resulting media. For many input FLVs, you can use
43             the following to find the keyframe times:
44              
45             my $flv = FLV::File->new;
46             $flv->parse;
47             $flv->populate_meta; # optional
48             my @times = @{ $flv->get_meta('keyframe')->{times} };
49              
50             =head1 METHODS
51              
52             =over
53              
54             =item $pkg->new()
55              
56             Instantiate a converter.
57              
58             =cut
59              
60             Readonly::Scalar my $MAX_TIME => 4_000_000_000;
61              
62             sub new
63             {
64 1     1 1 56 my $pkg = shift;
65              
66 1         5 my $start = { time => 0, out => [] };
67 1         5 my $end = { time => $MAX_TIME + 1, out => [] };
68 1         6 my $self = bless {
69             times => [$start, $end], ## no critic (Comma)
70             outfiles => {},
71             }, $pkg;
72 1         4 return $self;
73             }
74              
75             =item $self->add_output($flv_filename, $in_milliseconds, $out_milliseconds)
76              
77             Register an output file for a particular time slice. Either the in or
78             out times can be undefined to imply the beginning or end of the FLV,
79             respectively. You can set both to undef, but that's pretty
80             pointless... If the in or out times are not represented in the input
81             FLV, that's OK -- you may just end up with less data than you expected
82             in the output files.
83              
84             =cut
85              
86             sub add_output
87             {
88 8     8 1 2343 my $self = shift;
89 8         12 my $outfile = shift;
90 8   100     28 my $cutin = shift || 0;
91 8   66     35 my $cutout = shift || $MAX_TIME;
92              
93 8 50 33     42 if ($cutin < 0 || $cutout < 0)
94             {
95 0         0 croak 'Illegal negative time';
96             }
97 8 50 33     38 if ($cutin > $MAX_TIME || $cutout > $MAX_TIME)
98             {
99 0         0 croak 'Illegal huge time';
100             }
101 8 50       19 if ($cutin >= $cutout)
102             {
103 0         0 carp 'Ignoring cut-in >= cut-out';
104 0         0 return;
105             }
106              
107 8         47 my $out = FLV::File->new();
108 8         25 $out->empty();
109 8         39 my $outfh = FLV::Util->get_write_filehandle($outfile);
110 8 50       22 if (!$outfh)
111             {
112 0         0 die 'Failed to write FLV file: ' . $OS_ERROR;
113             }
114              
115 8 100       27 if ($self->{outfiles}->{$outfile})
116             {
117 2 100       9 if ($self->{outfiles}->{$outfile}->{cutin} > $cutin)
118             {
119 1         3 $self->{outfiles}->{$outfile}->{cutin} = $cutin;
120             }
121             }
122             else
123             {
124 6         68 $self->{outfiles}->{$outfile}
125             = { flv => $out, fh => $outfh, cutin => $cutin };
126             }
127              
128 8         16 my $times = $self->{times};
129 8         9 my $i = 0;
130 8         26 while ($times->[$i]->{time} < $cutin)
131             {
132 11         25 ++$i;
133             }
134 8 100       23 if ($times->[$i]->{time} != $cutin)
135             {
136              
137             # A new cutin time
138 1         6 my $new_time
139 1         2 = { time => $cutin, out => [@{ $times->[$i - 1]->{out} }] };
140 1         2 splice @{$times}, $i, 0, $new_time;
  1         5  
141             }
142 8         9 my $added_last;
143 8         20 while ($times->[$i]->{time} <= $cutout)
144             {
145 19 100   25   59 if (any { $_ eq $outfile } @{ $times->[$i]->{out} })
  25         47  
  19         72  
146             {
147 4         7 $added_last = undef;
148             }
149             else
150             {
151 15         21 $added_last = 1;
152 15         17 push @{ $times->[$i]->{out} }, $outfile;
  15         37  
153             }
154 19         74 ++$i;
155             }
156 8 100       24 if ($times->[$i]->{time} != $cutout + 1)
157             {
158              
159             # A new cutout time
160              
161 3         5 my @out = @{ $times->[$i - 1]->{out} };
  3         10  
162              
163             # It should not include this $outfile, unless it previously
164             # spanned this cutout
165 3 50       9 if ($added_last)
166             {
167 3         6 pop @out;
168             }
169              
170 3         4 splice @{$times}, $i, 0, { time => $cutout + 1, out => \@out };
  3         14  
171             }
172              
173 8         66 return;
174             }
175              
176             =item $self->parse_flv($flv_filename)
177              
178             =item $self->parse_flv($flv_instance)
179              
180             Open and parse the specified FLV file. Alternatively, you may pass an
181             instantiated and parsed L object.
182              
183             =cut
184              
185             sub parse_flv
186             {
187 1     1 1 7 my $self = shift;
188 1         3 my $infile = shift;
189              
190 1         9 my $flv;
191 1 50 33     7 if (ref $infile && $infile->isa('FLV::File'))
192             {
193 0         0 $flv = $infile;
194             }
195             else
196             {
197 1         8 $flv = FLV::File->new;
198 1         7 $flv->parse($infile);
199             }
200              
201             # ASSUMPTION: tags are time-sorted
202 1         4 my $times = $self->{times};
203 1         3 my $i = 0;
204 1         12 for my $tag ($flv->get_body->get_tags)
205             {
206 435 100 100     6491 if ($tag->isa('FLV::VideoTag') || $tag->isa('FLV::AudioTag'))
207             {
208 434         1230 my $time = $tag->get_time;
209 434         1823 while ($time >= $times->[$i + 1]->{time})
210             {
211 4         16 ++$i;
212             }
213 434         8151 for my $outfile (@{ $times->[$i]->{out} })
  434         837  
214             {
215 1618         4180 my $out = $self->{outfiles}->{$outfile};
216 1618         1902 my $tag_copy = bless { %{$tag} }, ref $tag; # shallow clone
  1618         17680  
217 1618         9453 $tag_copy->{start} -= $out->{cutin};
218 1618         2718 push @{ $out->{flv}->get_body->{tags} }, $tag_copy;
  1618         5359  
219             }
220             }
221             }
222 1         563 return;
223             }
224              
225             =item $self->save_all()
226              
227             Serialize all of the extracted FLVs to file.
228              
229             =cut
230              
231             sub save_all
232             {
233 1     1 1 3 my $self = shift;
234              
235 1         3 for my $out (values %{ $self->{outfiles} })
  1         5  
236             {
237 6         67 $out->{flv}->populate_meta();
238 6         43 $out->{flv}->serialize($out->{fh});
239             }
240              
241 1         2188 return;
242             }
243              
244             1;
245              
246             __END__