1package FLV::ToSWF; 2 3use warnings; 4use strict; 5use 5.008; 6 7use SWF::File; 8use SWF::Element; 9use FLV::File; 10use FLV::Util; 11use FLV::AudioTag; 12use FLV::VideoTag; 13use English qw(-no_match_vars); 14use Carp; 15 16our $VERSION = '0.24'; 17 18=for stopwords SWF transcodes framerate 19 20=head1 NAME 21 22FLV::ToSWF - Convert an FLV file into a SWF file 23 24=head1 LICENSE 25 26See L<FLV::Info> 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 35See also L<flv2swf>. 36 37=head1 DESCRIPTION 38 39Transcodes FLV files into SWF files. See the L<flv2swf> command-line 40program for a nice interface and a detailed list of caveats and 41limitations. 42 43=head1 METHODS 44 45=over 46 47=item $pkg->new() 48 49Instantiate a converter and prepare an empty SWF. 50 51=cut 52 53sub new 54{ 55 my $pkg = shift; 56 57 my $self = bless { 58 flv => FLV::File->new(), 59 background_color => [0, 0, 0], # RGB, black 60 }, $pkg; 61 $self->{flv}->empty(); 62 return $self; 63} 64 65=item $self->parse_flv($flv_filename) 66 67Open and parse the specified FLV file. If the FLV file lacks 68C<onMetadata> details, that tag is populated with duration, 69framerate, video dimensions, etc. 70 71=cut 72 73sub parse_flv 74{ 75 my $self = shift; 76 my $infile = shift; 77 78 $self->{flv}->parse($infile); 79 $self->{flv}->populate_meta(); 80 81 $self->_validate(); 82 83 return; 84} 85 86sub _validate 87{ 88 my $self = shift; 89 90 my $acodec = $self->{flv}->get_meta('audiocodecid'); 91 if (defined $acodec && $acodec != 2) 92 { 93 die "Audio format $AUDIO_FORMATS{$acodec} not supported; " 94 . "only MP3 audio allowed\n"; 95 } 96 return; 97} 98 99=item $self->save($swf_filename) 100 101Write out a SWF file. Note: this is usually called only after 102C<parse_flv()>. Throws an exception upon error. 103 104=cut 105 106sub save 107{ 108 my $self = shift; 109 my $outfile = shift; 110 111 # Collect FLV info 112 my $flvinfo = $self->_flvinfo(); 113 114 # Create a new SWF 115 my $swf = $self->_startswf($flvinfo); 116 117 $self->{audsamples} = 0; 118 119 for my $i (0 .. $#{ $flvinfo->{vidtags} }) 120 { 121 my $vidtag = $flvinfo->{vidtags}->[$i]; 122 my $data = $vidtag->{data}; 123 if (4 == $vidtag->{codec} || 5 == $vidtag->{codec}) 124 { 125 126 # On2 VP6 is different in FLV vs. SWF! 127 if ($data !~ s/\A(.)//xms || $1 ne pack 'C', 0) 128 { 129 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 StreamID => 1, 135 FrameNum => $i, 136 VideoData => $data, 137 )->pack($swf); 138 139 if (0 == $i) 140 { 141 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 SWF::Element::Tag::PlaceObject2->new( 159 Flags => 17, # move and tween ratio 160 Ratio => $i, 161 Depth => 4, 162 )->pack($swf); 163 } 164 165 $self->_add_audio($swf, $flvinfo, $vidtag->{start}, 166 $i == $#{ $flvinfo->{vidtags} }); 167 168 SWF::Element::Tag::ShowFrame->new()->pack($swf); 169 } 170 171 # Save to disk 172 $swf->close(q{-} eq $outfile ? \*STDOUT : $outfile); 173 174 return; 175} 176 177sub _add_audio 178{ 179 my $self = shift; 180 my $swf = shift; 181 my $flvinfo = shift; 182 my $start = shift; 183 my $islast = shift; 184 185 if (@{ $flvinfo->{audtags} }) 186 { 187 my $data = q{}; 188 my $any_tag = $flvinfo->{audtags}->[0]; 189 my $audstart = $any_tag->{start}; 190 my $format = $any_tag->{format}; 191 my $stereo = $any_tag->{type}; 192 my $ratecode = $any_tag->{rate}; 193 194 if ($format != 2) 195 { 196 die 'Only MP3 audio supported so far...'; 197 } 198 199 (my $rate = $AUDIO_RATES{$ratecode}) =~ s/\D//gxms; 200 my $bytes_per_sample = ($stereo ? 2 : 1) * ($any_tag->{size} ? 2 : 1); 201 202 my $needsamples = int 0.001 * $start * $rate; 203 my $startsamples = $self->{audsamples}; 204 205 while (@{ $flvinfo->{audtags} } 206 && ($islast || $self->{audsamples} < $needsamples)) 207 { 208 my $atag = shift @{ $flvinfo->{audtags} }; 209 $data .= $atag->{data}; 210 $self->{audsamples} = $self->_round_to_samples( 211 @{ $flvinfo->{audtags} } 212 ? 0.001 * $flvinfo->{audtags}->[0]->{start} * $rate 213 : 1_000_000_000 214 ); 215 } 216 if (0 < length $data) 217 { 218 my $samples = $self->{audsamples} - $startsamples; 219 220 my $seek = $startsamples ? int $needsamples - $startsamples : 0; 221 222 # signed -> unsigned conversion 223 $seek = unpack 'S', pack 's', $seek; 224 225 my $head = pack 'vv', $samples, $seek; 226 SWF::Element::Tag::SoundStreamBlock->new( 227 StreamSoundData => $head . $data)->pack($swf); 228 } 229 } 230 return; 231} 232 233sub _flvinfo 234{ 235 my $self = shift; 236 237 my %flvinfo = ( 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 $flvinfo{swfversion} = $flvinfo{vcodec} >= 4 ? 8 : 6; 250 251 if ($self->{flv}->{body}) 252 { 253 for my $tag ($self->{flv}->{body}->get_tags()) 254 { 255 if ($tag->isa('FLV::VideoTag')) 256 { 257 push @{ $flvinfo{vidtags} }, $tag; 258 $flvinfo{vidbytes} += length $tag->{data}; 259 } 260 elsif ($tag->isa('FLV::AudioTag')) 261 { 262 push @{ $flvinfo{audtags} }, $tag; 263 $flvinfo{audbytes} += length $tag->{data}; 264 } 265 } 266 } 267 268 return \%flvinfo; 269} 270 271sub _startswf 272{ 273 my $self = shift; 274 my $flvinfo = shift; 275 276 # SWF header 277 my $twp = 20; # 20 twips per pixel 278 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 my $bg = $self->{background_color}; 290 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 if (@{ $flvinfo->{audtags} }) 300 { 301 my $tag = $flvinfo->{audtags}->[0]; 302 (my $arate = $AUDIO_RATES{ $tag->{rate} }) =~ s/\D//gxms; 303 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 if (@{ $flvinfo->{vidtags} }) 317 { 318 my $tag = $flvinfo->{vidtags}->[0]; 319 SWF::Element::Tag::DefineVideoStream->new( 320 CharacterID => 1, 321 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 return $swf; 330} 331 332sub _round_to_samples 333{ 334 my $pkg_or_self = shift; 335 my $samples = shift; 336 337 return 576 * int $samples / 576 + 0.5; 338} 339 3401; 341 342__END__ 343 344=back 345 346=head1 AUTHOR 347 348See L<FLV::Info> 349 350=cut 351