File Coverage

blib/lib/FLV/FromSWF.pm
Criterion Covered Total %
statement 164 182 90.1
branch 21 38 55.2
condition 3 9 33.3
subroutine 24 24 100.0
pod 3 3 100.0
total 215 256 83.9


line stmt bran cond sub pod time code
1             package FLV::FromSWF;
2              
3 1     1   1553 use warnings;
  1         4  
  1         39  
4 1     1   7 use strict;
  1         1  
  1         36  
5 1     1   27 use 5.008;
  1         4  
  1         31  
6              
7 1     1   877 use SWF::File;
  1         120242  
  1         32  
8 1     1   10 use SWF::Parser;
  1         2  
  1         21  
9 1     1   6 use SWF::Element;
  1         3  
  1         19  
10 1     1   6 use FLV::File;
  1         1  
  1         23  
11 1     1   6 use FLV::Util;
  1         2  
  1         165  
12 1     1   7 use FLV::AudioTag;
  1         11  
  1         22  
13 1     1   5 use FLV::VideoTag;
  1         2  
  1         30  
14 1     1   6 use English qw(-no_match_vars);
  1         3  
  1         10  
15 1     1   456 use Carp;
  1         3  
  1         1851  
16              
17             our $VERSION = '0.24';
18              
19             =for stopwords SWF transcodes
20              
21             =head1 NAME
22              
23             FLV::FromSWF - Convert a SWF file into an FLV file
24              
25             =head1 LICENSE
26              
27             See L
28              
29             =head1 SYNOPSIS
30              
31             use FLV::FromSwf;
32             my $converter = FLV::FromSWF->new();
33             $converter->parse_swf($swf_filename);
34             $converter->save($flv_filename);
35              
36             See also L.
37              
38             =head1 DESCRIPTION
39              
40             Transcodes SWF files into FLV files. See the L command-line
41             program for a nice interface and a detailed list of caveats and
42             limitations.
43              
44             =head1 METHODS
45              
46             =over
47              
48             =item $pkg->new()
49              
50             Instantiate a converter and prepare an empty FLV.
51              
52             =cut
53              
54             sub new
55             {
56 5     5 1 260 my $pkg = shift;
57              
58 5         58 my $self = bless { flv => FLV::File->new() }, $pkg;
59 5         43 $self->{flv}->empty();
60 5         36 $self->{flv}->set_meta(canSeekToEnd => 1);
61 5         19 return $self;
62             }
63              
64             =item $self->parse_swf($swf_filename)
65              
66             Open and traverse the specified SWF file, creating FLV data as we find
67             video and audio nodes.
68              
69             =cut
70              
71             sub parse_swf
72             {
73 5     5 1 33 my $self = shift;
74 5         9 my $infile = shift;
75              
76 5         15 $self->{framenumber} = 0;
77 5         17 $self->{samples} = 0;
78 5         13 $self->{videobytes} = 0;
79 5         11 $self->{audiobytes} = 0;
80              
81             my $parser = SWF::Parser->new(
82 4     4   2179 header_callback => sub { $self->_header(@_); },
83 2398     2398   236205 tag_callback => sub { $self->_tag(@_); },
84 5         108 );
85 5         513 $parser->parse_file($infile);
86              
87             # This is a rough approximation, but should be good enough
88 4         499 my $duration = $self->{flv}->get_meta('duration');
89 4         20 my $vidrate = $self->{videobytes} * 8 / (1024 * $duration); # kbps
90 4         15 my $audrate = $self->{audiobytes} * 8 / (1024 * $duration); # kbps
91 4         27 $self->{flv}->set_meta(videodatarate => $vidrate);
92 4         20 $self->{flv}->set_meta(audiodatarate => $audrate);
93              
94 4         69 return;
95             }
96              
97             =item $self->save($flv_filename)
98              
99             Write out an FLV file. Note: this should be called only after
100             C. Throws an exception upon error.
101              
102             =cut
103              
104             sub save
105             {
106 3     3 1 211471 my $self = shift;
107 3         10 my $outfile = shift;
108              
109 3         38 my $outfh = FLV::Util->get_write_filehandle($outfile);
110 3 100       13 if (!$outfh)
111             {
112 1         13 die 'Failed to write FLV: ' . $OS_ERROR;
113             }
114              
115 2         58 $self->{flv}->set_meta(creationdate => scalar gmtime);
116 2 50       130 if (!$self->{flv}->serialize($outfh))
117             {
118 0         0 die 'Failed to write FLV';
119             }
120 2 50       203 close $outfh or die 'Failed to finish writing FLV';
121 2         23 return;
122             }
123              
124             sub _header
125             {
126 4     4   19 my ($self, $parser, @r) = @_;
127              
128 4         23 my %header;
129 4         29 @header{qw(signature version filelen xmin ymin xmax ymax rate count)} = @r;
130 4         11 $self->{header} = \%header;
131              
132 4         24 $self->{flv}->set_meta(framerate => $header{rate});
133 4         25 $self->{flv}->set_meta(duration => $header{count} / $header{rate});
134 4         22 return;
135             }
136              
137             my %tag_subs = (
138             DefineVideoStream => \&_video_stream,
139             VideoFrame => \&_video_frame,
140             SoundStreamHead => \&_audio_stream,
141             SoundStreamHead2 => \&_audio_stream,
142             SoundStreamBlock => \&_audio_block,
143             ShowFrame => \&_show_frame,
144             );
145              
146             sub _tag
147             {
148 2398     2398   3448 my $self = shift;
149 2398         2548 my $parser = shift;
150 2398         2586 my $tagid = shift;
151 2398         2534 my $length = shift;
152 2398         2542 my $stream = shift;
153              
154             # Naughty code: we use a private method from SWF::Element::Tag to
155             # save ourselves the trouble of maintaining a mapping of tag ID to
156             # human-readable name.
157             # TODO: rewrite to use SWF::Element::Tag methods
158              
159             ## no critic(ProtectPrivateSubs)
160 2398         7078 my $tagname = SWF::Element::Tag->_tag_class($tagid);
161 2398         16605 $tagname =~ s/SWF::Element::Tag:://xms;
162              
163 2398         3800 my $tag_sub = $tag_subs{$tagname};
164 2398 100       5189 if ($tag_sub)
165             {
166 1794         3133 $self->$tag_sub($stream, $length);
167             }
168              
169 2398         6842 return;
170             }
171              
172             sub _show_frame
173             {
174 598     598   724 my $self = shift;
175 598         582 my $stream = shift;
176 598         576 my $length = shift;
177              
178 598         885 $self->{framenumber}++;
179 598         955 return;
180             }
181              
182             sub _audio_stream
183             {
184 4     4   7 my $self = shift;
185 4         7 my $stream = shift;
186 4         9 my $length = shift;
187              
188 4         16 my $streamhead = $stream->get_string(4);
189 4         219 my ($playflags, $streamflags, $count) = unpack 'CCv', $streamhead;
190 4         22 $self->{audiocodec} = ($streamflags >> 4) & 0xf;
191 4         19 $self->{audiorate} = ($streamflags >> 2) & 0x3;
192 4         13 $self->{audiosize} = ($streamflags >> 1) & 0x1;
193 4         13 $self->{stereo} = $streamflags & 0x1;
194              
195 4 50 33     33 if (2 == $self->{audiocodec} && 4 < $length)
196             {
197 0         0 my ($latency) = unpack 'v', $stream->get_string(2);
198              
199             # unsigned -> signed conversion
200 0         0 $self->{audiolatency} = unpack 's', pack 'S', $latency;
201             }
202 4         13 $self->{flv}->{header}->{has_audio} = 1;
203 4         18 $self->{flv}->set_meta(audiocodecid => $self->{audiocodec});
204              
205 4         7 return;
206             }
207              
208             sub _audio_block
209             {
210 592     592   773 my $self = shift;
211 592         636 my $stream = shift;
212 592         620 my $length = shift;
213              
214 592 50       1220 if (0 == $length) # empty block
215             {
216 0         0 warn 'Skipping empty audio block';
217 0         0 return;
218             }
219              
220 592         1850 my $audiotag = FLV::AudioTag->new();
221              
222             # time calculation will be redone for MP3...
223 592         1693 my $millisec = 1000 * $self->{framenumber} / $self->{header}->{rate};
224              
225 592         1551 $audiotag->{format} = $self->{audiocodec};
226 592         1279 $audiotag->{rate} = $self->{audiorate};
227 592         1057 $audiotag->{size} = $self->{audiosize};
228 592         1105 $audiotag->{type} = $self->{stereo};
229              
230 592 50       1157 if (2 == $self->{audiocodec})
231             {
232 592 50       1245 if (4 == $length) # empty block
233             {
234 0         0 warn 'Skipping empty audio block';
235 0         0 return;
236             }
237              
238 592         1779 my ($samples) = unpack 'v', $stream->get_string(2);
239 592         17722 my ($seek) = unpack 'v', $stream->get_string(2);
240              
241             # unsigned -> signed conversion
242 592         21537 $seek = unpack 's', pack 'S', $seek;
243              
244 592         1685 $audiotag->{data} = $stream->get_string($length - 4);
245              
246 592         17748 (my $rate = $AUDIO_RATES{ $self->{audiorate} }) =~ s/\D//gxms;
247              
248 592 100       7440 if (0 == $self->{samples})
249             {
250 4         8 my $frame = $self->{framenumber};
251 4 50       13 if (1 == $frame)
252             {
253              
254             # Often audio skips one frame.
255             # This is true for On2 SWFs, but not Sorenson.
256 4         6 $frame = 0;
257             }
258              
259 4         31 $self->{samples} = $rate * $frame / $self->{header}->{rate};
260             }
261              
262 592         1286 $millisec = 1000 * $self->{samples} / $rate;
263 592 50 33     2933 if (4_000_000_000 < $millisec || 0 > $millisec)
264             {
265 0         0 warn 'Funny output timestamp: '
266             . "$millisec ($self->{samples}, $samples, $rate)";
267             }
268 592         1193 $self->{samples} += $samples;
269             }
270             else
271             {
272 0         0 $audiotag->{data} = $stream->get_string($length);
273             }
274 592         1160 $audiotag->{start} = int $millisec;
275              
276 592         629 push @{ $self->{flv}->{body}->{tags} }, $audiotag;
  592         1492  
277 592         938 $self->{audiobytes} += $length;
278              
279 592         1035 return;
280             }
281              
282             sub _video_stream
283             {
284 4     4   9 my $self = shift;
285 4         8 my $stream = shift;
286 4         7 my $length = shift;
287              
288 4         16 my ($streamid, $nframes, $width, $height, $flags, $codec)
289             = unpack 'vvvvCC', $stream->get_string(10);
290 4 50       120 if ($self->{streamid})
291             {
292 0         0 warn 'Found multiple video streams in this SWF, ignoring all but one';
293 0         0 return;
294             }
295 4         8 $self->{streamid} = $streamid;
296 4         9 $self->{codec} = $codec;
297              
298 4         10 $self->{flv}->{header}->{has_video} = 1;
299              
300 4         18 $self->{flv}->set_meta(videocodecid => $codec);
301 4         16 $self->{flv}->set_meta(width => $width);
302 4         18 $self->{flv}->set_meta(height => $height);
303              
304 4         10 return;
305             }
306              
307             sub _video_frame
308             {
309 596     596   847 my $self = shift;
310 596         937 my $stream = shift;
311 596         565 my $length = shift;
312              
313 596 50       1188 if (0 == $length) # empty block
314             {
315 0         0 warn 'Skipping empty video block';
316 0         0 return;
317             }
318              
319 596         1712 my ($streamid, $framenum) = unpack 'vv', $stream->get_string(4);
320 596 50       17183 return if ($self->{streamid} != $streamid);
321 596         1997 my $videotag = FLV::VideoTag->new();
322 596         1567 my $millisec = 1000 * $self->{framenumber} / $self->{header}->{rate};
323 596         1388 $videotag->{start} = int $millisec;
324 596         1876 $videotag->{data} = $stream->get_string($length - 4);
325 596         17549 $videotag->{codec} = $self->{codec};
326              
327             ## no critic(ControlStructures::ProhibitCascadingIfElse)
328              
329 596 100 33     2454 if (2 == $self->{codec})
    50          
    50          
    0          
330             {
331 298         1048 $videotag->_parse_h263(0);
332             }
333             elsif (3 == $self->{codec} || 6 == $self->{codec})
334             {
335              
336             # zeroth frame is a key frame, all others are deltas. Right???
337 0         0 $videotag->_parse_screen_video(0);
338 0 0       0 $videotag->{type} = $framenum ? 2 : 1;
339             }
340             elsif (4 == $self->{codec})
341             {
342              
343             # prepend pixel offsets present in FLV, but absent in SWF
344 298         367 my $offset = pack 'C', 0;
345 298         955 $videotag->{data} = $offset . $videotag->{data};
346 298         1182 $videotag->_parse_on2vp6(0);
347             }
348             elsif (5 == $self->{codec})
349             {
350              
351             # prepend pixel offsets present in FLV, but absent in SWF
352 0         0 my $offset = pack 'C', 0;
353 0         0 $videotag->{data} = $offset . $videotag->{data};
354 0         0 $videotag->_parse_on2vp6_alpha(0);
355             }
356              
357 596         673 push @{ $self->{flv}->{body}->{tags} }, $videotag;
  596         1555  
358 596         859 $self->{videobytes} += $length;
359              
360 596         999 return;
361             }
362              
363             1;
364              
365             __END__