File Coverage

blib/lib/Image/ExifTool/Ogg.pm
Criterion Covered Total %
statement 89 98 90.8
branch 49 72 68.0
condition 17 30 56.6
subroutine 5 5 100.0
pod 0 2 0.0
total 160 207 77.2


line stmt bran cond sub pod time code
1             #------------------------------------------------------------------------------
2             # File: Ogg.pm
3             #
4             # Description: Read Ogg meta information
5             #
6             # Revisions: 2011/07/13 - P. Harvey Created (split from Vorbis.pm)
7             # 2016/07/14 - PH Added Ogg Opus support
8             #
9             # References: 1) http://www.xiph.org/vorbis/doc/
10             # 2) http://flac.sourceforge.net/ogg_mapping.html
11             # 3) http://www.theora.org/doc/Theora.pdf
12             #------------------------------------------------------------------------------
13              
14             package Image::ExifTool::Ogg;
15              
16 3     3   18 use strict;
  3         4  
  3         85  
17 3     3   12 use vars qw($VERSION);
  3         5  
  3         107  
18 3     3   13 use Image::ExifTool qw(:DataAccess :Utils);
  3         6  
  3         3328  
19              
20             $VERSION = '1.02';
21              
22             my $MAX_PACKETS = 2; # maximum packets to scan from each stream at start of file
23              
24             # Information types recognizedi in Ogg files
25             %Image::ExifTool::Ogg::Main = (
26             NOTES => q{
27             ExifTool extracts the following types of information from Ogg files. See
28             L for the Ogg specification.
29             },
30             # (these are for documentation purposes only, and aren't used by the code below)
31             vorbis => { SubDirectory => { TagTable => 'Image::ExifTool::Vorbis::Main' } },
32             theora => { SubDirectory => { TagTable => 'Image::ExifTool::Theora::Main' } },
33             Opus => { SubDirectory => { TagTable => 'Image::ExifTool::Opus::Main' } },
34             FLAC => { SubDirectory => { TagTable => 'Image::ExifTool::FLAC::Main' } },
35             ID3 => { SubDirectory => { TagTable => 'Image::ExifTool::ID3::Main' } },
36             );
37              
38             #------------------------------------------------------------------------------
39             # Process Ogg packet
40             # Inputs: 0) ExifTool object ref, 1) data ref
41             # Returns: 1 on success
42             sub ProcessPacket($$)
43             {
44 4     4 0 7 my ($et, $dataPt) = @_;
45 4         5 my $rtnVal = 0;
46 4 50 66     24 if ($$dataPt =~ /^(.)(vorbis|theora)/s or $$dataPt =~ /^(OpusHead|OpusTags)/) {
47 4 100       19 my ($tag, $type, $pos) = $2 ? (ord($1), ucfirst($2), 7) : ($1, 'Opus', 8);
48             # this is an OGV file if it contains Theora video
49 4 50 33     10 $et->OverrideFileType('OGV') if $type eq 'Theora' and $$et{FILE_TYPE} eq 'OGG';
50 4 100 66     15 $et->OverrideFileType('OPUS') if $type eq 'Opus' and $$et{FILE_TYPE} eq 'OGG';
51 4         14 my $tagTablePtr = GetTagTable("Image::ExifTool::${type}::Main");
52 4         10 my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag);
53 4 50 33     17 return 0 unless $tagInfo and $$tagInfo{SubDirectory};
54 4         5 my $subdir = $$tagInfo{SubDirectory};
55             my %dirInfo = (
56             DataPt => $dataPt,
57             DirName => $$tagInfo{Name},
58 4         12 DirStart => $pos,
59             );
60 4         9 my $table = GetTagTable($$subdir{TagTable});
61             # set group1 so Theoris comments can be distinguised from Vorbis comments
62 4 50       9 $$et{SET_GROUP1} = $type if $type eq 'Theora';
63 4 50       7 SetByteOrder($$subdir{ByteOrder}) if $$subdir{ByteOrder};
64 4         14 $rtnVal = $et->ProcessDirectory(\%dirInfo, $table);
65 4         9 SetByteOrder('II');
66 4         9 delete $$et{SET_GROUP1};
67             }
68 4         7 return $rtnVal;
69             }
70              
71             #------------------------------------------------------------------------------
72             # Extract information from an Ogg file
73             # Inputs: 0) ExifTool object reference, 1) dirInfo reference
74             # Returns: 1 on success, 0 if this wasn't a valid Ogg file
75             sub ProcessOGG($$)
76             {
77 3     3 0 9 my ($et, $dirInfo) = @_;
78              
79             # must first check for leading/trailing ID3 information
80 3 50       9 unless ($$et{DoneID3}) {
81 3         995 require Image::ExifTool::ID3;
82 3 50       11 Image::ExifTool::ID3::ProcessID3($et, $dirInfo) and return 1;
83             }
84 3         6 my $raf = $$dirInfo{RAF};
85 3         8 my $verbose = $et->Options('Verbose');
86 3         7 my $out = $et->Options('TextOut');
87 3         7 my ($success, $page, $packets, $streams, $stream) = (0,0,0,0,'');
88 3         4 my ($buff, $flag, %val, $numFlac, %streamPage);
89              
90 3         4 for (;;) {
91             # must read ahead to next page to see if it is a continuation
92             # (this code would be a lot simpler if the continuation flag
93             # was on the leading instead of the trailing page!)
94 10 100 66     29 if ($raf and $raf->Read($buff, 28) == 28) {
95             # validate magic number
96 9 50       32 unless ($buff =~ /^OggS/) {
97 0 0       0 $success and $et->Warn('Lost synchronization');
98 0         0 last;
99             }
100 9 100       17 unless ($success) {
101             # set file type and initialize on first page
102 3         23 $success = 1;
103 3         14 $et->SetFileType();
104 3         9 SetByteOrder('II');
105             }
106 9         20 $flag = Get8u(\$buff, 5); # page flag
107 9         19 $stream = Get32u(\$buff, 14); # stream serial number
108 9 100       18 if ($flag & 0x02) {
109 3         4 ++$streams; # count start-of-stream pages
110 3         7 $streamPage{$stream} = $page = 0;
111             } else {
112 6         11 $page = $streamPage{$stream};
113             }
114 9 100       19 ++$packets unless $flag & 0x01; # keep track of packet count
115             } else {
116             # all done unless we have to process our last packet
117 1 50       3 last unless %val;
118 1         3 ($stream) = sort keys %val; # take a stream
119 1         2 $flag = 0; # no continuation
120 1         1 undef $raf; # flag for done reading
121             }
122              
123 10 100       14 if (defined $numFlac) {
124             # stop to process FLAC headers if we hit the end of file
125 2 100       3 last unless $raf;
126 1         1 --$numFlac; # one less header packet to read
127             } else {
128             # can finally process previous packet from this stream
129             # unless this is a continuation page
130 8 100 100     26 if (defined $val{$stream} and not $flag & 0x01) {
131 4         9 ProcessPacket($et, \$val{$stream});
132 4         7 delete $val{$stream};
133             # only read the first $MAX_PACKETS packets from each stream
134 4 100 66     13 if ($packets > $MAX_PACKETS * $streams or not defined $raf) {
135 2 50       6 last unless %val; # all done (success!)
136             }
137             }
138             # stop processing Ogg if we have scanned enough packets
139 6 50 33     94 last if $packets > $MAX_PACKETS * $streams and not %val;
140             }
141              
142             # continue processing the current page
143 7         27 my $pageNum = Get32u(\$buff, 18); # page sequence number
144 7         20 my $nseg = Get8u(\$buff, 26); # number of segments
145             # calculate total data length
146 7         14 my $dataLen = Get8u(\$buff, 27);
147 7 50       12 if ($nseg) {
148 7 50       19 $raf->Read($buff, $nseg-1) == $nseg-1 or last;
149 7         18 my @segs = unpack('C*', $buff);
150             # could check that all these (but the last) are 255...
151 7         10 foreach (@segs) { $dataLen += $_ }
  22         27  
152             }
153 7 50       11 if (defined $page) {
154 7 50       15 if ($page == $pageNum) {
155 7         11 $streamPage{$stream} = ++$page;
156             } else {
157 0         0 $et->Warn('Missing page(s) in Ogg file');
158 0         0 undef $page;
159 0         0 delete $streamPage{$stream};
160             }
161             }
162             # read page data
163 7 50       14 $raf->Read($buff, $dataLen) == $dataLen or last;
164 7 50       13 if ($verbose > 1) {
165 0         0 printf $out "Page %d, stream 0x%x, flag 0x%x (%d bytes)\n",
166             $pageNum, $stream, $flag, $dataLen;
167 0         0 $et->VerboseDump(\$buff, DataPos => $raf->Tell() - $dataLen);
168             }
169 7 100       32 if (defined $val{$stream}) {
    50          
170 2         13 $val{$stream} .= $buff; # add this continuation page
171             } elsif (not $flag & 0x01) { # ignore remaining pages of a continued packet
172             # ignore the first page of any packet we aren't parsing
173 5 100       26 if ($buff =~ /^(.(vorbis|theora)|Opus(Head|Tags))/s) {
    50          
174 4         9 $val{$stream} = $buff; # save this page
175             } elsif ($buff =~ /^\x7fFLAC..(..)/s) {
176 1         3 $numFlac = unpack('n',$1);
177 1         3 $val{$stream} = substr($buff, 9);
178             }
179             }
180 7 100 33     27 if (defined $numFlac) {
    50          
181             # stop to process FLAC headers if we have them all
182 2 50       5 last if $numFlac <= 0;
183             } elsif (defined $val{$stream} and $flag & 0x04) {
184             # process Ogg packet now if end-of-stream bit is set
185 0         0 ProcessPacket($et, \$val{$stream});
186 0         0 delete $val{$stream};
187             }
188             }
189 3 100 66     11 if (defined $numFlac and defined $val{$stream}) {
190             # process FLAC headers as if it was a complete FLAC file
191 1         16 require Image::ExifTool::FLAC;
192 1         5 my %dirInfo = ( RAF => new File::RandomAccess(\$val{$stream}) );
193 1         4 Image::ExifTool::FLAC::ProcessFLAC($et, \%dirInfo);
194             }
195 3         10 return $success;
196             }
197              
198             1; # end
199              
200             __END__