| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package Ogg::Vorbis::Header::PurePerl; | 
| 2 |  |  |  |  |  |  |  | 
| 3 | 3 |  |  | 3 |  | 116440 | use 5.006; | 
|  | 3 |  |  |  |  | 28 |  | 
| 4 | 3 |  |  | 3 |  | 14 | use strict; | 
|  | 3 |  |  |  |  | 5 |  | 
|  | 3 |  |  |  |  | 53 |  | 
| 5 | 3 |  |  | 3 |  | 11 | use warnings; | 
|  | 3 |  |  |  |  | 6 |  | 
|  | 3 |  |  |  |  | 106 |  | 
| 6 |  |  |  |  |  |  |  | 
| 7 |  |  |  |  |  |  | # First four bytes of stream are always OggS | 
| 8 | 3 |  |  | 3 |  | 15 | use constant OGGHEADERFLAG => 'OggS'; | 
|  | 3 |  |  |  |  | 5 |  | 
|  | 3 |  |  |  |  | 6294 |  | 
| 9 |  |  |  |  |  |  |  | 
| 10 |  |  |  |  |  |  | our $VERSION = '1.05'; | 
| 11 |  |  |  |  |  |  |  | 
| 12 |  |  |  |  |  |  | sub new { | 
| 13 | 5 |  |  | 5 | 1 | 1829 | my $class = shift; | 
| 14 | 5 |  |  |  |  | 8 | my $file  = shift; | 
| 15 |  |  |  |  |  |  |  | 
| 16 | 5 |  |  |  |  | 9 | my %data  = (); | 
| 17 |  |  |  |  |  |  |  | 
| 18 | 5 | 100 |  |  |  | 14 | if (ref $file) { | 
| 19 | 1 |  |  |  |  | 3 | binmode $file; | 
| 20 |  |  |  |  |  |  |  | 
| 21 | 1 |  |  |  |  | 12 | %data = ( | 
| 22 |  |  |  |  |  |  | 'filesize'   => -s $file, | 
| 23 |  |  |  |  |  |  | 'fileHandle' => $file, | 
| 24 |  |  |  |  |  |  | ); | 
| 25 |  |  |  |  |  |  |  | 
| 26 |  |  |  |  |  |  | } else { | 
| 27 |  |  |  |  |  |  |  | 
| 28 | 4 | 100 |  |  |  | 155 | open my $fh, '<', $file or do { | 
| 29 | 1 |  |  |  |  | 26 | warn "$class: File $file does not exist or cannot be read: $!"; | 
| 30 | 1 |  |  |  |  | 14 | return undef; | 
| 31 |  |  |  |  |  |  | }; | 
| 32 |  |  |  |  |  |  |  | 
| 33 |  |  |  |  |  |  | # make sure dos-type systems can handle it... | 
| 34 | 3 |  |  |  |  | 14 | binmode $fh; | 
| 35 |  |  |  |  |  |  |  | 
| 36 | 3 |  |  |  |  | 45 | %data = ( | 
| 37 |  |  |  |  |  |  | 'filename'   => $file, | 
| 38 |  |  |  |  |  |  | 'filesize'   => -s $file, | 
| 39 |  |  |  |  |  |  | 'fileHandle' => $fh, | 
| 40 |  |  |  |  |  |  | ); | 
| 41 |  |  |  |  |  |  | } | 
| 42 |  |  |  |  |  |  |  | 
| 43 | 4 | 100 |  |  |  | 18 | if ( _init(\%data) ) { | 
| 44 | 3 |  |  |  |  | 10 | _load_info(\%data); | 
| 45 | 3 |  |  |  |  | 10 | _load_comments(\%data); | 
| 46 | 3 |  |  |  |  | 9 | _calculate_track_length(\%data); | 
| 47 |  |  |  |  |  |  | } | 
| 48 |  |  |  |  |  |  |  | 
| 49 | 4 |  |  |  |  | 37 | undef $data{'fileHandle'}; | 
| 50 |  |  |  |  |  |  |  | 
| 51 | 4 |  |  |  |  | 31 | return bless \%data, $class; | 
| 52 |  |  |  |  |  |  | } | 
| 53 |  |  |  |  |  |  |  | 
| 54 |  |  |  |  |  |  | sub info { | 
| 55 | 3 |  |  | 3 | 1 | 6 | my $self = shift; | 
| 56 | 3 |  |  |  |  | 5 | my $key = shift; | 
| 57 |  |  |  |  |  |  |  | 
| 58 |  |  |  |  |  |  | # if the user did not supply a key, return the entire hash | 
| 59 | 3 | 100 |  |  |  | 13 | return $self->{'INFO'} unless $key; | 
| 60 |  |  |  |  |  |  |  | 
| 61 |  |  |  |  |  |  | # otherwise, return the value for the given key | 
| 62 | 1 |  |  |  |  | 5 | return $self->{'INFO'}{lc $key}; | 
| 63 |  |  |  |  |  |  | } | 
| 64 |  |  |  |  |  |  |  | 
| 65 |  |  |  |  |  |  | sub comment_tags { | 
| 66 | 1 |  |  | 1 | 1 | 2 | my $self = shift; | 
| 67 |  |  |  |  |  |  |  | 
| 68 | 1 |  |  |  |  | 3 | my %keys = (); | 
| 69 |  |  |  |  |  |  |  | 
| 70 | 1 |  |  |  |  | 2 | return grep { !$keys{$_}++ } @{$self->{'COMMENT_KEYS'}}; | 
|  | 3 |  |  |  |  | 11 |  | 
|  | 1 |  |  |  |  | 2 |  | 
| 71 |  |  |  |  |  |  | } | 
| 72 |  |  |  |  |  |  |  | 
| 73 |  |  |  |  |  |  | sub comment { | 
| 74 | 4 |  |  | 4 | 1 | 9 | my $self = shift; | 
| 75 | 4 |  |  |  |  | 6 | my $key = shift; | 
| 76 |  |  |  |  |  |  |  | 
| 77 |  |  |  |  |  |  | # if the user supplied key does not exist, return undef | 
| 78 | 4 | 50 |  |  |  | 12 | return undef unless($self->{'COMMENTS'}{lc $key}); | 
| 79 |  |  |  |  |  |  |  | 
| 80 |  |  |  |  |  |  | return wantarray | 
| 81 | 1 |  |  |  |  | 4 | ? @{$self->{'COMMENTS'}{lc $key}} | 
| 82 | 4 | 100 |  |  |  | 20 | : $self->{'COMMENTS'}{lc $key}->[0]; | 
| 83 |  |  |  |  |  |  | } | 
| 84 |  |  |  |  |  |  |  | 
| 85 |  |  |  |  |  |  | sub path { | 
| 86 | 1 |  |  | 1 | 1 | 2 | my $self = shift; | 
| 87 |  |  |  |  |  |  |  | 
| 88 | 1 |  |  |  |  | 4 | return $self->{'filename'}; | 
| 89 |  |  |  |  |  |  | } | 
| 90 |  |  |  |  |  |  |  | 
| 91 |  |  |  |  |  |  | # "private" methods | 
| 92 |  |  |  |  |  |  | sub _init { | 
| 93 | 4 |  |  | 4 |  | 8 | my $data = shift; | 
| 94 |  |  |  |  |  |  |  | 
| 95 |  |  |  |  |  |  | # check the header to make sure this is actually an Ogg-Vorbis file | 
| 96 | 4 |  | 100 |  |  | 12 | $data->{'startInfoHeader'} = _check_header($data) || return undef; | 
| 97 |  |  |  |  |  |  |  | 
| 98 | 3 |  |  |  |  | 10 | return 1; | 
| 99 |  |  |  |  |  |  | } | 
| 100 |  |  |  |  |  |  |  | 
| 101 |  |  |  |  |  |  | sub _skip_id3_header { | 
| 102 | 4 |  |  | 4 |  | 8 | my $fh = shift; | 
| 103 |  |  |  |  |  |  |  | 
| 104 | 4 |  |  |  |  | 70 | read $fh, my $buffer, 3; | 
| 105 |  |  |  |  |  |  |  | 
| 106 | 4 |  |  |  |  | 12 | my $byte_count = 3; | 
| 107 |  |  |  |  |  |  |  | 
| 108 | 4 | 100 |  |  |  | 14 | if ($buffer eq 'ID3') { | 
| 109 |  |  |  |  |  |  |  | 
| 110 | 1 |  |  |  |  | 12 | while (read $fh, $buffer, 4096) { | 
| 111 |  |  |  |  |  |  |  | 
| 112 | 13 |  |  |  |  | 22 | my $found; | 
| 113 | 13 | 50 |  |  |  | 35 | if (($found = index($buffer, OGGHEADERFLAG)) >= 0) { | 
| 114 | 0 |  |  |  |  | 0 | $byte_count += $found; | 
| 115 | 0 |  |  |  |  | 0 | seek $fh, $byte_count, 0; | 
| 116 | 0 |  |  |  |  | 0 | last; | 
| 117 |  |  |  |  |  |  | } else { | 
| 118 | 13 |  |  |  |  | 63 | $byte_count += 4096; | 
| 119 |  |  |  |  |  |  | } | 
| 120 |  |  |  |  |  |  | } | 
| 121 |  |  |  |  |  |  |  | 
| 122 |  |  |  |  |  |  | } else { | 
| 123 | 3 |  |  |  |  | 27 | seek $fh, 0, 0; | 
| 124 |  |  |  |  |  |  | } | 
| 125 |  |  |  |  |  |  |  | 
| 126 | 4 |  |  |  |  | 14 | return tell($fh); | 
| 127 |  |  |  |  |  |  | } | 
| 128 |  |  |  |  |  |  |  | 
| 129 |  |  |  |  |  |  | sub _check_header { | 
| 130 | 4 |  |  | 4 |  | 7 | my $data = shift; | 
| 131 |  |  |  |  |  |  |  | 
| 132 | 4 |  |  |  |  | 9 | my $fh = $data->{'fileHandle'}; | 
| 133 | 4 |  |  |  |  | 8 | my $buffer; | 
| 134 |  |  |  |  |  |  | my $page_seg_count; | 
| 135 |  |  |  |  |  |  |  | 
| 136 |  |  |  |  |  |  | # stores how far into the file we've read, so later reads into the file can | 
| 137 |  |  |  |  |  |  | # skip right past all of the header stuff | 
| 138 |  |  |  |  |  |  |  | 
| 139 | 4 |  |  |  |  | 10 | my $byte_count = _skip_id3_header($fh); | 
| 140 |  |  |  |  |  |  |  | 
| 141 |  |  |  |  |  |  | # Remember the start of the Ogg data | 
| 142 | 4 |  |  |  |  | 11 | $data->{startHeader} = $byte_count; | 
| 143 |  |  |  |  |  |  |  | 
| 144 |  |  |  |  |  |  | # check that the first four bytes are 'OggS' | 
| 145 | 4 |  |  |  |  | 24 | read($fh, $buffer, 27); | 
| 146 |  |  |  |  |  |  |  | 
| 147 | 4 | 100 |  |  |  | 17 | if (substr($buffer, 0, 4) ne OGGHEADERFLAG) { | 
| 148 | 1 |  |  |  |  | 12 | warn "This is not an Ogg bitstream (no OggS header)."; | 
| 149 | 1 |  |  |  |  | 9 | return undef; | 
| 150 |  |  |  |  |  |  | } | 
| 151 |  |  |  |  |  |  |  | 
| 152 | 3 |  |  |  |  | 17 | $byte_count += 4; | 
| 153 |  |  |  |  |  |  |  | 
| 154 |  |  |  |  |  |  | # check the stream structure version (1 byte, should be 0x00) | 
| 155 | 3 | 50 |  |  |  | 23 | if (ord(substr($buffer, 4, 1)) != 0x00) { | 
| 156 | 0 |  |  |  |  | 0 | warn "This is not an Ogg bitstream (invalid structure version)."; | 
| 157 | 0 |  |  |  |  | 0 | return undef; | 
| 158 |  |  |  |  |  |  | } | 
| 159 |  |  |  |  |  |  |  | 
| 160 | 3 |  |  |  |  | 6 | $byte_count += 1; | 
| 161 |  |  |  |  |  |  |  | 
| 162 |  |  |  |  |  |  | # check the header type flag | 
| 163 |  |  |  |  |  |  | # This is a bitfield, so technically we should check all of the bits | 
| 164 |  |  |  |  |  |  | # that could potentially be set. However, the only value this should | 
| 165 |  |  |  |  |  |  | # possibly have at the beginning of a proper Ogg-Vorbis file is 0x02, | 
| 166 |  |  |  |  |  |  | # so we just check for that. If it's not that, we go on anyway, but | 
| 167 |  |  |  |  |  |  | # give a warning (this behavior may (should?) be modified in the future. | 
| 168 | 3 | 50 |  |  |  | 8 | if (ord(substr($buffer, 5, 1)) != 0x02) { | 
| 169 | 0 |  |  |  |  | 0 | warn "Invalid header type flag (trying to go ahead anyway)."; | 
| 170 |  |  |  |  |  |  | } | 
| 171 |  |  |  |  |  |  |  | 
| 172 | 3 |  |  |  |  | 6 | $byte_count += 1; | 
| 173 |  |  |  |  |  |  |  | 
| 174 |  |  |  |  |  |  | # read the number of page segments | 
| 175 | 3 |  |  |  |  | 6 | $page_seg_count = ord(substr($buffer, 26, 1)); | 
| 176 | 3 |  |  |  |  | 5 | $byte_count += 21; | 
| 177 |  |  |  |  |  |  |  | 
| 178 |  |  |  |  |  |  | # read $page_seg_count bytes, then throw 'em out | 
| 179 | 3 |  |  |  |  | 25 | seek($fh, $page_seg_count, 1); | 
| 180 | 3 |  |  |  |  | 7 | $byte_count += $page_seg_count; | 
| 181 |  |  |  |  |  |  |  | 
| 182 |  |  |  |  |  |  | # check packet type. Should be 0x01 (for indentification header) | 
| 183 | 3 |  |  |  |  | 17 | read($fh, $buffer, 7); | 
| 184 | 3 | 50 |  |  |  | 29 | if (ord(substr($buffer, 0, 1)) != 0x01) { | 
| 185 | 0 |  |  |  |  | 0 | warn "Wrong vorbis header type, giving up."; | 
| 186 | 0 |  |  |  |  | 0 | return undef; | 
| 187 |  |  |  |  |  |  | } | 
| 188 |  |  |  |  |  |  |  | 
| 189 | 3 |  |  |  |  | 7 | $byte_count += 1; | 
| 190 |  |  |  |  |  |  |  | 
| 191 |  |  |  |  |  |  | # check that the packet identifies itself as 'vorbis' | 
| 192 | 3 | 50 |  |  |  | 18 | if (substr($buffer, 1, 6) ne 'vorbis') { | 
| 193 | 0 |  |  |  |  | 0 | warn "This does not appear to be a vorbis stream, giving up."; | 
| 194 | 0 |  |  |  |  | 0 | return undef; | 
| 195 |  |  |  |  |  |  | } | 
| 196 |  |  |  |  |  |  |  | 
| 197 | 3 |  |  |  |  | 6 | $byte_count += 6; | 
| 198 |  |  |  |  |  |  |  | 
| 199 |  |  |  |  |  |  | # at this point, we assume the bitstream is valid | 
| 200 | 3 |  |  |  |  | 13 | return $byte_count; | 
| 201 |  |  |  |  |  |  | } | 
| 202 |  |  |  |  |  |  |  | 
| 203 |  |  |  |  |  |  | sub _load_info { | 
| 204 | 3 |  |  | 3 |  | 6 | my $data = shift; | 
| 205 |  |  |  |  |  |  |  | 
| 206 | 3 |  |  |  |  | 7 | my $start = $data->{'startInfoHeader'}; | 
| 207 | 3 |  |  |  |  | 4 | my $fh    = $data->{'fileHandle'}; | 
| 208 |  |  |  |  |  |  |  | 
| 209 | 3 |  |  |  |  | 8 | my $byte_count = $start + 23; | 
| 210 | 3 |  |  |  |  | 6 | my %info = (); | 
| 211 |  |  |  |  |  |  |  | 
| 212 | 3 |  |  |  |  | 25 | seek($fh, $start, 0); | 
| 213 |  |  |  |  |  |  |  | 
| 214 |  |  |  |  |  |  | # read the vorbis version | 
| 215 | 3 |  |  |  |  | 22 | read($fh, my $buffer, 23); | 
| 216 | 3 |  |  |  |  | 23 | $info{'version'} = _decode_int(substr($buffer, 0, 4, '')); | 
| 217 |  |  |  |  |  |  |  | 
| 218 |  |  |  |  |  |  | # read the number of audio channels | 
| 219 | 3 |  |  |  |  | 9 | $info{'channels'} = ord(substr($buffer, 0, 1, '')); | 
| 220 |  |  |  |  |  |  |  | 
| 221 |  |  |  |  |  |  | # read the sample rate | 
| 222 | 3 |  |  |  |  | 8 | $info{'rate'} = _decode_int(substr($buffer, 0, 4, '')); | 
| 223 |  |  |  |  |  |  |  | 
| 224 |  |  |  |  |  |  | # read the bitrate maximum | 
| 225 | 3 |  |  |  |  | 17 | $info{'bitrate_upper'} = _decode_int(substr($buffer, 0, 4, '')); | 
| 226 |  |  |  |  |  |  |  | 
| 227 |  |  |  |  |  |  | # read the bitrate nominal | 
| 228 | 3 |  |  |  |  | 9 | $info{'bitrate_nominal'} = _decode_int(substr($buffer, 0, 4, '')); | 
| 229 |  |  |  |  |  |  |  | 
| 230 |  |  |  |  |  |  | # read the bitrate minimal | 
| 231 | 3 |  |  |  |  | 9 | $info{'bitrate_lower'} = _decode_int(substr($buffer, 0, 4, '')); | 
| 232 |  |  |  |  |  |  |  | 
| 233 |  |  |  |  |  |  | # read the blocksize_0 and blocksize_1 | 
| 234 |  |  |  |  |  |  | # these are each 4 bit fields, whose actual value is 2 to the power | 
| 235 |  |  |  |  |  |  | # of the value of the field | 
| 236 | 3 |  |  |  |  | 7 | my $blocksize = substr($buffer, 0, 1, ''); | 
| 237 | 3 |  |  |  |  | 7 | $info{'blocksize_0'} = 2 << ((ord($blocksize) & 0xF0) >> 4); | 
| 238 | 3 |  |  |  |  | 10 | $info{'blocksize_1'} = 2 << (ord($blocksize) & 0x0F); | 
| 239 |  |  |  |  |  |  |  | 
| 240 |  |  |  |  |  |  | # read the framing_flag | 
| 241 | 3 |  |  |  |  | 14 | $info{'framing_flag'} = ord(substr($buffer, 0, 1, '')); | 
| 242 |  |  |  |  |  |  |  | 
| 243 |  |  |  |  |  |  | # bitrate_window is -1 in the current version of vorbisfile | 
| 244 | 3 |  |  |  |  | 5 | $info{'bitrate_window'} = -1; | 
| 245 |  |  |  |  |  |  |  | 
| 246 | 3 |  |  |  |  | 5 | $data->{'startCommentHeader'} = $byte_count; | 
| 247 |  |  |  |  |  |  |  | 
| 248 | 3 |  |  |  |  | 8 | $data->{'INFO'} = \%info; | 
| 249 |  |  |  |  |  |  | } | 
| 250 |  |  |  |  |  |  |  | 
| 251 |  |  |  |  |  |  | sub _load_comments { | 
| 252 | 3 |  |  | 3 |  | 5 | my $data = shift; | 
| 253 |  |  |  |  |  |  |  | 
| 254 | 3 |  |  |  |  | 6 | my $fh    = $data->{'fileHandle'}; | 
| 255 | 3 |  |  |  |  | 5 | my $start = $data->{'startHeader'}; | 
| 256 |  |  |  |  |  |  |  | 
| 257 | 3 |  |  |  |  | 7 | $data->{COMMENT_KEYS} = []; | 
| 258 |  |  |  |  |  |  |  | 
| 259 |  |  |  |  |  |  | # Comment parsing code based on Image::ExifTool::Vorbis | 
| 260 | 3 |  |  |  |  | 4 | my $MAX_PACKETS = 2; | 
| 261 | 3 |  |  |  |  | 4 | my $done; | 
| 262 | 3 |  |  |  |  | 9 | my ($page, $packets, $streams) = (0,0,0,0); | 
| 263 | 3 |  |  |  |  | 6 | my ($buff, $flag, $stream, %val); | 
| 264 |  |  |  |  |  |  |  | 
| 265 | 3 |  |  |  |  | 26 | seek $fh, $start, 0; | 
| 266 |  |  |  |  |  |  |  | 
| 267 | 3 |  |  |  |  | 8 | while (1) { | 
| 268 | 9 | 50 | 33 |  |  | 81 | if (!$done && read( $fh, $buff, 28 ) == 28) { | 
| 269 |  |  |  |  |  |  | # validate magic number | 
| 270 | 9 | 50 |  |  |  | 36 | unless ( $buff =~ /^OggS/ ) { | 
| 271 | 0 |  |  |  |  | 0 | warn "No comment header?"; | 
| 272 | 0 |  |  |  |  | 0 | last; | 
| 273 |  |  |  |  |  |  | } | 
| 274 |  |  |  |  |  |  |  | 
| 275 | 9 |  |  |  |  | 24 | $flag   = get8u(\$buff, 5);	# page flag | 
| 276 | 9 |  |  |  |  | 32 | $stream = get32u(\$buff, 14);	# stream serial number | 
| 277 | 9 | 100 |  |  |  | 21 | ++$streams if $flag & 0x02;	# count start-of-stream pages | 
| 278 | 9 | 50 |  |  |  | 25 | ++$packets unless $flag & 0x01; # keep track of packet count | 
| 279 |  |  |  |  |  |  | } | 
| 280 |  |  |  |  |  |  | else { | 
| 281 |  |  |  |  |  |  | # all done unless we have to process our last packet | 
| 282 | 0 | 0 |  |  |  | 0 | last unless %val; | 
| 283 | 0 |  |  |  |  | 0 | ($stream) = sort keys %val;     # take a stream | 
| 284 | 0 |  |  |  |  | 0 | $flag = 0;                      # no continuation | 
| 285 | 0 |  |  |  |  | 0 | $done = 1;                      # flag for done reading | 
| 286 |  |  |  |  |  |  | } | 
| 287 |  |  |  |  |  |  |  | 
| 288 |  |  |  |  |  |  | # can finally process previous packet from this stream | 
| 289 |  |  |  |  |  |  | # unless this is a continuation page | 
| 290 | 9 | 100 | 66 |  |  | 29 | if (defined $val{$stream} and not $flag & 0x01) { | 
| 291 | 3 |  |  |  |  | 12 | _process_comments( $data, \$val{$stream} ); | 
| 292 | 3 |  |  |  |  | 7 | delete $val{$stream}; | 
| 293 |  |  |  |  |  |  | # only read the first $MAX_PACKETS packets from each stream | 
| 294 | 3 | 50 |  |  |  | 9 | if ($packets > $MAX_PACKETS * $streams) { | 
| 295 |  |  |  |  |  |  | # all done (success!) | 
| 296 | 3 | 50 |  |  |  | 21 | last unless %val; | 
| 297 |  |  |  |  |  |  | # process remaining stream(s) | 
| 298 | 0 |  |  |  |  | 0 | next; | 
| 299 |  |  |  |  |  |  | } | 
| 300 |  |  |  |  |  |  | } | 
| 301 |  |  |  |  |  |  |  | 
| 302 |  |  |  |  |  |  | # stop processing Ogg Vorbis if we have scanned enough packets | 
| 303 | 6 | 50 | 33 |  |  | 15 | last if $packets > $MAX_PACKETS * $streams and not %val; | 
| 304 |  |  |  |  |  |  |  | 
| 305 |  |  |  |  |  |  | # continue processing the current page | 
| 306 |  |  |  |  |  |  | # page sequence number | 
| 307 | 6 |  |  |  |  | 12 | my $page_num = get32u(\$buff, 18); | 
| 308 |  |  |  |  |  |  |  | 
| 309 |  |  |  |  |  |  | # number of segments | 
| 310 | 6 |  |  |  |  | 14 | my $nseg    = get8u(\$buff, 26); | 
| 311 |  |  |  |  |  |  |  | 
| 312 |  |  |  |  |  |  | # calculate total data length | 
| 313 | 6 |  |  |  |  | 11 | my $data_len = get8u(\$buff, 27); | 
| 314 |  |  |  |  |  |  |  | 
| 315 | 6 | 50 |  |  |  | 14 | if ($nseg) { | 
| 316 | 6 | 50 |  |  |  | 24 | read( $fh, $buff, $nseg-1 ) == $nseg-1 or last; | 
| 317 | 6 |  |  |  |  | 21 | my @segs = unpack('C*', $buff); | 
| 318 |  |  |  |  |  |  | # could check that all these (but the last) are 255... | 
| 319 | 6 |  |  |  |  | 26 | foreach (@segs) { $data_len += $_ } | 
|  | 48 |  |  |  |  | 58 |  | 
| 320 |  |  |  |  |  |  | } | 
| 321 |  |  |  |  |  |  |  | 
| 322 | 6 | 50 |  |  |  | 15 | if (defined $page) { | 
| 323 | 6 | 50 |  |  |  | 18 | if ($page == $page_num) { | 
| 324 | 6 |  |  |  |  | 9 | ++$page; | 
| 325 |  |  |  |  |  |  | } else { | 
| 326 | 0 |  |  |  |  | 0 | warn "Missing page(s) in Ogg file\n"; | 
| 327 | 0 |  |  |  |  | 0 | undef $page; | 
| 328 |  |  |  |  |  |  | } | 
| 329 |  |  |  |  |  |  | } | 
| 330 |  |  |  |  |  |  |  | 
| 331 |  |  |  |  |  |  | # read page data | 
| 332 | 6 | 50 |  |  |  | 33 | read($fh, $buff, $data_len) == $data_len or last; | 
| 333 |  |  |  |  |  |  |  | 
| 334 | 6 | 50 |  |  |  | 20 | if (defined $val{$stream}) { | 
|  |  | 50 |  |  |  |  |  | 
| 335 |  |  |  |  |  |  | # add this continuation page | 
| 336 | 0 |  |  |  |  | 0 | $val{$stream} .= $buff; | 
| 337 |  |  |  |  |  |  | } elsif (not $flag & 0x01) { | 
| 338 |  |  |  |  |  |  | # ignore remaining pages of a continued packet | 
| 339 |  |  |  |  |  |  | # ignore the first page of any packet we aren't parsing | 
| 340 | 6 | 100 | 66 |  |  | 47 | if ($buff =~ /^(.)vorbis/s and ord($1) == 3) { | 
| 341 |  |  |  |  |  |  | # save this page, it has comments | 
| 342 | 3 |  |  |  |  | 9 | $val{$stream} = $buff; | 
| 343 |  |  |  |  |  |  | } | 
| 344 |  |  |  |  |  |  | } | 
| 345 |  |  |  |  |  |  |  | 
| 346 | 6 | 50 | 66 |  |  | 22 | if (defined $val{$stream} and $flag & 0x04) { | 
| 347 |  |  |  |  |  |  | # process Ogg Vorbis packet now if end-of-stream bit is set | 
| 348 | 0 |  |  |  |  | 0 | _process_comments($data, \$val{$stream}); | 
| 349 | 0 |  |  |  |  | 0 | delete $val{$stream}; | 
| 350 |  |  |  |  |  |  | } | 
| 351 |  |  |  |  |  |  | } | 
| 352 |  |  |  |  |  |  |  | 
| 353 | 3 |  |  |  |  | 12 | $data->{'INFO'}{offset} = tell $fh; | 
| 354 |  |  |  |  |  |  | } | 
| 355 |  |  |  |  |  |  |  | 
| 356 |  |  |  |  |  |  | sub _process_comments { | 
| 357 | 3 |  |  | 3 |  | 7 | my ( $data, $data_pt ) = @_; | 
| 358 |  |  |  |  |  |  |  | 
| 359 | 3 |  |  |  |  | 12 | my $pos = 7; | 
| 360 | 3 |  |  |  |  | 6 | my $end = length $$data_pt; | 
| 361 |  |  |  |  |  |  |  | 
| 362 | 3 |  |  |  |  | 5 | my $num; | 
| 363 |  |  |  |  |  |  | my %comments; | 
| 364 |  |  |  |  |  |  |  | 
| 365 | 3 |  |  |  |  | 5 | while (1) { | 
| 366 | 9 | 50 |  |  |  | 16 | last if $pos + 4 > $end; | 
| 367 | 9 |  |  |  |  | 15 | my $len = get32u($data_pt, $pos); | 
| 368 | 9 | 50 |  |  |  | 24 | last if $pos + 4 + $len > $end; | 
| 369 | 9 |  |  |  |  | 13 | my $start = $pos + 4; | 
| 370 | 9 |  |  |  |  | 16 | my $buff = substr($$data_pt, $start, $len); | 
| 371 | 9 |  |  |  |  | 15 | $pos = $start + $len; | 
| 372 | 9 |  |  |  |  | 12 | my ($tag, $val); | 
| 373 | 9 | 100 |  |  |  | 14 | if (defined $num) { | 
| 374 | 6 | 50 |  |  |  | 26 | $buff =~ /(.*?)=(.*)/s or last; | 
| 375 | 6 |  |  |  |  | 18 | ($tag, $val) = ($1, $2); | 
| 376 |  |  |  |  |  |  | } else { | 
| 377 | 3 |  |  |  |  | 5 | $tag = 'vendor'; | 
| 378 | 3 |  |  |  |  | 6 | $val = $buff; | 
| 379 | 3 | 50 |  |  |  | 15 | $num = ($pos + 4 < $end) ? get32u($data_pt, $pos) : 0; | 
| 380 | 3 |  |  |  |  | 6 | $pos += 4; | 
| 381 |  |  |  |  |  |  | } | 
| 382 |  |  |  |  |  |  |  | 
| 383 | 9 |  |  |  |  | 15 | my $lctag = lc $tag; | 
| 384 |  |  |  |  |  |  |  | 
| 385 | 9 |  |  |  |  | 13 | push @{$comments{$lctag}}, $val; | 
|  | 9 |  |  |  |  | 22 |  | 
| 386 | 9 |  |  |  |  | 13 | push @{$data->{COMMENT_KEYS}}, $lctag; | 
|  | 9 |  |  |  |  | 16 |  | 
| 387 |  |  |  |  |  |  |  | 
| 388 |  |  |  |  |  |  | # all done if this was our last tag | 
| 389 | 9 | 100 |  |  |  | 21 | if ( !$num-- ) { | 
| 390 | 3 |  |  |  |  | 7 | $data->{COMMENTS} = \%comments; | 
| 391 | 3 |  |  |  |  | 8 | return 1; | 
| 392 |  |  |  |  |  |  | } | 
| 393 |  |  |  |  |  |  | } | 
| 394 |  |  |  |  |  |  |  | 
| 395 | 0 |  |  |  |  | 0 | warn "format error in Vorbis comments\n"; | 
| 396 |  |  |  |  |  |  |  | 
| 397 | 0 |  |  |  |  | 0 | return 0; | 
| 398 |  |  |  |  |  |  | } | 
| 399 |  |  |  |  |  |  |  | 
| 400 |  |  |  |  |  |  | sub get8u { | 
| 401 | 21 |  |  | 21 | 0 | 33 | return unpack( "x$_[1] C", ${$_[0]} ); | 
|  | 21 |  |  |  |  | 64 |  | 
| 402 |  |  |  |  |  |  | } | 
| 403 |  |  |  |  |  |  |  | 
| 404 |  |  |  |  |  |  | sub get32u { | 
| 405 | 27 |  |  | 27 | 0 | 46 | return unpack( "x$_[1] V", ${$_[0]} ); | 
|  | 27 |  |  |  |  | 52 |  | 
| 406 |  |  |  |  |  |  | } | 
| 407 |  |  |  |  |  |  |  | 
| 408 |  |  |  |  |  |  | sub _calculate_track_length { | 
| 409 | 3 |  |  | 3 |  | 5 | my $data = shift; | 
| 410 |  |  |  |  |  |  |  | 
| 411 | 3 |  |  |  |  | 7 | my $fh = $data->{'fileHandle'}; | 
| 412 |  |  |  |  |  |  |  | 
| 413 |  |  |  |  |  |  | # The original author was doing something pretty lame, and was walking the | 
| 414 |  |  |  |  |  |  | # entire file to find the last granule_position. Instead, let's seek to | 
| 415 |  |  |  |  |  |  | # the end of the file - blocksize_0, and read from there. | 
| 416 | 3 |  |  |  |  | 4 | my $len = 0; | 
| 417 |  |  |  |  |  |  |  | 
| 418 |  |  |  |  |  |  | # Bug 1155 - Seek further back to get the granule_position. | 
| 419 |  |  |  |  |  |  | # However, for short tracks, don't seek that far back. | 
| 420 | 3 | 50 |  |  |  | 24 | if (($data->{'filesize'} - $data->{'INFO'}{'offset'}) > ($data->{'INFO'}{'blocksize_0'} * 2)) { | 
|  |  | 50 |  |  |  |  |  | 
| 421 |  |  |  |  |  |  |  | 
| 422 | 0 |  |  |  |  | 0 | $len = $data->{'INFO'}{'blocksize_0'} * 2; | 
| 423 |  |  |  |  |  |  | } elsif ($data->{'filesize'} < $data->{'INFO'}{'blocksize_0'}) { | 
| 424 | 3 |  |  |  |  | 6 | $len = $data->{'filesize'}; | 
| 425 |  |  |  |  |  |  | } else { | 
| 426 | 0 |  |  |  |  | 0 | $len = $data->{'INFO'}{'blocksize_0'}; | 
| 427 |  |  |  |  |  |  | } | 
| 428 |  |  |  |  |  |  |  | 
| 429 | 3 | 50 |  |  |  | 17 | if ($data->{'INFO'}{'blocksize_0'} == 0) { | 
| 430 | 0 |  |  |  |  | 0 | print "Ogg::Vorbis::Header::PurePerl:\n"; | 
| 431 | 0 |  |  |  |  | 0 | warn "blocksize_0 is 0! Should be a power of 2! http://www.xiph.org/ogg/vorbis/doc/vorbis-spec-ref.html\n"; | 
| 432 | 0 |  |  |  |  | 0 | return; | 
| 433 |  |  |  |  |  |  | } | 
| 434 |  |  |  |  |  |  |  | 
| 435 | 3 |  |  |  |  | 30 | seek($fh, -$len, 2); | 
| 436 |  |  |  |  |  |  |  | 
| 437 | 3 |  |  |  |  | 7 | my $buf = ''; | 
| 438 | 3 |  |  |  |  | 5 | my $found_header = 0; | 
| 439 | 3 |  |  |  |  | 4 | my $block = $len; | 
| 440 |  |  |  |  |  |  |  | 
| 441 |  |  |  |  |  |  | SEEK: | 
| 442 | 3 |  | 33 |  |  | 50 | while ($found_header == 0 && read($fh, $buf, $len)) { | 
| 443 |  |  |  |  |  |  | # search the last read $block bytes for Ogg header flag | 
| 444 |  |  |  |  |  |  | # the search is conducted backwards so that the last flag | 
| 445 |  |  |  |  |  |  | # is found first | 
| 446 | 3 |  |  |  |  | 41 | for (my $i = $block; $i >= 0; $i--) { | 
| 447 | 90 | 100 |  |  |  | 199 | if (substr($buf, $i, 4) eq OGGHEADERFLAG) { | 
| 448 | 3 |  |  |  |  | 6 | substr($buf, 0, ($i+4), ''); | 
| 449 | 3 |  |  |  |  | 6 | $found_header = 1; | 
| 450 | 3 |  |  |  |  | 6 | last SEEK; | 
| 451 |  |  |  |  |  |  | } | 
| 452 |  |  |  |  |  |  | } | 
| 453 |  |  |  |  |  |  |  | 
| 454 |  |  |  |  |  |  | # already read the whole file? | 
| 455 | 0 | 0 |  |  |  | 0 | last if $len == $data->{'filesize'}; | 
| 456 |  |  |  |  |  |  |  | 
| 457 | 0 |  |  |  |  | 0 | $len += $block; | 
| 458 | 0 | 0 |  |  |  | 0 | $len = $data->{'filesize'} if $len > $data->{'filesize'}; | 
| 459 |  |  |  |  |  |  |  | 
| 460 | 0 |  |  |  |  | 0 | seek($fh, -$len, 2); | 
| 461 |  |  |  |  |  |  | } | 
| 462 |  |  |  |  |  |  |  | 
| 463 | 3 | 50 |  |  |  | 8 | unless ($found_header) { | 
| 464 | 0 |  |  |  |  | 0 | warn "Ogg::Vorbis::Header::PurePerl: Didn't find an ogg header - invalid file?\n"; | 
| 465 | 0 |  |  |  |  | 0 | return; | 
| 466 |  |  |  |  |  |  | } | 
| 467 |  |  |  |  |  |  |  | 
| 468 |  |  |  |  |  |  | # stream structure version - must be 0x00 | 
| 469 | 3 | 50 |  |  |  | 9 | if (ord(substr($buf, 0, 1, '')) != 0x00) { | 
| 470 | 0 |  |  |  |  | 0 | warn "Ogg::Vorbis::Header::PurePerl: Invalid stream structure version: " . sprintf("%x", ord($buf)); | 
| 471 | 0 |  |  |  |  | 0 | return; | 
| 472 |  |  |  |  |  |  | } | 
| 473 |  |  |  |  |  |  |  | 
| 474 |  |  |  |  |  |  | # absolute granule position - this is what we need! | 
| 475 | 3 |  |  |  |  | 8 | substr($buf, 0, 1, ''); | 
| 476 |  |  |  |  |  |  |  | 
| 477 | 3 |  |  |  |  | 11 | my $granule_position = _decode_int(substr($buf, 0, 8, '')); | 
| 478 |  |  |  |  |  |  |  | 
| 479 | 3 | 50 | 33 |  |  | 11 | if ($granule_position && $data->{'INFO'}{'rate'}) { | 
| 480 | 0 |  |  |  |  | 0 | $data->{'INFO'}{'length'}          = int($granule_position / $data->{'INFO'}{'rate'}); | 
| 481 | 0 | 0 |  |  |  | 0 | if ($data->{'INFO'}{'length'}) { | 
| 482 | 0 |  |  |  |  | 0 | $data->{'INFO'}{'bitrate_average'} = sprintf( "%d", ( $data->{'filesize'} * 8 ) / $data->{'INFO'}{'length'} ); | 
| 483 |  |  |  |  |  |  | } else { | 
| 484 | 0 |  |  |  |  | 0 | $data->{'INFO'}{'bitrate_average'} = 0; | 
| 485 |  |  |  |  |  |  | } | 
| 486 |  |  |  |  |  |  | } else { | 
| 487 | 3 |  |  |  |  | 25 | $data->{'INFO'}{'length'} = 0; | 
| 488 |  |  |  |  |  |  | } | 
| 489 |  |  |  |  |  |  | } | 
| 490 |  |  |  |  |  |  |  | 
| 491 |  |  |  |  |  |  | sub _decode_int { | 
| 492 | 18 |  |  | 18 |  | 24 | my $bytes = shift; | 
| 493 |  |  |  |  |  |  |  | 
| 494 | 18 |  |  |  |  | 27 | my $num_bytes = length($bytes); | 
| 495 | 18 |  |  |  |  | 20 | my $num = 0; | 
| 496 | 18 |  |  |  |  | 21 | my $mult = 1; | 
| 497 |  |  |  |  |  |  |  | 
| 498 | 18 |  |  |  |  | 35 | for (my $i = 0; $i < $num_bytes; $i ++) { | 
| 499 |  |  |  |  |  |  |  | 
| 500 | 84 |  |  |  |  | 108 | $num += ord(substr($bytes, 0, 1, '')) * $mult; | 
| 501 | 84 |  |  |  |  | 126 | $mult *= 256; | 
| 502 |  |  |  |  |  |  | } | 
| 503 |  |  |  |  |  |  |  | 
| 504 | 18 |  |  |  |  | 37 | return $num; | 
| 505 |  |  |  |  |  |  | } | 
| 506 |  |  |  |  |  |  |  | 
| 507 |  |  |  |  |  |  | 1; | 
| 508 |  |  |  |  |  |  |  | 
| 509 |  |  |  |  |  |  | __END__ |