File Coverage

blib/lib/Image/ExifTool/Ogg.pm
Criterion Covered Total %
statement 90 99 90.9
branch 50 74 67.5
condition 18 33 54.5
subroutine 5 5 100.0
pod 0 2 0.0
total 163 213 76.5


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   22 use strict;
  3         7  
  3         192  
17 3     3   20 use vars qw($VERSION);
  3         7  
  3         189  
18 3     3   19 use Image::ExifTool qw(:DataAccess :Utils);
  3         8  
  3         6016  
19              
20             $VERSION = '1.04';
21              
22             my $MAX_PACKETS = 2; # maximum packets to scan from each stream at start of file
23              
24             # Information types recognized 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 23 my ($et, $dataPt) = @_;
45 4         10 my $rtnVal = 0;
46 4 50 66     37 if ($$dataPt =~ /^(.)(vorbis|theora)/s or $$dataPt =~ /^(OpusHead|OpusTags)/) {
47 4 100       35 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     15 $et->OverrideFileType('OGV') if $type eq 'Theora' and $$et{FILE_TYPE} eq 'OGG';
50 4 100 66     32 $et->OverrideFileType('OPUS') if $type eq 'Opus' and $$et{FILE_TYPE} eq 'OGG';
51 4         19 my $tagTablePtr = GetTagTable("Image::ExifTool::${type}::Main");
52 4         31 my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag);
53 4 50 33     24 return 0 unless $tagInfo and $$tagInfo{SubDirectory};
54 4         10 my $subdir = $$tagInfo{SubDirectory};
55             my %dirInfo = (
56             DataPt => $dataPt,
57             DirName => $$tagInfo{Name},
58 4         22 DirStart => $pos,
59             );
60 4         16 my $table = GetTagTable($$subdir{TagTable});
61             # set group1 so Theoris comments can be distinguised from Vorbis comments
62 4 50       28 $$et{SET_GROUP1} = $type if $type eq 'Theora';
63 4 50       14 SetByteOrder($$subdir{ByteOrder}) if $$subdir{ByteOrder};
64 4         38 $rtnVal = $et->ProcessDirectory(\%dirInfo, $table);
65 4         15 SetByteOrder('II');
66 4         16 delete $$et{SET_GROUP1};
67             }
68 4         11 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       16 unless ($$et{DoneID3}) {
81 3         1554 require Image::ExifTool::ID3;
82 3 50       25 Image::ExifTool::ID3::ProcessID3($et, $dirInfo) and return 1;
83             }
84 3         12 my $raf = $$dirInfo{RAF};
85 3         18 my $verbose = $et->Options('Verbose');
86 3         13 my $out = $et->Options('TextOut');
87 3         10 my ($success, $page, $packets, $streams, $stream) = (0,0,0,0,'');
88 3         9 my ($buff, $flag, %val, $numFlac, %streamPage);
89              
90 3         6 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     57 if ($raf and $raf->Read($buff, 28) == 28) {
95             # validate magic number
96 9 50       40 unless ($buff =~ /^OggS/) {
97 0 0       0 $success and $et->Warn('Lost synchronization');
98 0         0 last;
99             }
100 9 100       26 unless ($success) {
101             # set file type and initialize on first page
102 3         6 $success = 1;
103 3         22 $et->SetFileType();
104 3         16 SetByteOrder('II');
105             }
106 9         33 $flag = Get8u(\$buff, 5); # page flag
107 9         36 $stream = Get32u(\$buff, 14); # stream serial number
108 9 100       32 if ($flag & 0x02) {
109 3         7 ++$streams; # count start-of-stream pages
110 3         13 $streamPage{$stream} = $page = 0;
111             } else {
112 6         18 $page = $streamPage{$stream};
113             }
114 9 100       29 ++$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       4 last unless %val;
118 1         4 ($stream) = sort keys %val; # take a stream
119 1         2 $flag = 0; # no continuation
120 1         3 undef $raf; # flag for done reading
121             }
122              
123 10 100       24 if (defined $numFlac) {
124             # stop to process FLAC headers if we hit the end of file
125 2 100       7 last unless $raf;
126 1         2 --$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     62 if (defined $val{$stream} and not $flag & 0x01) {
131 4         31 ProcessPacket($et, \$val{$stream});
132 4         12 delete $val{$stream};
133             # only read the first $MAX_PACKETS packets from each stream
134 4 100 66     77 if ($packets > $MAX_PACKETS * $streams or not defined $raf) {
135 2 50       10 last unless %val; # all done (success!)
136             }
137             }
138             # stop processing Ogg if we have scanned enough packets
139 6 50 33     24 last if $packets > $MAX_PACKETS * $streams and not %val;
140             }
141              
142             # continue processing the current page
143 7         30 my $pageNum = Get32u(\$buff, 18); # page sequence number
144 7         55 my $nseg = Get8u(\$buff, 26); # number of segments
145             # calculate total data length
146 7         20 my $dataLen = Get8u(\$buff, 27);
147 7 50       23 if ($nseg) {
148 7 50       24 last unless $raf;
149 7 50       31 $raf->Read($buff, $nseg-1) == $nseg-1 or last;
150 7         25 my @segs = unpack('C*', $buff);
151             # could check that all these (but the last) are 255...
152 7         18 foreach (@segs) { $dataLen += $_ }
  22         40  
153             }
154 7 50       20 if (defined $page) {
155 7 50       37 if ($page == $pageNum) {
156 7         19 $streamPage{$stream} = ++$page;
157             } else {
158 0         0 $et->Warn('Missing page(s) in Ogg file');
159 0         0 undef $page;
160 0         0 delete $streamPage{$stream};
161             }
162             }
163             # read page data
164 7 50 33     35 last unless $raf and $raf->Read($buff, $dataLen) == $dataLen;
165 7 50       21 if ($verbose > 1) {
166 0         0 printf $out "Page %d, stream 0x%x, flag 0x%x (%d bytes)\n",
167             $pageNum, $stream, $flag, $dataLen;
168 0         0 $et->VerboseDump(\$buff, DataPos => $raf->Tell() - $dataLen);
169             }
170 7 100       28 if (defined $val{$stream}) {
    50          
171 2         16 $val{$stream} .= $buff; # add this continuation page
172             } elsif (not $flag & 0x01) { # ignore remaining pages of a continued packet
173             # ignore the first page of any packet we aren't parsing
174 5 100       38 if ($buff =~ /^(.(vorbis|theora)|Opus(Head|Tags))/s) {
    50          
175 4         15 $val{$stream} = $buff; # save this page
176             } elsif ($buff =~ /^\x7fFLAC..(..)/s) {
177 1         5 $numFlac = unpack('n',$1);
178 1         4 $val{$stream} = substr($buff, 9);
179             }
180             }
181 7 100 33     37 if (defined $numFlac) {
    50          
182             # stop to process FLAC headers if we have them all
183 2 50       35 last if $numFlac <= 0;
184             } elsif (defined $val{$stream} and $flag & 0x04) {
185             # process Ogg packet now if end-of-stream bit is set
186 0         0 ProcessPacket($et, \$val{$stream});
187 0         0 delete $val{$stream};
188             }
189             }
190 3 100 66     43 if (defined $numFlac and defined $val{$stream}) {
191             # process FLAC headers as if it was a complete FLAC file
192 1         9 require Image::ExifTool::FLAC;
193 1         8 my %dirInfo = ( RAF => File::RandomAccess->new(\$val{$stream}) );
194 1         6 Image::ExifTool::FLAC::ProcessFLAC($et, \%dirInfo);
195             }
196 3         20 return $success;
197             }
198              
199             1; # end
200              
201             __END__