| FLV-Info documentation | Contained in the FLV-Info distribution. |
FLV::Body - Flash video file data structure
See FLV::Info
This is a subclass of FLV::Base.
Takes a FLV::File instance and extracts the FLV body from the file stream. This method throws exceptions if the stream is not a valid FLV v1.0 or v1.1 file.
There is no return value.
Create an independent copy of this instance.
Serializes the in-memory FLV body. If that representation is not complete, this throws an exception via croak(). Returns a boolean indicating whether writing to the file handle was successful.
Returns a hash of FLV metadata. See File::Info for more details.
Returns an array of tag instances.
Replace all of the existing tags with new ones. For example, you can remove all audio from a movie like so:
$body->set_tags(grep {!$_->isa('FLV::AudioTag')} $body->get_tags);
Returns the video tags (FLV::VideoTag instances) in the FLV stream.
Returns just the video tags which contain keyframe data.
Returns the audio tags (FLV::AudioTag instances) in the FLV stream.
Returns the meta tags (FLV::MetaTag instances) in the FLV stream.
Returns the start timestamp of the last tag, in milliseconds.
These are convenience functions for interacting with an onMetadata
tag at time 0, which is a common convention in FLV files. If the zeroth
tag is not an FLV::MetaTag instance, one is created and prepended
to the tag list.
See also get_value and set_value in FLV::MetaTag.
Consolidate zero or more meta tags into a single tag. If there are more than one tags and there are any duplicate keys, the first key takes precedence.
Create a new header from the body data.
See FLV::Info
| FLV-Info documentation | Contained in the FLV-Info distribution. |
package FLV::Body; use warnings; use strict; use 5.008; use Carp; use English qw(-no_match_vars); use File::Temp qw(); use base 'FLV::Base'; use FLV::Header; use FLV::Tag; use FLV::VideoTag; use FLV::AudioTag; use FLV::MetaTag; our $VERSION = '0.24';
sub parse { my $self = shift; my $file = shift; my $opts = shift; $opts ||= {}; my @tags; TAGS: while (1) { my $lastsize = $file->get_bytes(4); if ($file->at_end()) { last TAGS; } my $tag = FLV::Tag->new(); $tag->parse($file, $opts); # might throw exception push @tags, $tag->get_payload(); } my %tagorder = ( 'FLV::MetaTag' => 1, 'FLV::AudioTag' => 2, 'FLV::VideoTag' => 3, ); @tags = sort { $a->{start} <=> $b->{start} || $tagorder{ ref $a } <=> $tagorder{ ref $b } } @tags; $self->{tags} = \@tags; return; }
sub clone { my $self = shift; my $copy = FLV::Body->new; $copy->{tags} = [ map { $_->clone } @{$self->{tags}} ]; return $copy; }
sub serialize { my $self = shift; my $filehandle = shift || croak 'Please specify a filehandle'; my $headersize = shift || 9; return if (!print {$filehandle} pack 'V', 0); return if (!$self->{tags}); my $size_so_far = $headersize + 4; for my $i (0 .. $#{ $self->{tags} }) { my $tag = $self->{tags}->[$i]; if ( $tag->isa('FLV::MetaTag') && ( defined $tag->get_value('keyframes') || defined $tag->get_value('filesize')) ) { return $self->_serialize_with_sizes($filehandle, $i, $size_so_far); } my $size = FLV::Tag->serialize($tag, $filehandle); if (!$size) { return; } print {$filehandle} pack 'V', $size; $size_so_far += $size + 4; } return 1; } sub _serialize_with_sizes { my $self = shift; my $filehandle = shift; my $i = shift; my $size_so_far = shift; my $meta = $self->{tags}->[$i]; my $keyframes = $meta->get_value('keyframes'); my $filesize = $meta->get_value('filesize'); # Write the REST of the tags out to a tempfile my ($media_fh, $media_filename) = File::Temp::tempfile(); my $success = 1; my $pos = 0; my @filepositions; for my $tag (@{ $self->{tags} }[$i + 1 .. $#{ $self->{tags} }]) { if ($tag->isa('FLV::VideoTag') && $tag->is_keyframe()) { push @filepositions, $pos; } my $size = FLV::Tag->serialize($tag, $media_fh); if (!$size) { $success = 0; last; } print {$media_fh} pack 'V', $size; $pos += $size + 4; } close $media_fh or warn 'Unexpected error closing filehandle'; if (!$success) { # Abort, write out without file positions delete $keyframes->{filepositions}; $meta->set_value('filesize', undef); my $size = FLV::Tag->serialize($meta, $filehandle); if (!$size) { unlink $media_filename; return; } print {$filehandle} pack 'V', $size; $self->_copy_file_to_fh($media_filename, $filehandle); unlink $media_filename; return; } # Problem: changing the file positions in the metatag changes the # size of the metatag and, thus, the filepositions. # Solution: set file positions in metadata, write out as temp file # to get resulting size, and iterate until sizes converge. This # should happen on the second iteration if the sizes are written # out as numbers and not as strings. # Start with a (wrong) guess of zero bytes my ($meta_fh, $meta_filename) = File::Temp::tempfile(); close $meta_fh or warn 'Unexpected error closing filehandle'; my $tries = 0; while ($tries++ < 10) { my $meta_size = -s $meta_filename; # Put in corrected sizes my $offset = $size_so_far + $meta_size; if ($keyframes) { $keyframes->{filepositions} = [map { $offset + $_ } @filepositions]; } $meta->set_value('filesize', $offset + -s $media_filename); # Write out meta tag to tempfile # Warning: I'm ignoring the case of a failure to write out the # metatag at all my ($try_fh, $try_filename) = File::Temp::tempfile(); my $size = FLV::Tag->serialize($meta, $try_fh); if ($size) { print {$try_fh} pack 'V', $size; } close $try_fh or warn 'Unexpected error closing filehandle'; # Clean up last try. This try becomes "last try" for the next iteration unlink $meta_filename; $meta_filename = $try_filename; # Did we converge? if ($meta_size == -s $meta_filename) { # Yes! last; } # Otherwise do another iteration } $self->_copy_file_to_fh($meta_filename, $filehandle); unlink $meta_filename; $self->_copy_file_to_fh($media_filename, $filehandle); unlink $media_filename; return 1; } sub _copy_file_to_fh { my $self = shift; my $filename = shift; my $filehandle = shift; open my $fh, '<', $filename or die 'Failed to open temporary file'; binmode $fh or die 'Failed to set binary mode on file'; my $buf; while (read $fh, $buf, 4096) { print {$filehandle} $buf; } close $fh or warn 'Unexpected error closing filehandle'; return; }
sub get_info { my $self = shift; my %info = ( duration => $self->last_start_time(), FLV::VideoTag->get_info( grep { $_->isa('FLV::VideoTag') } @{ $self->{tags} } ), FLV::AudioTag->get_info( grep { $_->isa('FLV::AudioTag') } @{ $self->{tags} } ), FLV::MetaTag->get_info( grep { $_->isa('FLV::MetaTag') } @{ $self->{tags} } ), ); return %info; }
sub get_tags { my $self = shift; return @{ $self->{tags} || [] }; }
sub set_tags { my $self = shift; my @tags = @_; $self->{tags} = \@tags; return; }
sub get_video_frames { my $self = shift; return grep { $_->isa('FLV::VideoTag') } @{ $self->{tags} }; }
sub get_video_keyframes { my $self = shift; return grep { $_->isa('FLV::VideoTag') && $_->is_keyframe() } @{ $self->{tags} }; }
sub get_audio_packets { my $self = shift; return grep { $_->isa('FLV::AudioTag') } @{ $self->{tags} }; }
sub get_meta_tags { my $self = shift; return grep { $_->isa('FLV::MetaTag') } @{ $self->{tags} }; }
sub last_start_time { my $self = shift; my $tag = $self->{tags}->[-1] or die 'No tags found'; return $tag->{start}; }
sub get_meta { my $self = shift; my $key = shift; return if (!$self->{tags}); for my $meta (grep { $_->isa('FLV::MetaTag') } @{ $self->{tags} }) { my $value = $meta->get_value($key); return $value if (defined $value); } return; } sub set_meta { my ($self, @keyvalues) = @_; $self->{tags} ||= []; my @metatags = grep { $_->isa('FLV::MetaTag') } @{ $self->{tags} }; if (!@metatags) { # no metatags at all! Create one. my $new_meta = FLV::MetaTag->new(); $new_meta->{start} = 0; unshift @{ $self->{tags} }, $new_meta; @metatags = ($new_meta); } KEYVALUE: while (@keyvalues) { my ($key, $value) = splice @keyvalues, 0, 2; # Check all existing meta tags for that key for my $meta (@metatags) { if (defined $meta->get_value($key)) { $meta->set_value($key => $value); next KEYVALUE; } } # key not found $metatags[0]->set_value($key => $value); } return; }
sub merge_meta { my $self = shift; $self->{tags} ||= []; # Remove all meta tags my @meta = grep { $_->isa('FLV::MetaTag') } @{ $self->{tags} }; @{ $self->{tags} } = grep { !$_->isa('FLV::MetaTag') } @{ $self->{tags} }; # Merge all metadata my %meta = map { $_->get_values() } reverse @meta; # Insert a new metatag $self->set_meta(%meta); return; }
sub make_header { my $self = shift; my $header = FLV::Header->new; for my $tag (@{$self->{tags}}) { if ($tag->isa('FLV::VideoTag')) { $header->{has_video} = 1; } elsif ($tag->isa('FLV::AudioTag')) { $header->{has_audio} = 1; } } return $header; } 1; __END__