test_pie/external/exiftool/lib/Image/ExifTool/Ogg.pm

241 lines
8.8 KiB
Perl

#------------------------------------------------------------------------------
# File: Ogg.pm
#
# Description: Read Ogg meta information
#
# Revisions: 2011/07/13 - P. Harvey Created (split from Vorbis.pm)
# 2016/07/14 - PH Added Ogg Opus support
#
# References: 1) http://www.xiph.org/vorbis/doc/
# 2) http://flac.sourceforge.net/ogg_mapping.html
# 3) http://www.theora.org/doc/Theora.pdf
#------------------------------------------------------------------------------
package Image::ExifTool::Ogg;
use strict;
use vars qw($VERSION);
use Image::ExifTool qw(:DataAccess :Utils);
$VERSION = '1.02';
my $MAX_PACKETS = 2; # maximum packets to scan from each stream at start of file
# Information types recognizedi in Ogg files
%Image::ExifTool::Ogg::Main = (
NOTES => q{
ExifTool extracts the following types of information from Ogg files. See
L<http://www.xiph.org/vorbis/doc/> for the Ogg specification.
},
# (these are for documentation purposes only, and aren't used by the code below)
vorbis => { SubDirectory => { TagTable => 'Image::ExifTool::Vorbis::Main' } },
theora => { SubDirectory => { TagTable => 'Image::ExifTool::Theora::Main' } },
Opus => { SubDirectory => { TagTable => 'Image::ExifTool::Opus::Main' } },
FLAC => { SubDirectory => { TagTable => 'Image::ExifTool::FLAC::Main' } },
ID3 => { SubDirectory => { TagTable => 'Image::ExifTool::ID3::Main' } },
);
#------------------------------------------------------------------------------
# Process Ogg packet
# Inputs: 0) ExifTool object ref, 1) data ref
# Returns: 1 on success
sub ProcessPacket($$)
{
my ($et, $dataPt) = @_;
my $rtnVal = 0;
if ($$dataPt =~ /^(.)(vorbis|theora)/s or $$dataPt =~ /^(OpusHead|OpusTags)/) {
my ($tag, $type, $pos) = $2 ? (ord($1), ucfirst($2), 7) : ($1, 'Opus', 8);
# this is an OGV file if it contains Theora video
$et->OverrideFileType('OGV') if $type eq 'Theora' and $$et{FILE_TYPE} eq 'OGG';
$et->OverrideFileType('OPUS') if $type eq 'Opus' and $$et{FILE_TYPE} eq 'OGG';
my $tagTablePtr = GetTagTable("Image::ExifTool::${type}::Main");
my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag);
return 0 unless $tagInfo and $$tagInfo{SubDirectory};
my $subdir = $$tagInfo{SubDirectory};
my %dirInfo = (
DataPt => $dataPt,
DirName => $$tagInfo{Name},
DirStart => $pos,
);
my $table = GetTagTable($$subdir{TagTable});
# set group1 so Theoris comments can be distinguised from Vorbis comments
$$et{SET_GROUP1} = $type if $type eq 'Theora';
SetByteOrder($$subdir{ByteOrder}) if $$subdir{ByteOrder};
$rtnVal = $et->ProcessDirectory(\%dirInfo, $table);
SetByteOrder('II');
delete $$et{SET_GROUP1};
}
return $rtnVal;
}
#------------------------------------------------------------------------------
# Extract information from an Ogg file
# Inputs: 0) ExifTool object reference, 1) dirInfo reference
# Returns: 1 on success, 0 if this wasn't a valid Ogg file
sub ProcessOGG($$)
{
my ($et, $dirInfo) = @_;
# must first check for leading/trailing ID3 information
unless ($$et{DoneID3}) {
require Image::ExifTool::ID3;
Image::ExifTool::ID3::ProcessID3($et, $dirInfo) and return 1;
}
my $raf = $$dirInfo{RAF};
my $verbose = $et->Options('Verbose');
my $out = $et->Options('TextOut');
my ($success, $page, $packets, $streams, $stream) = (0,0,0,0,'');
my ($buff, $flag, %val, $numFlac, %streamPage);
for (;;) {
# must read ahead to next page to see if it is a continuation
# (this code would be a lot simpler if the continuation flag
# was on the leading instead of the trailing page!)
if ($raf and $raf->Read($buff, 28) == 28) {
# validate magic number
unless ($buff =~ /^OggS/) {
$success and $et->Warn('Lost synchronization');
last;
}
unless ($success) {
# set file type and initialize on first page
$success = 1;
$et->SetFileType();
SetByteOrder('II');
}
$flag = Get8u(\$buff, 5); # page flag
$stream = Get32u(\$buff, 14); # stream serial number
if ($flag & 0x02) {
++$streams; # count start-of-stream pages
$streamPage{$stream} = $page = 0;
} else {
$page = $streamPage{$stream};
}
++$packets unless $flag & 0x01; # keep track of packet count
} else {
# all done unless we have to process our last packet
last unless %val;
($stream) = sort keys %val; # take a stream
$flag = 0; # no continuation
undef $raf; # flag for done reading
}
if (defined $numFlac) {
# stop to process FLAC headers if we hit the end of file
last unless $raf;
--$numFlac; # one less header packet to read
} else {
# can finally process previous packet from this stream
# unless this is a continuation page
if (defined $val{$stream} and not $flag & 0x01) {
ProcessPacket($et, \$val{$stream});
delete $val{$stream};
# only read the first $MAX_PACKETS packets from each stream
if ($packets > $MAX_PACKETS * $streams or not defined $raf) {
last unless %val; # all done (success!)
}
}
# stop processing Ogg if we have scanned enough packets
last if $packets > $MAX_PACKETS * $streams and not %val;
}
# continue processing the current page
my $pageNum = Get32u(\$buff, 18); # page sequence number
my $nseg = Get8u(\$buff, 26); # number of segments
# calculate total data length
my $dataLen = Get8u(\$buff, 27);
if ($nseg) {
$raf->Read($buff, $nseg-1) == $nseg-1 or last;
my @segs = unpack('C*', $buff);
# could check that all these (but the last) are 255...
foreach (@segs) { $dataLen += $_ }
}
if (defined $page) {
if ($page == $pageNum) {
$streamPage{$stream} = ++$page;
} else {
$et->Warn('Missing page(s) in Ogg file');
undef $page;
delete $streamPage{$stream};
}
}
# read page data
$raf->Read($buff, $dataLen) == $dataLen or last;
if ($verbose > 1) {
printf $out "Page %d, stream 0x%x, flag 0x%x (%d bytes)\n",
$pageNum, $stream, $flag, $dataLen;
$et->VerboseDump(\$buff, DataPos => $raf->Tell() - $dataLen);
}
if (defined $val{$stream}) {
$val{$stream} .= $buff; # add this continuation page
} elsif (not $flag & 0x01) { # ignore remaining pages of a continued packet
# ignore the first page of any packet we aren't parsing
if ($buff =~ /^(.(vorbis|theora)|Opus(Head|Tags))/s) {
$val{$stream} = $buff; # save this page
} elsif ($buff =~ /^\x7fFLAC..(..)/s) {
$numFlac = unpack('n',$1);
$val{$stream} = substr($buff, 9);
}
}
if (defined $numFlac) {
# stop to process FLAC headers if we have them all
last if $numFlac <= 0;
} elsif (defined $val{$stream} and $flag & 0x04) {
# process Ogg packet now if end-of-stream bit is set
ProcessPacket($et, \$val{$stream});
delete $val{$stream};
}
}
if (defined $numFlac and defined $val{$stream}) {
# process FLAC headers as if it was a complete FLAC file
require Image::ExifTool::FLAC;
my %dirInfo = ( RAF => new File::RandomAccess(\$val{$stream}) );
Image::ExifTool::FLAC::ProcessFLAC($et, \%dirInfo);
}
return $success;
}
1; # end
__END__
=head1 NAME
Image::ExifTool::Ogg - Read Ogg meta information
=head1 SYNOPSIS
This module is used by Image::ExifTool
=head1 DESCRIPTION
This module contains definitions required by Image::ExifTool to extract meta
information from Ogg bitstream container files.
=head1 AUTHOR
Copyright 2003-2018, Phil Harvey (phil at owl.phy.queensu.ca)
This library is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.
=head1 REFERENCES
=over 4
=item L<http://www.xiph.org/vorbis/doc/>
=item L<http://flac.sourceforge.net/ogg_mapping.html>
=item L<http://www.theora.org/doc/Theora.pdf>
=back
=head1 SEE ALSO
L<Image::ExifTool::TagNames/Ogg Tags>,
L<Image::ExifTool(3pm)|Image::ExifTool>
=cut