Daniel Stenberg | 4fb816d | 2005-06-07 12:22:52 +0000 | [diff] [blame] | 1 | ############################################################################# |
| 2 | # This is |
| 3 | # http://search.cpan.org/~amolloy/Ogg-Vorbis-Header-PurePerl-0.07/PurePerl.pm |
| 4 | # written by Andrew Molloy |
| 5 | # Code under GNU GENERAL PUBLIC LICENCE v2 |
| 6 | # $Id$ |
| 7 | ############################################################################# |
| 8 | |
| 9 | package vorbiscomm; |
| 10 | |
| 11 | use 5.005; |
| 12 | use strict; |
| 13 | use warnings; |
| 14 | |
| 15 | use Fcntl qw/SEEK_END/; |
| 16 | |
| 17 | our $VERSION = '0.07'; |
| 18 | |
| 19 | sub new |
| 20 | { |
| 21 | my $class = shift; |
| 22 | my $file = shift; |
| 23 | |
| 24 | return load($class, $file); |
| 25 | } |
| 26 | |
| 27 | sub load |
| 28 | { |
| 29 | my $class = shift; |
| 30 | my $file = shift; |
| 31 | my $from_new = shift; |
| 32 | my %data; |
| 33 | my $self; |
| 34 | |
| 35 | # there must be a better way... |
| 36 | if ($class eq 'vorbiscomm') |
| 37 | { |
| 38 | $self = bless \%data, $class; |
| 39 | } |
| 40 | else |
| 41 | { |
| 42 | $self = $class; |
| 43 | } |
| 44 | |
| 45 | if ($self->{'FILE_LOADED'}) |
| 46 | { |
| 47 | return $self; |
| 48 | } |
| 49 | |
| 50 | $self->{'FILE_LOADED'} = 1; |
| 51 | |
| 52 | # check that the file exists and is readable |
| 53 | unless ( -e $file && -r _ ) |
| 54 | { |
| 55 | warn "File does not exist or cannot be read."; |
| 56 | # file does not exist, can't do anything |
| 57 | return undef; |
| 58 | } |
| 59 | # open up the file |
| 60 | open FILE, $file; |
| 61 | # make sure dos-type systems can handle it... |
| 62 | binmode FILE; |
| 63 | |
| 64 | $data{'filename'} = $file; |
| 65 | $data{'fileHandle'} = \*FILE; |
| 66 | |
| 67 | _init(\%data); |
| 68 | _loadInfo(\%data); |
| 69 | _loadComments(\%data); |
| 70 | _calculateTrackLength(\%data); |
| 71 | |
| 72 | close FILE; |
| 73 | |
| 74 | return $self; |
| 75 | } |
| 76 | |
| 77 | sub info |
| 78 | { |
| 79 | my $self = shift; |
| 80 | my $key = shift; |
| 81 | |
| 82 | # if the user did not supply a key, return the entire hash |
| 83 | unless ($key) |
| 84 | { |
| 85 | return $self->{'INFO'}; |
| 86 | } |
| 87 | |
| 88 | # otherwise, return the value for the given key |
| 89 | return $self->{'INFO'}{lc $key}; |
| 90 | } |
| 91 | |
| 92 | sub comment_tags |
| 93 | { |
| 94 | my $self = shift; |
| 95 | |
| 96 | return @{$self->{'COMMENT_KEYS'}}; |
| 97 | } |
| 98 | |
| 99 | sub comment |
| 100 | { |
| 101 | my $self = shift; |
| 102 | my $key = shift; |
| 103 | |
| 104 | # if the user supplied key does not exist, return undef |
| 105 | unless($self->{'COMMENTS'}{lc $key}) |
| 106 | { |
| 107 | return undef; |
| 108 | } |
| 109 | |
| 110 | return @{$self->{'COMMENTS'}{lc $key}}; |
| 111 | } |
| 112 | |
| 113 | sub add_comments |
| 114 | { |
| 115 | warn "Ogg::Vorbis::Header::PurePerl add_comments() unimplemented."; |
| 116 | } |
| 117 | |
| 118 | sub edit_comment |
| 119 | { |
| 120 | warn "Ogg::Vorbis::Header::PurePerl edit_comment() unimplemented."; |
| 121 | } |
| 122 | |
| 123 | sub delete_comment |
| 124 | { |
| 125 | warn "Ogg::Vorbis::Header::PurePerl delete_comment() unimplemented."; |
| 126 | } |
| 127 | |
| 128 | sub clear_comments |
| 129 | { |
| 130 | warn "Ogg::Vorbis::Header::PurePerl clear_comments() unimplemented."; |
| 131 | } |
| 132 | |
| 133 | sub path |
| 134 | { |
| 135 | my $self = shift; |
| 136 | |
| 137 | return $self->{'fileName'}; |
| 138 | } |
| 139 | |
| 140 | sub write_vorbis |
| 141 | { |
| 142 | warn "Ogg::Vorbis::Header::PurePerl write_vorbis unimplemented."; |
| 143 | } |
| 144 | |
| 145 | # "private" methods |
| 146 | |
| 147 | sub _init |
| 148 | { |
| 149 | my $data = shift; |
| 150 | my $fh = $data->{'fileHandle'}; |
| 151 | my $byteCount = 0; |
| 152 | |
| 153 | # check the header to make sure this is actually an Ogg-Vorbis file |
| 154 | $byteCount = _checkHeader($data); |
| 155 | |
| 156 | unless($byteCount) |
| 157 | { |
| 158 | # if it's not, we can't do anything |
| 159 | return undef; |
| 160 | } |
| 161 | |
| 162 | $data->{'startInfoHeader'} = $byteCount; |
| 163 | } |
| 164 | |
| 165 | sub _checkHeader |
| 166 | { |
| 167 | my $data = shift; |
| 168 | my $fh = $data->{'fileHandle'}; |
| 169 | my $buffer; |
| 170 | my $pageSegCount; |
| 171 | my $byteCount = 0; # stores how far into the file we've read, |
| 172 | # so later reads into the file can skip right |
| 173 | # past all of the header stuff |
| 174 | |
| 175 | # check that the first four bytes are 'OggS' |
| 176 | read($fh, $buffer, 4); |
| 177 | if ($buffer ne 'OggS') |
| 178 | { |
| 179 | warn "This is not an Ogg bitstream (no OggS header)."; |
| 180 | return undef; |
| 181 | } |
| 182 | $byteCount += 4; |
| 183 | |
| 184 | # check the stream structure version (1 byte, should be 0x00) |
| 185 | read($fh, $buffer, 1); |
| 186 | if (ord($buffer) != 0x00) |
| 187 | { |
| 188 | warn "This is not an Ogg bitstream (invalid structure version)."; |
| 189 | return undef; |
| 190 | } |
| 191 | $byteCount += 1; |
| 192 | |
| 193 | # check the header type flag |
| 194 | # This is a bitfield, so technically we should check all of the bits |
| 195 | # that could potentially be set. However, the only value this should |
| 196 | # possibly have at the beginning of a proper Ogg-Vorbis file is 0x02, |
| 197 | # so we just check for that. If it's not that, we go on anyway, but |
| 198 | # give a warning (this behavior may (should?) be modified in the future. |
| 199 | read($fh, $buffer, 1); |
| 200 | if (ord($buffer) != 0x02) |
| 201 | { |
| 202 | warn "Invalid header type flag (trying to go ahead anyway)."; |
| 203 | } |
| 204 | $byteCount += 1; |
| 205 | |
| 206 | # skip to the page_segments count |
| 207 | read($fh, $buffer, 20); |
| 208 | $byteCount += 20; |
| 209 | # we do nothing with this data |
| 210 | |
| 211 | # read the number of page segments |
| 212 | read($fh, $buffer, 1); |
| 213 | $pageSegCount = ord($buffer); |
| 214 | $byteCount += 1; |
| 215 | |
| 216 | # read $pageSegCount bytes, then throw 'em out |
| 217 | read($fh, $buffer, $pageSegCount); |
| 218 | $byteCount += $pageSegCount; |
| 219 | |
| 220 | # check packet type. Should be 0x01 (for indentification header) |
| 221 | read($fh, $buffer, 1); |
| 222 | if (ord($buffer) != 0x01) |
| 223 | { |
| 224 | warn "Wrong vorbis header type, giving up."; |
| 225 | return undef; |
| 226 | } |
| 227 | $byteCount += 1; |
| 228 | |
| 229 | # check that the packet identifies itself as 'vorbis' |
| 230 | read($fh, $buffer, 6); |
| 231 | if ($buffer ne 'vorbis') |
| 232 | { |
| 233 | warn "This does not appear to be a vorbis stream, giving up."; |
| 234 | return undef; |
| 235 | } |
| 236 | $byteCount += 6; |
| 237 | |
| 238 | # at this point, we assume the bitstream is valid |
| 239 | return $byteCount; |
| 240 | } |
| 241 | |
| 242 | sub _loadInfo |
| 243 | { |
| 244 | my $data = shift; |
| 245 | my $start = $data->{'startInfoHeader'}; |
| 246 | my $fh = $data->{'fileHandle'}; |
| 247 | my $buffer; |
| 248 | my $byteCount = $start; |
| 249 | my %info; |
| 250 | |
| 251 | seek $fh, $start, 0; |
| 252 | |
| 253 | # read the vorbis version |
| 254 | read($fh, $buffer, 4); |
| 255 | $info{'version'} = _decodeInt($buffer); |
| 256 | $byteCount += 4; |
| 257 | |
| 258 | # read the number of audio channels |
| 259 | read($fh, $buffer, 1); |
| 260 | $info{'channels'} = ord($buffer); |
| 261 | $byteCount += 1; |
| 262 | |
| 263 | # read the sample rate |
| 264 | read($fh, $buffer, 4); |
| 265 | $info{'rate'} = _decodeInt($buffer); |
| 266 | $byteCount += 4; |
| 267 | |
| 268 | # read the bitrate maximum |
| 269 | read($fh, $buffer, 4); |
| 270 | $info{'bitrate_upper'} = _decodeInt($buffer); |
| 271 | $byteCount += 4; |
| 272 | |
| 273 | # read the bitrate nominal |
| 274 | read($fh, $buffer, 4); |
| 275 | $info{'bitrate_nominal'} = _decodeInt($buffer); |
| 276 | $byteCount += 4; |
| 277 | |
| 278 | # read the bitrate minimal |
| 279 | read($fh, $buffer, 4); |
| 280 | $info{'bitrate_lower'} = _decodeInt($buffer); |
| 281 | $byteCount += 4; |
| 282 | |
| 283 | # read the blocksize_0 and blocksize_1 |
| 284 | read($fh, $buffer, 1); |
| 285 | # these are each 4 bit fields, whose actual value is 2 to the power |
| 286 | # of the value of the field |
| 287 | $info{'blocksize_0'} = 2 << ((ord($buffer) & 0xF0) >> 4); |
| 288 | $info{'blocksize_1'} = 2 << (ord($buffer) & 0x0F); |
| 289 | $byteCount += 1; |
| 290 | |
| 291 | # read the framing_flag |
| 292 | read($fh, $buffer, 1); |
| 293 | $info{'framing_flag'} = ord($buffer); |
| 294 | $byteCount += 1; |
| 295 | |
| 296 | # bitrate_window is -1 in the current version of vorbisfile |
| 297 | $info{'bitrate_window'} = -1; |
| 298 | |
| 299 | $data->{'startCommentHeader'} = $byteCount; |
| 300 | |
| 301 | $data->{'INFO'} = \%info; |
| 302 | } |
| 303 | |
| 304 | sub _loadComments |
| 305 | { |
| 306 | my $data = shift; |
| 307 | my $fh = $data->{'fileHandle'}; |
| 308 | my $start = $data->{'startCommentHeader'}; |
| 309 | my $buffer; |
| 310 | my $page_segments; |
| 311 | my $vendor_length; |
| 312 | my $user_comment_count; |
| 313 | my $byteCount = $start; |
| 314 | my %comments; |
| 315 | |
| 316 | seek $fh, $start, 0; |
| 317 | |
| 318 | # check that the first four bytes are 'OggS' |
| 319 | read($fh, $buffer, 4); |
| 320 | if ($buffer ne 'OggS') |
| 321 | { |
| 322 | warn "No comment header?"; |
| 323 | return undef; |
| 324 | } |
| 325 | $byteCount += 4; |
| 326 | |
| 327 | # skip over next ten bytes |
| 328 | read($fh, $buffer, 10); |
| 329 | $byteCount += 10; |
| 330 | |
| 331 | # read the stream serial number |
| 332 | read($fh, $buffer, 4); |
| 333 | push @{$data->{'commentSerialNumber'}}, _decodeInt($buffer); |
| 334 | $byteCount += 4; |
| 335 | |
| 336 | # read the page sequence number (should be 0x01) |
| 337 | read($fh, $buffer, 4); |
| 338 | if (_decodeInt($buffer) != 0x01) |
| 339 | { |
| 340 | warn "Comment header page sequence number is not 0x01: " + |
| 341 | _decodeInt($buffer); |
| 342 | warn "Going to keep going anyway."; |
| 343 | } |
| 344 | $byteCount += 4; |
| 345 | |
| 346 | # and ignore the page checksum for now |
| 347 | read($fh, $buffer, 4); |
| 348 | $byteCount += 4; |
| 349 | |
| 350 | # get the number of entries in the segment_table... |
| 351 | read($fh, $buffer, 1); |
| 352 | $page_segments = _decodeInt($buffer); |
| 353 | $byteCount += 1; |
| 354 | # then skip on past it |
| 355 | read($fh, $buffer, $page_segments); |
| 356 | $byteCount += $page_segments; |
| 357 | |
| 358 | # check the header type (should be 0x03) |
| 359 | read($fh, $buffer, 1); |
| 360 | if (ord($buffer) != 0x03) |
| 361 | { |
| 362 | warn "Wrong header type: " . ord($buffer); |
| 363 | } |
| 364 | $byteCount += 1; |
| 365 | |
| 366 | # now we should see 'vorbis' |
| 367 | read($fh, $buffer, 6); |
| 368 | if ($buffer ne 'vorbis') |
| 369 | { |
| 370 | warn "Missing comment header. Should have found 'vorbis', found " . |
| 371 | $buffer; |
| 372 | } |
| 373 | $byteCount += 6; |
| 374 | |
| 375 | # get the vendor length |
| 376 | read($fh, $buffer, 4); |
| 377 | $vendor_length = _decodeInt($buffer); |
| 378 | $byteCount += 4; |
| 379 | |
| 380 | # read in the vendor |
| 381 | read($fh, $buffer, $vendor_length); |
| 382 | $comments{'vendor'} = $buffer; |
| 383 | $byteCount += $vendor_length; |
| 384 | |
| 385 | # read in the number of user comments |
| 386 | read($fh, $buffer, 4); |
| 387 | $user_comment_count = _decodeInt($buffer); |
| 388 | $byteCount += 4; |
| 389 | |
| 390 | $data->{'COMMENT_KEYS'} = []; |
| 391 | |
| 392 | # finally, read the comments |
| 393 | for (my $i = 0; $i < $user_comment_count; $i++) |
| 394 | { |
| 395 | # first read the length |
| 396 | read($fh, $buffer, 4); |
| 397 | my $comment_length = _decodeInt($buffer); |
| 398 | $byteCount += 4; |
| 399 | |
| 400 | # then the comment itself |
| 401 | read($fh, $buffer, $comment_length); |
| 402 | $byteCount += $comment_length; |
| 403 | |
| 404 | my ($key) = $buffer =~ /^([^=]+)/; |
| 405 | my ($value) = $buffer =~ /=(.*)$/; |
| 406 | |
| 407 | push @{$comments{lc $key}}, $value; |
| 408 | push @{$data->{'COMMENT_KEYS'}}, lc $key; |
| 409 | } |
| 410 | |
| 411 | # read past the framing_bit |
| 412 | read($fh, $buffer, 1); |
| 413 | $byteCount += 1; |
| 414 | |
| 415 | $data->{'INFO'}{'offset'} = $byteCount; |
| 416 | |
| 417 | $data->{'COMMENTS'} = \%comments; |
| 418 | } |
| 419 | |
| 420 | sub _calculateTrackLength |
| 421 | { |
| 422 | my $data = shift; |
| 423 | my $fh = $data->{'fileHandle'}; |
| 424 | my $buffer; |
| 425 | my $pageSize; |
| 426 | my $granule_position; |
| 427 | |
| 428 | seek($fh,-8500,SEEK_END); # that magic number is from vorbisfile.c |
| 429 | # in the constant CHUNKSIZE, which comes |
| 430 | # with the comment /* a shade over 8k; |
| 431 | # anyone using pages well over 8k gets |
| 432 | # what they deserve */ |
| 433 | |
| 434 | # we just keep looking through the headers until we get to the last one |
| 435 | # (there might be a couple of blocks here) |
| 436 | while(_findPage($fh)) |
| 437 | { |
| 438 | # stream structure version - must be 0x00 |
| 439 | read($fh, $buffer, 1); |
| 440 | if (ord($buffer) != 0x00) |
| 441 | { |
| 442 | warn "Invalid stream structure version: " . |
| 443 | sprintf("%x", ord($buffer)); |
| 444 | return; |
| 445 | } |
| 446 | |
| 447 | # header type flag |
| 448 | read($fh, $buffer, 1); |
| 449 | # we should check this, but for now we'll just ignore it |
| 450 | |
| 451 | # absolute granule position - this is what we need! |
| 452 | read($fh, $buffer, 8); |
| 453 | $granule_position = _decodeInt($buffer); |
| 454 | |
| 455 | # skip past stream_serial_number, page_sequence_number, and crc |
| 456 | read($fh, $buffer, 12); |
| 457 | |
| 458 | # page_segments |
| 459 | read($fh, $buffer, 1); |
| 460 | my $page_segments = ord($buffer); |
| 461 | |
| 462 | # reset pageSize |
| 463 | $pageSize = 0; |
| 464 | |
| 465 | # calculate approx. page size |
| 466 | for (my $i = 0; $i < $page_segments; $i++) |
| 467 | { |
| 468 | read($fh, $buffer, 1); |
| 469 | $pageSize += ord($buffer); |
| 470 | } |
| 471 | |
| 472 | seek $fh, $pageSize, 1; |
| 473 | } |
| 474 | |
| 475 | $data->{'INFO'}{'length'} = |
| 476 | int($granule_position / $data->{'INFO'}{'rate'}); |
| 477 | } |
| 478 | |
| 479 | sub _findPage |
| 480 | { |
| 481 | # search forward in the file for the 'OggS' page header |
| 482 | my $fh = shift; |
| 483 | my $char; |
| 484 | my $curStr = ''; |
| 485 | |
| 486 | while (read($fh, $char, 1)) |
| 487 | { |
| 488 | $curStr = $char . $curStr; |
| 489 | $curStr = substr($curStr, 0, 4); |
| 490 | |
| 491 | # we are actually looking for the string 'SggO' because we |
| 492 | # tack character on to our test string backwards, to make |
| 493 | # trimming it to 4 characters easier. |
| 494 | if ($curStr eq 'SggO') |
| 495 | { |
| 496 | return 1; |
| 497 | } |
| 498 | } |
| 499 | |
| 500 | return undef; |
| 501 | } |
| 502 | |
| 503 | sub _decodeInt |
| 504 | { |
| 505 | my $bytes = shift; |
| 506 | my $num = 0; |
| 507 | my @byteList = split //, $bytes; |
| 508 | my $numBytes = @byteList; |
| 509 | my $mult = 1; |
| 510 | |
| 511 | for (my $i = 0; $i < $numBytes; $i ++) |
| 512 | { |
| 513 | $num += ord($byteList[$i]) * $mult; |
| 514 | $mult *= 256; |
| 515 | } |
| 516 | |
| 517 | return $num; |
| 518 | } |
| 519 | |
| 520 | sub _decodeInt5Bit |
| 521 | { |
| 522 | my $byte = ord(shift); |
| 523 | |
| 524 | $byte = $byte & 0xF8; # clear out the bottm 3 bits |
| 525 | $byte = $byte >> 3; # and shifted down to where it belongs |
| 526 | |
| 527 | return $byte; |
| 528 | } |
| 529 | |
| 530 | sub _decodeInt4Bit |
| 531 | { |
| 532 | my $byte = ord(shift); |
| 533 | |
| 534 | $byte = $byte & 0xFC; # clear out the bottm 4 bits |
| 535 | $byte = $byte >> 4; # and shifted down to where it belongs |
| 536 | |
| 537 | return $byte; |
| 538 | } |
| 539 | |
| 540 | sub _ilog |
| 541 | { |
| 542 | my $x = shift; |
| 543 | my $ret = 0; |
| 544 | |
| 545 | unless ($x > 0) |
| 546 | { |
| 547 | return 0; |
| 548 | } |
| 549 | |
| 550 | while ($x > 0) |
| 551 | { |
| 552 | $ret++; |
| 553 | $x = $x >> 1; |
| 554 | } |
| 555 | |
| 556 | return $ret; |
| 557 | } |
| 558 | |
| 559 | 1; |
| 560 | __DATA__ |
| 561 | |
| 562 | =head1 NAME |
| 563 | |
| 564 | Ogg::Vorbis::Header::PurePerl - An object-oriented interface to Ogg Vorbis |
| 565 | information and comment fields, implemented entirely in Perl. Intended to be |
| 566 | a drop in replacement for Ogg::Vobis::Header. |
| 567 | |
| 568 | Unlike Ogg::Vorbis::Header, this module will go ahead and fill in all of the |
| 569 | information fields as soon as you construct the object. In other words, |
| 570 | the C<new> and C<load> constructors have identical behavior. |
| 571 | |
| 572 | =head1 SYNOPSIS |
| 573 | |
| 574 | use Ogg::Vorbis::Header::PurePerl; |
| 575 | my $ogg = Ogg::Vorbis::Header::PurePerl->new("song.ogg"); |
| 576 | while (my ($k, $v) = each %{$ogg->info}) { |
| 577 | print "$k: $v\n"; |
| 578 | } |
| 579 | foreach my $com ($ogg->comment_tags) { |
| 580 | print "$com: $_\n" foreach $ogg->comment($com); |
| 581 | } |
| 582 | |
| 583 | =head1 DESCRIPTION |
| 584 | |
| 585 | This module is intended to be a drop in replacement for Ogg::Vorbis::Header, |
| 586 | implemented entirely in Perl. It provides an object-oriented interface to |
| 587 | Ogg Vorbis information and comment fields. (NOTE: This module currently |
| 588 | supports only read operations). |
| 589 | |
| 590 | =head1 CONSTRUCTORS |
| 591 | |
| 592 | =head2 C<new ($filename)> |
| 593 | |
| 594 | Opens an Ogg Vorbis file, ensuring that it exists and is actually an |
| 595 | Ogg Vorbis stream. This method does not actually read any of the |
| 596 | information or comment fields, and closes the file immediately. |
| 597 | |
| 598 | =head2 C<load ([$filename])> |
| 599 | |
| 600 | Opens an Ogg Vorbis file, ensuring that it exists and is actually an |
| 601 | Ogg Vorbis stream, then loads the information and comment fields. This |
| 602 | method can also be used without a filename to load the information |
| 603 | and fields of an already constructed instance. |
| 604 | |
| 605 | =head1 INSTANCE METHODS |
| 606 | |
| 607 | =head2 C<info ([$key])> |
| 608 | |
| 609 | Returns a hashref containing information about the Ogg Vorbis file from |
| 610 | the file's information header. Hash fields are: version, channels, rate, |
| 611 | bitrate_upper, bitrate_nominal, bitrate_lower, bitrate_window, and length. |
| 612 | The bitrate_window value is not currently used by the vorbis codec, and |
| 613 | will always be -1. |
| 614 | |
| 615 | The optional parameter, key, allows you to retrieve a single value from |
| 616 | the object's hash. Returns C<undef> if the key is not found. |
| 617 | |
| 618 | =head2 C<comment_tags ()> |
| 619 | |
| 620 | Returns an array containing the key values for the comment fields. |
| 621 | These values can then be passed to C<comment> to retrieve their values. |
| 622 | |
| 623 | =head2 C<comment ($key)> |
| 624 | |
| 625 | Returns an array of comment values associated with the given key. |
| 626 | |
| 627 | =head2 C<add_comments ($key, $value, [$key, $value, ...])> |
| 628 | |
| 629 | Unimplemented. |
| 630 | |
| 631 | =head2 C<edit_comment ($key, $value, [$num])> |
| 632 | |
| 633 | Unimplemented. |
| 634 | |
| 635 | =head2 C<delete_comment ($key, [$num])> |
| 636 | |
| 637 | Unimplemented. |
| 638 | |
| 639 | =head2 C<clear_comments ([@keys])> |
| 640 | |
| 641 | Unimplemented. |
| 642 | |
| 643 | =head2 C<write_vorbis ()> |
| 644 | |
| 645 | Unimplemented. |
| 646 | |
| 647 | =head2 C<path ()> |
| 648 | |
| 649 | Returns the path/filename of the file the object represents. |
| 650 | |
| 651 | =head1 NOTE |
| 652 | |
| 653 | This is ALPHA SOFTWARE. It may very well be very broken. Do not use it in |
| 654 | a production environment. You have been warned. |
| 655 | |
| 656 | =head1 ACKNOWLEDGEMENTS |
| 657 | |
| 658 | Dave Brown <cpan@dagbrown.com> made this module significantly faster |
| 659 | at calculating the length of ogg files. |
| 660 | |
| 661 | Robert Moser II <rlmoser@earthlink.net> fixed a problem with files that |
| 662 | have no comments. |
| 663 | |
| 664 | =head1 AUTHOR |
| 665 | |
| 666 | Andrew Molloy E<lt>amolloy@kaizolabs.comE<gt> |
| 667 | |
| 668 | =head1 COPYRIGHT |
| 669 | |
| 670 | Copyright (c) 2003, Andrew Molloy. All Rights Reserved. |
| 671 | |
| 672 | This program is free software; you can redistribute it and/or modify it |
| 673 | under the terms of the GNU General Public License as published by the |
| 674 | Free Software Foundation; either version 2 of the License, or (at |
| 675 | your option) any later version. A copy of this license is included |
| 676 | with this module (LICENSE.GPL). |
| 677 | |
| 678 | =head1 SEE ALSO |
| 679 | |
| 680 | L<Ogg::Vorbis::Header>, L<Ogg::Vorbis::Decoder> |
| 681 | |
| 682 | =cut |