File Coverage

blib/lib/FLV/ToSWF.pm
Criterion Covered Total %
statement 132 135 97.7
branch 29 36 80.5
condition 21 27 77.7
subroutine 19 19 100.0
pod 3 3 100.0
total 204 220 92.7


line stmt bran cond sub pod time code
1             package FLV::ToSWF;
2              
3 1     1   1539 use warnings;
  1         2  
  1         41  
4 1     1   6 use strict;
  1         3  
  1         35  
5 1     1   32 use 5.008;
  1         3  
  1         35  
6              
7 1     1   5 use SWF::File;
  1         2  
  1         19  
8 1     1   7 use SWF::Element;
  1         2  
  1         20  
9 1     1   5 use FLV::File;
  1         2  
  1         42  
10 1     1   6 use FLV::Util;
  1         2  
  1         169  
11 1     1   6 use FLV::AudioTag;
  1         3  
  1         29  
12 1     1   5 use FLV::VideoTag;
  1         1  
  1         27  
13 1     1   5 use English qw(-no_match_vars);
  1         2  
  1         7  
14 1     1   395 use Carp;
  1         3  
  1         1967  
15              
16             our $VERSION = '0.24';
17              
18             =for stopwords SWF transcodes framerate
19              
20             =head1 NAME
21              
22             FLV::ToSWF - Convert an FLV file into a SWF file
23              
24             =head1 LICENSE
25              
26             See L
27              
28             =head1 SYNOPSIS
29              
30             use FLV::ToSwf;
31             my $converter = FLV::ToSWF->new();
32             $converter->parse_flv($flv_filename);
33             $converter->save($swf_filename);
34              
35             See also L.
36              
37             =head1 DESCRIPTION
38              
39             Transcodes FLV files into SWF files. See the L command-line
40             program for a nice interface and a detailed list of caveats and
41             limitations.
42              
43             =head1 METHODS
44              
45             =over
46              
47             =item $pkg->new()
48              
49             Instantiate a converter and prepare an empty SWF.
50              
51             =cut
52              
53             sub new
54             {
55 3     3 1 10609 my $pkg = shift;
56              
57 3         124 my $self = bless {
58             flv => FLV::File->new(),
59             background_color => [0, 0, 0], # RGB, black
60             }, $pkg;
61 3         34 $self->{flv}->empty();
62 3         8 return $self;
63             }
64              
65             =item $self->parse_flv($flv_filename)
66              
67             Open and parse the specified FLV file. If the FLV file lacks
68             C details, that tag is populated with duration,
69             framerate, video dimensions, etc.
70              
71             =cut
72              
73             sub parse_flv
74             {
75 3     3 1 20 my $self = shift;
76 3         61 my $infile = shift;
77              
78 3         19 $self->{flv}->parse($infile);
79 2         50 $self->{flv}->populate_meta();
80              
81 2         12 $self->_validate();
82              
83 2         9 return;
84             }
85              
86             sub _validate
87             {
88 2     2   3 my $self = shift;
89              
90 2         10 my $acodec = $self->{flv}->get_meta('audiocodecid');
91 2 50 33     17 if (defined $acodec && $acodec != 2)
92             {
93 0         0 die "Audio format $AUDIO_FORMATS{$acodec} not supported; "
94             . "only MP3 audio allowed\n";
95             }
96 2         5 return;
97             }
98              
99             =item $self->save($swf_filename)
100              
101             Write out a SWF file. Note: this is usually called only after
102             C. Throws an exception upon error.
103              
104             =cut
105              
106             sub save
107             {
108 3     3 1 2253 my $self = shift;
109 3         8 my $outfile = shift;
110              
111             # Collect FLV info
112 3         15 my $flvinfo = $self->_flvinfo();
113              
114             # Create a new SWF
115 3         17 my $swf = $self->_startswf($flvinfo);
116              
117 3         9 $self->{audsamples} = 0;
118              
119 3         5 for my $i (0 .. $#{ $flvinfo->{vidtags} })
  3         14  
120             {
121 298         140404 my $vidtag = $flvinfo->{vidtags}->[$i];
122 298         962 my $data = $vidtag->{data};
123 298 100 66     1806 if (4 == $vidtag->{codec} || 5 == $vidtag->{codec})
124             {
125              
126             # On2 VP6 is different in FLV vs. SWF!
127 149 50 33     3640 if ($data !~ s/\A(.)//xms || $1 ne pack 'C', 0)
128             {
129 0         0 warn 'This FLV has a non-zero video size adjustment. '
130             . "It may not play properly as a SWF...\n";
131             }
132             }
133             SWF::Element::Tag::VideoFrame->new(
134 298         3281 StreamID => 1,
135             FrameNum => $i,
136             VideoData => $data,
137             )->pack($swf);
138              
139 298 100       2681354 if (0 == $i)
140             {
141 2         41 SWF::Element::Tag::PlaceObject2->new(
142             Flags => 22, # matrix, tween ratio and characterID
143             CharacterID => 1,
144             Matrix => SWF::Element::MATRIX->new(
145             ScaleX => 1,
146             ScaleY => 1,
147             RotateSkew0 => 0,
148             RotateSkew1 => 0,
149             TranslateX => 0,
150             TranslateY => 0,
151             ),
152             Ratio => $i,
153             Depth => 4,
154             )->pack($swf);
155             }
156             else
157             {
158 296         5195 SWF::Element::Tag::PlaceObject2->new(
159             Flags => 17, # move and tween ratio
160             Ratio => $i,
161             Depth => 4,
162             )->pack($swf);
163             }
164              
165 298         7434 $self->_add_audio($swf, $flvinfo, $vidtag->{start},
166 298         471767 $i == $#{ $flvinfo->{vidtags} });
167              
168 298         7122 SWF::Element::Tag::ShowFrame->new()->pack($swf);
169             }
170              
171             # Save to disk
172 3 50       564 $swf->close(q{-} eq $outfile ? \*STDOUT : $outfile);
173              
174 2         1989788 return;
175             }
176              
177             sub _add_audio
178             {
179 298     298   491 my $self = shift;
180 298         1612 my $swf = shift;
181 298         422 my $flvinfo = shift;
182 298         691 my $start = shift;
183 298         625 my $islast = shift;
184              
185 298 50       429 if (@{ $flvinfo->{audtags} })
  298         56898  
186             {
187 298         483 my $data = q{};
188 298         735 my $any_tag = $flvinfo->{audtags}->[0];
189 298         1884 my $audstart = $any_tag->{start};
190 298         4916 my $format = $any_tag->{format};
191 298         542 my $stereo = $any_tag->{type};
192 298         475 my $ratecode = $any_tag->{rate};
193              
194 298 50       3001 if ($format != 2)
195             {
196 0         0 die 'Only MP3 audio supported so far...';
197             }
198              
199 298         4457 (my $rate = $AUDIO_RATES{$ratecode}) =~ s/\D//gxms;
200 298 50       19141 my $bytes_per_sample = ($stereo ? 2 : 1) * ($any_tag->{size} ? 2 : 1);
    50          
201              
202 298         1649 my $needsamples = int 0.001 * $start * $rate;
203 298         1745 my $startsamples = $self->{audsamples};
204              
205 298   100     428 while (@{ $flvinfo->{audtags} }
  868   66     9443  
206             && ($islast || $self->{audsamples} < $needsamples))
207             {
208 570         2033 my $atag = shift @{ $flvinfo->{audtags} };
  570         1649  
209 570         15255 $data .= $atag->{data};
210 570         10246 $self->{audsamples} = $self->_round_to_samples(
211 570 100       2194 @{ $flvinfo->{audtags} }
212             ? 0.001 * $flvinfo->{audtags}->[0]->{start} * $rate
213             : 1_000_000_000
214             );
215             }
216 298 100       26802 if (0 < length $data)
217             {
218 296         775 my $samples = $self->{audsamples} - $startsamples;
219              
220 296 100       845 my $seek = $startsamples ? int $needsamples - $startsamples : 0;
221              
222             # signed -> unsigned conversion
223 296         2056 $seek = unpack 'S', pack 's', $seek;
224              
225 296         884 my $head = pack 'vv', $samples, $seek;
226 296         2057 SWF::Element::Tag::SoundStreamBlock->new(
227             StreamSoundData => $head . $data)->pack($swf);
228             }
229             }
230 298         287471 return;
231             }
232              
233             sub _flvinfo
234             {
235 3     3   7 my $self = shift;
236              
237 3   100     17 my %flvinfo = (
      100        
      100        
      100        
      100        
      100        
238             duration => $self->{flv}->get_meta('duration') || 0,
239             vcodec => $self->{flv}->get_meta('videocodecid') || 0,
240             acodec => $self->{flv}->get_meta('audiocodecid') || 0,
241             width => $self->{flv}->get_meta('width') || 320,
242             height => $self->{flv}->get_meta('height') || 240,
243             framerate => $self->{flv}->get_meta('framerate') || 12,
244             vidbytes => 0,
245             audbytes => 0,
246             vidtags => [],
247             audtags => [],
248             );
249 3 100       21 $flvinfo{swfversion} = $flvinfo{vcodec} >= 4 ? 8 : 6;
250              
251 3 100       14 if ($self->{flv}->{body})
252             {
253 2         17 for my $tag ($self->{flv}->{body}->get_tags())
254             {
255 870 100       3934 if ($tag->isa('FLV::VideoTag'))
    100          
256             {
257 298         301 push @{ $flvinfo{vidtags} }, $tag;
  298         470  
258 298         624 $flvinfo{vidbytes} += length $tag->{data};
259             }
260             elsif ($tag->isa('FLV::AudioTag'))
261             {
262 570         578 push @{ $flvinfo{audtags} }, $tag;
  570         1061  
263 570         1255 $flvinfo{audbytes} += length $tag->{data};
264             }
265             }
266             }
267              
268 3         57 return \%flvinfo;
269             }
270              
271             sub _startswf
272             {
273 3     3   8 my $self = shift;
274 3         6 my $flvinfo = shift;
275              
276             # SWF header
277 3         7 my $twp = 20; # 20 twips per pixel
278 3         54 my $swf = SWF::File->new(
279             undef,
280             Version => $flvinfo->{swfversion},
281             FrameSize =>
282             [0, 0, $twp * $flvinfo->{width}, $twp * $flvinfo->{height}],
283             FrameRate => $flvinfo->{framerate},
284             );
285              
286             ## Populate the SWF
287              
288             # Generic stuff...
289 3         778 my $bg = $self->{background_color};
290 3         51 SWF::Element::Tag::SetBackgroundColor->new(
291             BackgroundColor => [
292             Red => $bg->[0],
293             Green => $bg->[1],
294             Blue => $bg->[2],
295             ],
296             )->pack($swf);
297              
298             # Add the audio stream header
299 3 100       1946 if (@{ $flvinfo->{audtags} })
  3         18  
300             {
301 2         7 my $tag = $flvinfo->{audtags}->[0];
302 2         30 (my $arate = $AUDIO_RATES{ $tag->{rate} }) =~ s/\D//gxms;
303 2         71 SWF::Element::Tag::SoundStreamHead->new(
304             StreamSoundCompression => $tag->{format},
305             PlaybackSoundRate => $tag->{rate},
306             StreamSoundRate => $tag->{rate},
307             PlaybackSoundSize => $tag->{size},
308             StreamSoundSize => $tag->{size},
309             PlaybackSoundType => $tag->{type},
310             StreamSoundType => $tag->{type},
311             StreamSoundSampleCount => $arate / $flvinfo->{framerate},
312             )->pack($swf);
313             }
314              
315             # Add the video stream header
316 3 100       980 if (@{ $flvinfo->{vidtags} })
  3         16  
317             {
318 2         5 my $tag = $flvinfo->{vidtags}->[0];
319 2         27 SWF::Element::Tag::DefineVideoStream->new(
320             CharacterID => 1,
321 2         5 NumFrames => scalar @{ $flvinfo->{vidtags} },
322             Width => $flvinfo->{width},
323             Height => $flvinfo->{height},
324             VideoFlags => 1, # Smoothing on
325             CodecID => $tag->{codec},
326             )->pack($swf);
327             }
328              
329 3         1459 return $swf;
330             }
331              
332             sub _round_to_samples
333             {
334 570     570   786 my $pkg_or_self = shift;
335 570         1994 my $samples = shift;
336              
337 570         3595 return 576 * int $samples / 576 + 0.5;
338             }
339              
340             1;
341              
342             __END__