2013-03-21 05:55:42 +01:00
< ? php
2019-09-14 21:07:57 +02:00
2013-03-21 05:55:42 +01:00
/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org> //
2019-09-14 21:07:57 +02:00
// available at https://github.com/JamesHeinrich/getID3 //
// or https://www.getid3.org //
// or http://getid3.sourceforge.net //
// see readme.txt for more details //
2013-03-21 05:55:42 +01:00
/////////////////////////////////////////////////////////////////
// //
// module.audio.ogg.php //
// module for analyzing Ogg Vorbis, OggFLAC and Speex files //
// dependencies: module.audio.flac.php //
// ///
/////////////////////////////////////////////////////////////////
2020-07-02 17:48:04 +02:00
if ( ! defined ( 'GETID3_INCLUDEPATH' )) { // prevent path-exposing attacks that access modules directly on public webservers
exit ;
}
2013-03-21 05:55:42 +01:00
getid3_lib :: IncludeDependency ( GETID3_INCLUDEPATH . 'module.audio.flac.php' , __FILE__ , true );
class getid3_ogg extends getid3_handler
{
2019-09-14 21:07:57 +02:00
/**
* @ link http :// xiph . org / vorbis / doc / Vorbis_I_spec . html
*
* @ return bool
*/
2013-03-21 05:55:42 +01:00
public function Analyze () {
$info = & $this -> getid3 -> info ;
$info [ 'fileformat' ] = 'ogg' ;
// Warn about illegal tags - only vorbiscomments are allowed
if ( isset ( $info [ 'id3v2' ])) {
2017-07-31 21:50:45 +02:00
$this -> warning ( 'Illegal ID3v2 tag present.' );
2013-03-21 05:55:42 +01:00
}
if ( isset ( $info [ 'id3v1' ])) {
2017-07-31 21:50:45 +02:00
$this -> warning ( 'Illegal ID3v1 tag present.' );
2013-03-21 05:55:42 +01:00
}
if ( isset ( $info [ 'ape' ])) {
2017-07-31 21:50:45 +02:00
$this -> warning ( 'Illegal APE tag present.' );
2013-03-21 05:55:42 +01:00
}
// Page 1 - Stream Header
$this -> fseek ( $info [ 'avdataoffset' ]);
$oggpageinfo = $this -> ParseOggPageHeader ();
$info [ 'ogg' ][ 'pageheader' ][ $oggpageinfo [ 'page_seqno' ]] = $oggpageinfo ;
if ( $this -> ftell () >= $this -> getid3 -> fread_buffer_size ()) {
2017-07-31 21:50:45 +02:00
$this -> error ( 'Could not find start of Ogg page in the first ' . $this -> getid3 -> fread_buffer_size () . ' bytes (this might not be an Ogg-Vorbis file?)' );
2013-03-21 05:55:42 +01:00
unset ( $info [ 'fileformat' ]);
unset ( $info [ 'ogg' ]);
return false ;
}
$filedata = $this -> fread ( $oggpageinfo [ 'page_length' ]);
$filedataoffset = 0 ;
if ( substr ( $filedata , 0 , 4 ) == 'fLaC' ) {
$info [ 'audio' ][ 'dataformat' ] = 'flac' ;
$info [ 'audio' ][ 'bitrate_mode' ] = 'vbr' ;
$info [ 'audio' ][ 'lossless' ] = true ;
} elseif ( substr ( $filedata , 1 , 6 ) == 'vorbis' ) {
$this -> ParseVorbisPageHeader ( $filedata , $filedataoffset , $oggpageinfo );
2015-06-28 02:17:25 +02:00
} elseif ( substr ( $filedata , 0 , 8 ) == 'OpusHead' ) {
2019-09-14 21:07:57 +02:00
if ( $this -> ParseOpusPageHeader ( $filedata , $filedataoffset , $oggpageinfo ) === false ) {
2015-06-28 02:17:25 +02:00
return false ;
}
2013-03-21 05:55:42 +01:00
} elseif ( substr ( $filedata , 0 , 8 ) == 'Speex ' ) {
// http://www.speex.org/manual/node10.html
$info [ 'audio' ][ 'dataformat' ] = 'speex' ;
$info [ 'mime_type' ] = 'audio/speex' ;
$info [ 'audio' ][ 'bitrate_mode' ] = 'abr' ;
$info [ 'audio' ][ 'lossless' ] = false ;
$info [ 'ogg' ][ 'pageheader' ][ $oggpageinfo [ 'page_seqno' ]][ 'speex_string' ] = substr ( $filedata , $filedataoffset , 8 ); // hard-coded to 'Speex '
$filedataoffset += 8 ;
$info [ 'ogg' ][ 'pageheader' ][ $oggpageinfo [ 'page_seqno' ]][ 'speex_version' ] = substr ( $filedata , $filedataoffset , 20 );
$filedataoffset += 20 ;
$info [ 'ogg' ][ 'pageheader' ][ $oggpageinfo [ 'page_seqno' ]][ 'speex_version_id' ] = getid3_lib :: LittleEndian2Int ( substr ( $filedata , $filedataoffset , 4 ));
$filedataoffset += 4 ;
$info [ 'ogg' ][ 'pageheader' ][ $oggpageinfo [ 'page_seqno' ]][ 'header_size' ] = getid3_lib :: LittleEndian2Int ( substr ( $filedata , $filedataoffset , 4 ));
$filedataoffset += 4 ;
$info [ 'ogg' ][ 'pageheader' ][ $oggpageinfo [ 'page_seqno' ]][ 'rate' ] = getid3_lib :: LittleEndian2Int ( substr ( $filedata , $filedataoffset , 4 ));
$filedataoffset += 4 ;
$info [ 'ogg' ][ 'pageheader' ][ $oggpageinfo [ 'page_seqno' ]][ 'mode' ] = getid3_lib :: LittleEndian2Int ( substr ( $filedata , $filedataoffset , 4 ));
$filedataoffset += 4 ;
$info [ 'ogg' ][ 'pageheader' ][ $oggpageinfo [ 'page_seqno' ]][ 'mode_bitstream_version' ] = getid3_lib :: LittleEndian2Int ( substr ( $filedata , $filedataoffset , 4 ));
$filedataoffset += 4 ;
$info [ 'ogg' ][ 'pageheader' ][ $oggpageinfo [ 'page_seqno' ]][ 'nb_channels' ] = getid3_lib :: LittleEndian2Int ( substr ( $filedata , $filedataoffset , 4 ));
$filedataoffset += 4 ;
$info [ 'ogg' ][ 'pageheader' ][ $oggpageinfo [ 'page_seqno' ]][ 'bitrate' ] = getid3_lib :: LittleEndian2Int ( substr ( $filedata , $filedataoffset , 4 ));
$filedataoffset += 4 ;
$info [ 'ogg' ][ 'pageheader' ][ $oggpageinfo [ 'page_seqno' ]][ 'framesize' ] = getid3_lib :: LittleEndian2Int ( substr ( $filedata , $filedataoffset , 4 ));
$filedataoffset += 4 ;
$info [ 'ogg' ][ 'pageheader' ][ $oggpageinfo [ 'page_seqno' ]][ 'vbr' ] = getid3_lib :: LittleEndian2Int ( substr ( $filedata , $filedataoffset , 4 ));
$filedataoffset += 4 ;
$info [ 'ogg' ][ 'pageheader' ][ $oggpageinfo [ 'page_seqno' ]][ 'frames_per_packet' ] = getid3_lib :: LittleEndian2Int ( substr ( $filedata , $filedataoffset , 4 ));
$filedataoffset += 4 ;
$info [ 'ogg' ][ 'pageheader' ][ $oggpageinfo [ 'page_seqno' ]][ 'extra_headers' ] = getid3_lib :: LittleEndian2Int ( substr ( $filedata , $filedataoffset , 4 ));
$filedataoffset += 4 ;
$info [ 'ogg' ][ 'pageheader' ][ $oggpageinfo [ 'page_seqno' ]][ 'reserved1' ] = getid3_lib :: LittleEndian2Int ( substr ( $filedata , $filedataoffset , 4 ));
$filedataoffset += 4 ;
$info [ 'ogg' ][ 'pageheader' ][ $oggpageinfo [ 'page_seqno' ]][ 'reserved2' ] = getid3_lib :: LittleEndian2Int ( substr ( $filedata , $filedataoffset , 4 ));
$filedataoffset += 4 ;
$info [ 'speex' ][ 'speex_version' ] = trim ( $info [ 'ogg' ][ 'pageheader' ][ $oggpageinfo [ 'page_seqno' ]][ 'speex_version' ]);
$info [ 'speex' ][ 'sample_rate' ] = $info [ 'ogg' ][ 'pageheader' ][ $oggpageinfo [ 'page_seqno' ]][ 'rate' ];
$info [ 'speex' ][ 'channels' ] = $info [ 'ogg' ][ 'pageheader' ][ $oggpageinfo [ 'page_seqno' ]][ 'nb_channels' ];
$info [ 'speex' ][ 'vbr' ] = ( bool ) $info [ 'ogg' ][ 'pageheader' ][ $oggpageinfo [ 'page_seqno' ]][ 'vbr' ];
$info [ 'speex' ][ 'band_type' ] = $this -> SpeexBandModeLookup ( $info [ 'ogg' ][ 'pageheader' ][ $oggpageinfo [ 'page_seqno' ]][ 'mode' ]);
$info [ 'audio' ][ 'sample_rate' ] = $info [ 'speex' ][ 'sample_rate' ];
$info [ 'audio' ][ 'channels' ] = $info [ 'speex' ][ 'channels' ];
if ( $info [ 'speex' ][ 'vbr' ]) {
$info [ 'audio' ][ 'bitrate_mode' ] = 'vbr' ;
}
2014-09-11 21:07:17 +02:00
} elseif ( substr ( $filedata , 0 , 7 ) == " \x80 " . 'theora' ) {
// http://www.theora.org/doc/Theora.pdf (section 6.2)
$info [ 'ogg' ][ 'pageheader' ][ 'theora' ][ 'theora_magic' ] = substr ( $filedata , $filedataoffset , 7 ); // hard-coded to "\x80.'theora'
$filedataoffset += 7 ;
$info [ 'ogg' ][ 'pageheader' ][ 'theora' ][ 'version_major' ] = getid3_lib :: BigEndian2Int ( substr ( $filedata , $filedataoffset , 1 ));
$filedataoffset += 1 ;
$info [ 'ogg' ][ 'pageheader' ][ 'theora' ][ 'version_minor' ] = getid3_lib :: BigEndian2Int ( substr ( $filedata , $filedataoffset , 1 ));
$filedataoffset += 1 ;
$info [ 'ogg' ][ 'pageheader' ][ 'theora' ][ 'version_revision' ] = getid3_lib :: BigEndian2Int ( substr ( $filedata , $filedataoffset , 1 ));
$filedataoffset += 1 ;
$info [ 'ogg' ][ 'pageheader' ][ 'theora' ][ 'frame_width_macroblocks' ] = getid3_lib :: BigEndian2Int ( substr ( $filedata , $filedataoffset , 2 ));
$filedataoffset += 2 ;
$info [ 'ogg' ][ 'pageheader' ][ 'theora' ][ 'frame_height_macroblocks' ] = getid3_lib :: BigEndian2Int ( substr ( $filedata , $filedataoffset , 2 ));
$filedataoffset += 2 ;
$info [ 'ogg' ][ 'pageheader' ][ 'theora' ][ 'resolution_x' ] = getid3_lib :: BigEndian2Int ( substr ( $filedata , $filedataoffset , 3 ));
$filedataoffset += 3 ;
$info [ 'ogg' ][ 'pageheader' ][ 'theora' ][ 'resolution_y' ] = getid3_lib :: BigEndian2Int ( substr ( $filedata , $filedataoffset , 3 ));
$filedataoffset += 3 ;
$info [ 'ogg' ][ 'pageheader' ][ 'theora' ][ 'picture_offset_x' ] = getid3_lib :: BigEndian2Int ( substr ( $filedata , $filedataoffset , 1 ));
$filedataoffset += 1 ;
$info [ 'ogg' ][ 'pageheader' ][ 'theora' ][ 'picture_offset_y' ] = getid3_lib :: BigEndian2Int ( substr ( $filedata , $filedataoffset , 1 ));
$filedataoffset += 1 ;
$info [ 'ogg' ][ 'pageheader' ][ 'theora' ][ 'frame_rate_numerator' ] = getid3_lib :: BigEndian2Int ( substr ( $filedata , $filedataoffset , 4 ));
$filedataoffset += 4 ;
$info [ 'ogg' ][ 'pageheader' ][ 'theora' ][ 'frame_rate_denominator' ] = getid3_lib :: BigEndian2Int ( substr ( $filedata , $filedataoffset , 4 ));
$filedataoffset += 4 ;
$info [ 'ogg' ][ 'pageheader' ][ 'theora' ][ 'pixel_aspect_numerator' ] = getid3_lib :: BigEndian2Int ( substr ( $filedata , $filedataoffset , 3 ));
$filedataoffset += 3 ;
$info [ 'ogg' ][ 'pageheader' ][ 'theora' ][ 'pixel_aspect_denominator' ] = getid3_lib :: BigEndian2Int ( substr ( $filedata , $filedataoffset , 3 ));
$filedataoffset += 3 ;
$info [ 'ogg' ][ 'pageheader' ][ 'theora' ][ 'color_space_id' ] = getid3_lib :: BigEndian2Int ( substr ( $filedata , $filedataoffset , 1 ));
$filedataoffset += 1 ;
$info [ 'ogg' ][ 'pageheader' ][ 'theora' ][ 'nominal_bitrate' ] = getid3_lib :: BigEndian2Int ( substr ( $filedata , $filedataoffset , 3 ));
$filedataoffset += 3 ;
$info [ 'ogg' ][ 'pageheader' ][ 'theora' ][ 'flags' ] = getid3_lib :: BigEndian2Int ( substr ( $filedata , $filedataoffset , 2 ));
$filedataoffset += 2 ;
$info [ 'ogg' ][ 'pageheader' ][ 'theora' ][ 'quality' ] = ( $info [ 'ogg' ][ 'pageheader' ][ 'theora' ][ 'flags' ] & 0xFC00 ) >> 10 ;
$info [ 'ogg' ][ 'pageheader' ][ 'theora' ][ 'kfg_shift' ] = ( $info [ 'ogg' ][ 'pageheader' ][ 'theora' ][ 'flags' ] & 0x03E0 ) >> 5 ;
$info [ 'ogg' ][ 'pageheader' ][ 'theora' ][ 'pixel_format_id' ] = ( $info [ 'ogg' ][ 'pageheader' ][ 'theora' ][ 'flags' ] & 0x0018 ) >> 3 ;
$info [ 'ogg' ][ 'pageheader' ][ 'theora' ][ 'reserved' ] = ( $info [ 'ogg' ][ 'pageheader' ][ 'theora' ][ 'flags' ] & 0x0007 ) >> 0 ; // should be 0
$info [ 'ogg' ][ 'pageheader' ][ 'theora' ][ 'color_space' ] = self :: TheoraColorSpace ( $info [ 'ogg' ][ 'pageheader' ][ 'theora' ][ 'color_space_id' ]);
$info [ 'ogg' ][ 'pageheader' ][ 'theora' ][ 'pixel_format' ] = self :: TheoraPixelFormat ( $info [ 'ogg' ][ 'pageheader' ][ 'theora' ][ 'pixel_format_id' ]);
$info [ 'video' ][ 'dataformat' ] = 'theora' ;
$info [ 'mime_type' ] = 'video/ogg' ;
//$info['audio']['bitrate_mode'] = 'abr';
//$info['audio']['lossless'] = false;
$info [ 'video' ][ 'resolution_x' ] = $info [ 'ogg' ][ 'pageheader' ][ 'theora' ][ 'resolution_x' ];
$info [ 'video' ][ 'resolution_y' ] = $info [ 'ogg' ][ 'pageheader' ][ 'theora' ][ 'resolution_y' ];
if ( $info [ 'ogg' ][ 'pageheader' ][ 'theora' ][ 'frame_rate_denominator' ] > 0 ) {
$info [ 'video' ][ 'frame_rate' ] = ( float ) $info [ 'ogg' ][ 'pageheader' ][ 'theora' ][ 'frame_rate_numerator' ] / $info [ 'ogg' ][ 'pageheader' ][ 'theora' ][ 'frame_rate_denominator' ];
}
if ( $info [ 'ogg' ][ 'pageheader' ][ 'theora' ][ 'pixel_aspect_denominator' ] > 0 ) {
$info [ 'video' ][ 'pixel_aspect_ratio' ] = ( float ) $info [ 'ogg' ][ 'pageheader' ][ 'theora' ][ 'pixel_aspect_numerator' ] / $info [ 'ogg' ][ 'pageheader' ][ 'theora' ][ 'pixel_aspect_denominator' ];
}
2022-10-04 04:08:11 +02:00
$this -> warning ( 'Ogg Theora (v3) not fully supported in this version of getID3 [' . $this -> getid3 -> version () . '] -- bitrate, playtime and all audio data are currently unavailable' );
2014-09-11 21:07:17 +02:00
2013-03-21 05:55:42 +01:00
} elseif ( substr ( $filedata , 0 , 8 ) == " fishead \x00 " ) {
// Ogg Skeleton version 3.0 Format Specification
// http://xiph.org/ogg/doc/skeleton.html
$filedataoffset += 8 ;
$info [ 'ogg' ][ 'skeleton' ][ 'fishead' ][ 'raw' ][ 'version_major' ] = getid3_lib :: LittleEndian2Int ( substr ( $filedata , $filedataoffset , 2 ));
$filedataoffset += 2 ;
$info [ 'ogg' ][ 'skeleton' ][ 'fishead' ][ 'raw' ][ 'version_minor' ] = getid3_lib :: LittleEndian2Int ( substr ( $filedata , $filedataoffset , 2 ));
$filedataoffset += 2 ;
$info [ 'ogg' ][ 'skeleton' ][ 'fishead' ][ 'raw' ][ 'presentationtime_numerator' ] = getid3_lib :: LittleEndian2Int ( substr ( $filedata , $filedataoffset , 8 ));
$filedataoffset += 8 ;
$info [ 'ogg' ][ 'skeleton' ][ 'fishead' ][ 'raw' ][ 'presentationtime_denominator' ] = getid3_lib :: LittleEndian2Int ( substr ( $filedata , $filedataoffset , 8 ));
$filedataoffset += 8 ;
$info [ 'ogg' ][ 'skeleton' ][ 'fishead' ][ 'raw' ][ 'basetime_numerator' ] = getid3_lib :: LittleEndian2Int ( substr ( $filedata , $filedataoffset , 8 ));
$filedataoffset += 8 ;
$info [ 'ogg' ][ 'skeleton' ][ 'fishead' ][ 'raw' ][ 'basetime_denominator' ] = getid3_lib :: LittleEndian2Int ( substr ( $filedata , $filedataoffset , 8 ));
$filedataoffset += 8 ;
$info [ 'ogg' ][ 'skeleton' ][ 'fishead' ][ 'raw' ][ 'utc' ] = getid3_lib :: LittleEndian2Int ( substr ( $filedata , $filedataoffset , 20 ));
$filedataoffset += 20 ;
$info [ 'ogg' ][ 'skeleton' ][ 'fishead' ][ 'version' ] = $info [ 'ogg' ][ 'skeleton' ][ 'fishead' ][ 'raw' ][ 'version_major' ] . '.' . $info [ 'ogg' ][ 'skeleton' ][ 'fishead' ][ 'raw' ][ 'version_minor' ];
2023-10-20 15:29:27 +02:00
$info [ 'ogg' ][ 'skeleton' ][ 'fishead' ][ 'presentationtime' ] = getid3_lib :: SafeDiv ( $info [ 'ogg' ][ 'skeleton' ][ 'fishead' ][ 'raw' ][ 'presentationtime_numerator' ], $info [ 'ogg' ][ 'skeleton' ][ 'fishead' ][ 'raw' ][ 'presentationtime_denominator' ]);
$info [ 'ogg' ][ 'skeleton' ][ 'fishead' ][ 'basetime' ] = getid3_lib :: SafeDiv ( $info [ 'ogg' ][ 'skeleton' ][ 'fishead' ][ 'raw' ][ 'basetime_numerator' ], $info [ 'ogg' ][ 'skeleton' ][ 'fishead' ][ 'raw' ][ 'basetime_denominator' ]);
2013-03-21 05:55:42 +01:00
$info [ 'ogg' ][ 'skeleton' ][ 'fishead' ][ 'utc' ] = $info [ 'ogg' ][ 'skeleton' ][ 'fishead' ][ 'raw' ][ 'utc' ];
$counter = 0 ;
do {
$oggpageinfo = $this -> ParseOggPageHeader ();
$info [ 'ogg' ][ 'pageheader' ][ $oggpageinfo [ 'page_seqno' ] . '.' . $counter ++ ] = $oggpageinfo ;
$filedata = $this -> fread ( $oggpageinfo [ 'page_length' ]);
$this -> fseek ( $oggpageinfo [ 'page_end_offset' ]);
if ( substr ( $filedata , 0 , 8 ) == " fisbone \x00 " ) {
$filedataoffset = 8 ;
$info [ 'ogg' ][ 'skeleton' ][ 'fisbone' ][ 'raw' ][ 'message_header_offset' ] = getid3_lib :: LittleEndian2Int ( substr ( $filedata , $filedataoffset , 4 ));
$filedataoffset += 4 ;
$info [ 'ogg' ][ 'skeleton' ][ 'fisbone' ][ 'raw' ][ 'serial_number' ] = getid3_lib :: LittleEndian2Int ( substr ( $filedata , $filedataoffset , 4 ));
$filedataoffset += 4 ;
$info [ 'ogg' ][ 'skeleton' ][ 'fisbone' ][ 'raw' ][ 'number_header_packets' ] = getid3_lib :: LittleEndian2Int ( substr ( $filedata , $filedataoffset , 4 ));
$filedataoffset += 4 ;
$info [ 'ogg' ][ 'skeleton' ][ 'fisbone' ][ 'raw' ][ 'granulerate_numerator' ] = getid3_lib :: LittleEndian2Int ( substr ( $filedata , $filedataoffset , 8 ));
$filedataoffset += 8 ;
$info [ 'ogg' ][ 'skeleton' ][ 'fisbone' ][ 'raw' ][ 'granulerate_denominator' ] = getid3_lib :: LittleEndian2Int ( substr ( $filedata , $filedataoffset , 8 ));
$filedataoffset += 8 ;
$info [ 'ogg' ][ 'skeleton' ][ 'fisbone' ][ 'raw' ][ 'basegranule' ] = getid3_lib :: LittleEndian2Int ( substr ( $filedata , $filedataoffset , 8 ));
$filedataoffset += 8 ;
$info [ 'ogg' ][ 'skeleton' ][ 'fisbone' ][ 'raw' ][ 'preroll' ] = getid3_lib :: LittleEndian2Int ( substr ( $filedata , $filedataoffset , 4 ));
$filedataoffset += 4 ;
$info [ 'ogg' ][ 'skeleton' ][ 'fisbone' ][ 'raw' ][ 'granuleshift' ] = getid3_lib :: LittleEndian2Int ( substr ( $filedata , $filedataoffset , 1 ));
$filedataoffset += 1 ;
$info [ 'ogg' ][ 'skeleton' ][ 'fisbone' ][ 'raw' ][ 'padding' ] = substr ( $filedata , $filedataoffset , 3 );
$filedataoffset += 3 ;
} elseif ( substr ( $filedata , 1 , 6 ) == 'theora' ) {
2014-09-11 21:07:17 +02:00
$info [ 'video' ][ 'dataformat' ] = 'theora1' ;
2017-07-31 21:50:45 +02:00
$this -> error ( 'Ogg Theora (v1) not correctly handled in this version of getID3 [' . $this -> getid3 -> version () . ']' );
2013-03-21 05:55:42 +01:00
//break;
} elseif ( substr ( $filedata , 1 , 6 ) == 'vorbis' ) {
$this -> ParseVorbisPageHeader ( $filedata , $filedataoffset , $oggpageinfo );
} else {
2017-07-31 21:50:45 +02:00
$this -> error ( 'unexpected' );
2013-03-21 05:55:42 +01:00
//break;
}
//} while ($oggpageinfo['page_seqno'] == 0);
} while (( $oggpageinfo [ 'page_seqno' ] == 0 ) && ( substr ( $filedata , 0 , 8 ) != " fisbone \x00 " ));
$this -> fseek ( $oggpageinfo [ 'page_start_offset' ]);
2017-07-31 21:50:45 +02:00
$this -> error ( 'Ogg Skeleton not correctly handled in this version of getID3 [' . $this -> getid3 -> version () . ']' );
2013-03-21 05:55:42 +01:00
//return false;
2019-09-14 21:07:57 +02:00
} elseif ( substr ( $filedata , 0 , 5 ) == " \x7F " . 'FLAC' ) {
// https://xiph.org/flac/ogg_mapping.html
$info [ 'audio' ][ 'dataformat' ] = 'flac' ;
$info [ 'audio' ][ 'bitrate_mode' ] = 'vbr' ;
$info [ 'audio' ][ 'lossless' ] = true ;
$info [ 'ogg' ][ 'flac' ][ 'header' ][ 'version_major' ] = ord ( substr ( $filedata , 5 , 1 ));
$info [ 'ogg' ][ 'flac' ][ 'header' ][ 'version_minor' ] = ord ( substr ( $filedata , 6 , 1 ));
$info [ 'ogg' ][ 'flac' ][ 'header' ][ 'header_packets' ] = getid3_lib :: BigEndian2Int ( substr ( $filedata , 7 , 2 )) + 1 ; // "A two-byte, big-endian binary number signifying the number of header (non-audio) packets, not including this one. This number may be zero (0x0000) to signify 'unknown' but be aware that some decoders may not be able to handle such streams."
$info [ 'ogg' ][ 'flac' ][ 'header' ][ 'magic' ] = substr ( $filedata , 9 , 4 );
if ( $info [ 'ogg' ][ 'flac' ][ 'header' ][ 'magic' ] != 'fLaC' ) {
$this -> error ( 'Ogg-FLAC expecting "fLaC", found "' . $info [ 'ogg' ][ 'flac' ][ 'header' ][ 'magic' ] . '" (' . trim ( getid3_lib :: PrintHexBytes ( $info [ 'ogg' ][ 'flac' ][ 'header' ][ 'magic' ])) . ')' );
return false ;
}
$info [ 'ogg' ][ 'flac' ][ 'header' ][ 'STREAMINFO_bytes' ] = getid3_lib :: BigEndian2Int ( substr ( $filedata , 13 , 4 ));
$info [ 'flac' ][ 'STREAMINFO' ] = getid3_flac :: parseSTREAMINFOdata ( substr ( $filedata , 17 , 34 ));
if ( ! empty ( $info [ 'flac' ][ 'STREAMINFO' ][ 'sample_rate' ])) {
$info [ 'audio' ][ 'bitrate_mode' ] = 'vbr' ;
$info [ 'audio' ][ 'sample_rate' ] = $info [ 'flac' ][ 'STREAMINFO' ][ 'sample_rate' ];
$info [ 'audio' ][ 'channels' ] = $info [ 'flac' ][ 'STREAMINFO' ][ 'channels' ];
$info [ 'audio' ][ 'bits_per_sample' ] = $info [ 'flac' ][ 'STREAMINFO' ][ 'bits_per_sample' ];
2023-10-20 15:29:27 +02:00
$info [ 'playtime_seconds' ] = getid3_lib :: SafeDiv ( $info [ 'flac' ][ 'STREAMINFO' ][ 'samples_stream' ], $info [ 'flac' ][ 'STREAMINFO' ][ 'sample_rate' ]);
2019-09-14 21:07:57 +02:00
}
2013-03-21 05:55:42 +01:00
} else {
2019-09-14 21:07:57 +02:00
$this -> error ( 'Expecting one of "vorbis", "Speex", "OpusHead", "vorbis", "fishhead", "theora", "fLaC" identifier strings, found "' . substr ( $filedata , 0 , 8 ) . '"' );
2013-03-21 05:55:42 +01:00
unset ( $info [ 'ogg' ]);
unset ( $info [ 'mime_type' ]);
return false ;
}
// Page 2 - Comment Header
$oggpageinfo = $this -> ParseOggPageHeader ();
$info [ 'ogg' ][ 'pageheader' ][ $oggpageinfo [ 'page_seqno' ]] = $oggpageinfo ;
switch ( $info [ 'audio' ][ 'dataformat' ]) {
case 'vorbis' :
$filedata = $this -> fread ( $info [ 'ogg' ][ 'pageheader' ][ $oggpageinfo [ 'page_seqno' ]][ 'page_length' ]);
$info [ 'ogg' ][ 'pageheader' ][ $oggpageinfo [ 'page_seqno' ]][ 'packet_type' ] = getid3_lib :: LittleEndian2Int ( substr ( $filedata , 0 , 1 ));
$info [ 'ogg' ][ 'pageheader' ][ $oggpageinfo [ 'page_seqno' ]][ 'stream_type' ] = substr ( $filedata , 1 , 6 ); // hard-coded to 'vorbis'
$this -> ParseVorbisComments ();
break ;
case 'flac' :
$flac = new getid3_flac ( $this -> getid3 );
if ( ! $flac -> parseMETAdata ()) {
2017-07-31 21:50:45 +02:00
$this -> error ( 'Failed to parse FLAC headers' );
2013-03-21 05:55:42 +01:00
return false ;
}
unset ( $flac );
break ;
case 'speex' :
$this -> fseek ( $info [ 'ogg' ][ 'pageheader' ][ $oggpageinfo [ 'page_seqno' ]][ 'page_length' ], SEEK_CUR );
$this -> ParseVorbisComments ();
break ;
2015-06-28 02:17:25 +02:00
case 'opus' :
$filedata = $this -> fread ( $info [ 'ogg' ][ 'pageheader' ][ $oggpageinfo [ 'page_seqno' ]][ 'page_length' ]);
$info [ 'ogg' ][ 'pageheader' ][ $oggpageinfo [ 'page_seqno' ]][ 'stream_type' ] = substr ( $filedata , 0 , 8 ); // hard-coded to 'OpusTags'
if ( substr ( $filedata , 0 , 8 ) != 'OpusTags' ) {
2017-07-31 21:50:45 +02:00
$this -> error ( 'Expected "OpusTags" as header but got "' . substr ( $filedata , 0 , 8 ) . '"' );
2015-06-28 02:17:25 +02:00
return false ;
}
$this -> ParseVorbisComments ();
break ;
}
2013-03-21 05:55:42 +01:00
// Last Page - Number of Samples
if ( ! getid3_lib :: intValueSupported ( $info [ 'avdataend' ])) {
2017-07-31 21:50:45 +02:00
$this -> warning ( 'Unable to parse Ogg end chunk file (PHP does not support file operations beyond ' . round ( PHP_INT_MAX / 1073741824 ) . 'GB)' );
2013-03-21 05:55:42 +01:00
} else {
$this -> fseek ( max ( $info [ 'avdataend' ] - $this -> getid3 -> fread_buffer_size (), 0 ));
$LastChunkOfOgg = strrev ( $this -> fread ( $this -> getid3 -> fread_buffer_size ()));
if ( $LastOggSpostion = strpos ( $LastChunkOfOgg , 'SggO' )) {
$this -> fseek ( $info [ 'avdataend' ] - ( $LastOggSpostion + strlen ( 'SggO' )));
$info [ 'avdataend' ] = $this -> ftell ();
$info [ 'ogg' ][ 'pageheader' ][ 'eos' ] = $this -> ParseOggPageHeader ();
$info [ 'ogg' ][ 'samples' ] = $info [ 'ogg' ][ 'pageheader' ][ 'eos' ][ 'pcm_abs_position' ];
if ( $info [ 'ogg' ][ 'samples' ] == 0 ) {
2017-07-31 21:50:45 +02:00
$this -> error ( 'Corrupt Ogg file: eos.number of samples == zero' );
2013-03-21 05:55:42 +01:00
return false ;
}
if ( ! empty ( $info [ 'audio' ][ 'sample_rate' ])) {
2023-10-20 15:29:27 +02:00
$info [ 'ogg' ][ 'bitrate_average' ] = (( $info [ 'avdataend' ] - $info [ 'avdataoffset' ]) * 8 ) * $info [ 'audio' ][ 'sample_rate' ] / $info [ 'ogg' ][ 'samples' ];
2013-03-21 05:55:42 +01:00
}
}
}
if ( ! empty ( $info [ 'ogg' ][ 'bitrate_average' ])) {
$info [ 'audio' ][ 'bitrate' ] = $info [ 'ogg' ][ 'bitrate_average' ];
} elseif ( ! empty ( $info [ 'ogg' ][ 'bitrate_nominal' ])) {
$info [ 'audio' ][ 'bitrate' ] = $info [ 'ogg' ][ 'bitrate_nominal' ];
} elseif ( ! empty ( $info [ 'ogg' ][ 'bitrate_min' ]) && ! empty ( $info [ 'ogg' ][ 'bitrate_max' ])) {
$info [ 'audio' ][ 'bitrate' ] = ( $info [ 'ogg' ][ 'bitrate_min' ] + $info [ 'ogg' ][ 'bitrate_max' ]) / 2 ;
}
if ( isset ( $info [ 'audio' ][ 'bitrate' ]) && ! isset ( $info [ 'playtime_seconds' ])) {
if ( $info [ 'audio' ][ 'bitrate' ] == 0 ) {
2017-07-31 21:50:45 +02:00
$this -> error ( 'Corrupt Ogg file: bitrate_audio == zero' );
2013-03-21 05:55:42 +01:00
return false ;
}
$info [ 'playtime_seconds' ] = ( float ) ((( $info [ 'avdataend' ] - $info [ 'avdataoffset' ]) * 8 ) / $info [ 'audio' ][ 'bitrate' ]);
}
if ( isset ( $info [ 'ogg' ][ 'vendor' ])) {
$info [ 'audio' ][ 'encoder' ] = preg_replace ( '/^Encoded with /' , '' , $info [ 'ogg' ][ 'vendor' ]);
// Vorbis only
if ( $info [ 'audio' ][ 'dataformat' ] == 'vorbis' ) {
// Vorbis 1.0 starts with Xiph.Org
if ( preg_match ( '/^Xiph.Org/' , $info [ 'audio' ][ 'encoder' ])) {
if ( $info [ 'audio' ][ 'bitrate_mode' ] == 'abr' ) {
// Set -b 128 on abr files
$info [ 'audio' ][ 'encoder_options' ] = '-b ' . round ( $info [ 'ogg' ][ 'bitrate_nominal' ] / 1000 );
} elseif (( $info [ 'audio' ][ 'bitrate_mode' ] == 'vbr' ) && ( $info [ 'audio' ][ 'channels' ] == 2 ) && ( $info [ 'audio' ][ 'sample_rate' ] >= 44100 ) && ( $info [ 'audio' ][ 'sample_rate' ] <= 48000 )) {
// Set -q N on vbr files
$info [ 'audio' ][ 'encoder_options' ] = '-q ' . $this -> get_quality_from_nominal_bitrate ( $info [ 'ogg' ][ 'bitrate_nominal' ]);
}
}
if ( empty ( $info [ 'audio' ][ 'encoder_options' ]) && ! empty ( $info [ 'ogg' ][ 'bitrate_nominal' ])) {
$info [ 'audio' ][ 'encoder_options' ] = 'Nominal bitrate: ' . intval ( round ( $info [ 'ogg' ][ 'bitrate_nominal' ] / 1000 )) . 'kbps' ;
}
}
}
return true ;
}
2019-09-14 21:07:57 +02:00
/**
* @ param string $filedata
* @ param int $filedataoffset
* @ param array $oggpageinfo
*
* @ return bool
*/
2013-03-21 05:55:42 +01:00
public function ParseVorbisPageHeader ( & $filedata , & $filedataoffset , & $oggpageinfo ) {
$info = & $this -> getid3 -> info ;
$info [ 'audio' ][ 'dataformat' ] = 'vorbis' ;
$info [ 'audio' ][ 'lossless' ] = false ;
$info [ 'ogg' ][ 'pageheader' ][ $oggpageinfo [ 'page_seqno' ]][ 'packet_type' ] = getid3_lib :: LittleEndian2Int ( substr ( $filedata , $filedataoffset , 1 ));
$filedataoffset += 1 ;
$info [ 'ogg' ][ 'pageheader' ][ $oggpageinfo [ 'page_seqno' ]][ 'stream_type' ] = substr ( $filedata , $filedataoffset , 6 ); // hard-coded to 'vorbis'
$filedataoffset += 6 ;
$info [ 'ogg' ][ 'bitstreamversion' ] = getid3_lib :: LittleEndian2Int ( substr ( $filedata , $filedataoffset , 4 ));
$filedataoffset += 4 ;
$info [ 'ogg' ][ 'numberofchannels' ] = getid3_lib :: LittleEndian2Int ( substr ( $filedata , $filedataoffset , 1 ));
$filedataoffset += 1 ;
$info [ 'audio' ][ 'channels' ] = $info [ 'ogg' ][ 'numberofchannels' ];
$info [ 'ogg' ][ 'samplerate' ] = getid3_lib :: LittleEndian2Int ( substr ( $filedata , $filedataoffset , 4 ));
$filedataoffset += 4 ;
if ( $info [ 'ogg' ][ 'samplerate' ] == 0 ) {
2017-07-31 21:50:45 +02:00
$this -> error ( 'Corrupt Ogg file: sample rate == zero' );
2013-03-21 05:55:42 +01:00
return false ;
}
$info [ 'audio' ][ 'sample_rate' ] = $info [ 'ogg' ][ 'samplerate' ];
$info [ 'ogg' ][ 'samples' ] = 0 ; // filled in later
$info [ 'ogg' ][ 'bitrate_average' ] = 0 ; // filled in later
$info [ 'ogg' ][ 'bitrate_max' ] = getid3_lib :: LittleEndian2Int ( substr ( $filedata , $filedataoffset , 4 ));
$filedataoffset += 4 ;
$info [ 'ogg' ][ 'bitrate_nominal' ] = getid3_lib :: LittleEndian2Int ( substr ( $filedata , $filedataoffset , 4 ));
$filedataoffset += 4 ;
$info [ 'ogg' ][ 'bitrate_min' ] = getid3_lib :: LittleEndian2Int ( substr ( $filedata , $filedataoffset , 4 ));
$filedataoffset += 4 ;
$info [ 'ogg' ][ 'blocksize_small' ] = pow ( 2 , getid3_lib :: LittleEndian2Int ( substr ( $filedata , $filedataoffset , 1 )) & 0x0F );
$info [ 'ogg' ][ 'blocksize_large' ] = pow ( 2 , ( getid3_lib :: LittleEndian2Int ( substr ( $filedata , $filedataoffset , 1 )) & 0xF0 ) >> 4 );
$info [ 'ogg' ][ 'stop_bit' ] = getid3_lib :: LittleEndian2Int ( substr ( $filedata , $filedataoffset , 1 )); // must be 1, marks end of packet
$info [ 'audio' ][ 'bitrate_mode' ] = 'vbr' ; // overridden if actually abr
if ( $info [ 'ogg' ][ 'bitrate_max' ] == 0xFFFFFFFF ) {
unset ( $info [ 'ogg' ][ 'bitrate_max' ]);
$info [ 'audio' ][ 'bitrate_mode' ] = 'abr' ;
}
if ( $info [ 'ogg' ][ 'bitrate_nominal' ] == 0xFFFFFFFF ) {
unset ( $info [ 'ogg' ][ 'bitrate_nominal' ]);
}
if ( $info [ 'ogg' ][ 'bitrate_min' ] == 0xFFFFFFFF ) {
unset ( $info [ 'ogg' ][ 'bitrate_min' ]);
$info [ 'audio' ][ 'bitrate_mode' ] = 'abr' ;
}
return true ;
}
2019-09-14 21:07:57 +02:00
/**
* @ link http :// tools . ietf . org / html / draft - ietf - codec - oggopus - 03
*
* @ param string $filedata
* @ param int $filedataoffset
* @ param array $oggpageinfo
*
* @ return bool
*/
2015-06-28 02:17:25 +02:00
public function ParseOpusPageHeader ( & $filedata , & $filedataoffset , & $oggpageinfo ) {
$info = & $this -> getid3 -> info ;
$info [ 'audio' ][ 'dataformat' ] = 'opus' ;
$info [ 'mime_type' ] = 'audio/ogg; codecs=opus' ;
/** @todo find a usable way to detect abr (vbr that is padded to be abr) */
$info [ 'audio' ][ 'bitrate_mode' ] = 'vbr' ;
$info [ 'audio' ][ 'lossless' ] = false ;
$info [ 'ogg' ][ 'pageheader' ][ 'opus' ][ 'opus_magic' ] = substr ( $filedata , $filedataoffset , 8 ); // hard-coded to 'OpusHead'
$filedataoffset += 8 ;
$info [ 'ogg' ][ 'pageheader' ][ 'opus' ][ 'version' ] = getid3_lib :: LittleEndian2Int ( substr ( $filedata , $filedataoffset , 1 ));
$filedataoffset += 1 ;
if ( $info [ 'ogg' ][ 'pageheader' ][ 'opus' ][ 'version' ] < 1 || $info [ 'ogg' ][ 'pageheader' ][ 'opus' ][ 'version' ] > 15 ) {
2017-07-31 21:50:45 +02:00
$this -> error ( 'Unknown opus version number (only accepting 1-15)' );
2015-06-28 02:17:25 +02:00
return false ;
}
$info [ 'ogg' ][ 'pageheader' ][ 'opus' ][ 'out_channel_count' ] = getid3_lib :: LittleEndian2Int ( substr ( $filedata , $filedataoffset , 1 ));
$filedataoffset += 1 ;
if ( $info [ 'ogg' ][ 'pageheader' ][ 'opus' ][ 'out_channel_count' ] == 0 ) {
2017-07-31 21:50:45 +02:00
$this -> error ( 'Invalid channel count in opus header (must not be zero)' );
2015-06-28 02:17:25 +02:00
return false ;
}
$info [ 'ogg' ][ 'pageheader' ][ 'opus' ][ 'pre_skip' ] = getid3_lib :: LittleEndian2Int ( substr ( $filedata , $filedataoffset , 2 ));
$filedataoffset += 2 ;
2019-09-14 21:07:57 +02:00
$info [ 'ogg' ][ 'pageheader' ][ 'opus' ][ 'input_sample_rate' ] = getid3_lib :: LittleEndian2Int ( substr ( $filedata , $filedataoffset , 4 ));
2015-06-28 02:17:25 +02:00
$filedataoffset += 4 ;
//$info['ogg']['pageheader']['opus']['output_gain'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 2));
//$filedataoffset += 2;
//$info['ogg']['pageheader']['opus']['channel_mapping_family'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1));
//$filedataoffset += 1;
2019-09-14 21:07:57 +02:00
$info [ 'opus' ][ 'opus_version' ] = $info [ 'ogg' ][ 'pageheader' ][ 'opus' ][ 'version' ];
$info [ 'opus' ][ 'sample_rate_input' ] = $info [ 'ogg' ][ 'pageheader' ][ 'opus' ][ 'input_sample_rate' ];
$info [ 'opus' ][ 'out_channel_count' ] = $info [ 'ogg' ][ 'pageheader' ][ 'opus' ][ 'out_channel_count' ];
2015-06-28 02:17:25 +02:00
2019-09-14 21:07:57 +02:00
$info [ 'audio' ][ 'channels' ] = $info [ 'opus' ][ 'out_channel_count' ];
$info [ 'audio' ][ 'sample_rate_input' ] = $info [ 'opus' ][ 'sample_rate_input' ];
$info [ 'audio' ][ 'sample_rate' ] = 48000 ; // "All Opus audio is coded at 48 kHz, and should also be decoded at 48 kHz for playback (unless the target hardware does not support this sampling rate). However, this field may be used to resample the audio back to the original sampling rate, for example, when saving the output to a file." -- https://mf4.xiph.org/jenkins/view/opus/job/opusfile-unix/ws/doc/html/structOpusHead.html
2015-06-28 02:17:25 +02:00
return true ;
}
2019-09-14 21:07:57 +02:00
/**
* @ return array | false
*/
2013-03-21 05:55:42 +01:00
public function ParseOggPageHeader () {
// http://xiph.org/ogg/vorbis/doc/framing.html
2021-11-26 04:06:03 +01:00
$oggheader = array ();
2013-03-21 05:55:42 +01:00
$oggheader [ 'page_start_offset' ] = $this -> ftell (); // where we started from in the file
$filedata = $this -> fread ( $this -> getid3 -> fread_buffer_size ());
$filedataoffset = 0 ;
2023-10-20 15:29:27 +02:00
while ( substr ( $filedata , $filedataoffset ++ , 4 ) != 'OggS' ) {
2013-03-21 05:55:42 +01:00
if (( $this -> ftell () - $oggheader [ 'page_start_offset' ]) >= $this -> getid3 -> fread_buffer_size ()) {
// should be found before here
return false ;
}
2023-10-20 15:29:27 +02:00
if (( $filedataoffset + 28 ) > strlen ( $filedata )) {
2019-09-14 21:07:57 +02:00
if ( $this -> feof () || (( $filedata .= $this -> fread ( $this -> getid3 -> fread_buffer_size ())) === '' )) {
2013-03-21 05:55:42 +01:00
// get some more data, unless eof, in which case fail
return false ;
}
}
}
$filedataoffset += strlen ( 'OggS' ) - 1 ; // page, delimited by 'OggS'
$oggheader [ 'stream_structver' ] = getid3_lib :: LittleEndian2Int ( substr ( $filedata , $filedataoffset , 1 ));
$filedataoffset += 1 ;
$oggheader [ 'flags_raw' ] = getid3_lib :: LittleEndian2Int ( substr ( $filedata , $filedataoffset , 1 ));
$filedataoffset += 1 ;
$oggheader [ 'flags' ][ 'fresh' ] = ( bool ) ( $oggheader [ 'flags_raw' ] & 0x01 ); // fresh packet
$oggheader [ 'flags' ][ 'bos' ] = ( bool ) ( $oggheader [ 'flags_raw' ] & 0x02 ); // first page of logical bitstream (bos)
$oggheader [ 'flags' ][ 'eos' ] = ( bool ) ( $oggheader [ 'flags_raw' ] & 0x04 ); // last page of logical bitstream (eos)
$oggheader [ 'pcm_abs_position' ] = getid3_lib :: LittleEndian2Int ( substr ( $filedata , $filedataoffset , 8 ));
$filedataoffset += 8 ;
$oggheader [ 'stream_serialno' ] = getid3_lib :: LittleEndian2Int ( substr ( $filedata , $filedataoffset , 4 ));
$filedataoffset += 4 ;
$oggheader [ 'page_seqno' ] = getid3_lib :: LittleEndian2Int ( substr ( $filedata , $filedataoffset , 4 ));
$filedataoffset += 4 ;
$oggheader [ 'page_checksum' ] = getid3_lib :: LittleEndian2Int ( substr ( $filedata , $filedataoffset , 4 ));
$filedataoffset += 4 ;
$oggheader [ 'page_segments' ] = getid3_lib :: LittleEndian2Int ( substr ( $filedata , $filedataoffset , 1 ));
$filedataoffset += 1 ;
$oggheader [ 'page_length' ] = 0 ;
for ( $i = 0 ; $i < $oggheader [ 'page_segments' ]; $i ++ ) {
$oggheader [ 'segment_table' ][ $i ] = getid3_lib :: LittleEndian2Int ( substr ( $filedata , $filedataoffset , 1 ));
$filedataoffset += 1 ;
$oggheader [ 'page_length' ] += $oggheader [ 'segment_table' ][ $i ];
}
$oggheader [ 'header_end_offset' ] = $oggheader [ 'page_start_offset' ] + $filedataoffset ;
$oggheader [ 'page_end_offset' ] = $oggheader [ 'header_end_offset' ] + $oggheader [ 'page_length' ];
$this -> fseek ( $oggheader [ 'header_end_offset' ]);
return $oggheader ;
}
2019-09-14 21:07:57 +02:00
/**
* @ link http :// xiph . org / vorbis / doc / Vorbis_I_spec . html #x1-810005
*
* @ return bool
*/
2013-03-21 05:55:42 +01:00
public function ParseVorbisComments () {
$info = & $this -> getid3 -> info ;
$OriginalOffset = $this -> ftell ();
2019-09-14 21:07:57 +02:00
$commentdata = null ;
2013-03-21 05:55:42 +01:00
$commentdataoffset = 0 ;
$VorbisCommentPage = 1 ;
2019-09-14 21:07:57 +02:00
$CommentStartOffset = 0 ;
2013-03-21 05:55:42 +01:00
switch ( $info [ 'audio' ][ 'dataformat' ]) {
case 'vorbis' :
case 'speex' :
2015-06-28 02:17:25 +02:00
case 'opus' :
2013-03-21 05:55:42 +01:00
$CommentStartOffset = $info [ 'ogg' ][ 'pageheader' ][ $VorbisCommentPage ][ 'page_start_offset' ]; // Second Ogg page, after header block
$this -> fseek ( $CommentStartOffset );
$commentdataoffset = 27 + $info [ 'ogg' ][ 'pageheader' ][ $VorbisCommentPage ][ 'page_segments' ];
$commentdata = $this -> fread ( self :: OggPageSegmentLength ( $info [ 'ogg' ][ 'pageheader' ][ $VorbisCommentPage ], 1 ) + $commentdataoffset );
if ( $info [ 'audio' ][ 'dataformat' ] == 'vorbis' ) {
$commentdataoffset += ( strlen ( 'vorbis' ) + 1 );
}
2015-06-28 02:17:25 +02:00
else if ( $info [ 'audio' ][ 'dataformat' ] == 'opus' ) {
$commentdataoffset += strlen ( 'OpusTags' );
}
2013-03-21 05:55:42 +01:00
break ;
case 'flac' :
$CommentStartOffset = $info [ 'flac' ][ 'VORBIS_COMMENT' ][ 'raw' ][ 'offset' ] + 4 ;
$this -> fseek ( $CommentStartOffset );
$commentdata = $this -> fread ( $info [ 'flac' ][ 'VORBIS_COMMENT' ][ 'raw' ][ 'block_length' ]);
break ;
default :
return false ;
}
$VendorSize = getid3_lib :: LittleEndian2Int ( substr ( $commentdata , $commentdataoffset , 4 ));
$commentdataoffset += 4 ;
$info [ 'ogg' ][ 'vendor' ] = substr ( $commentdata , $commentdataoffset , $VendorSize );
$commentdataoffset += $VendorSize ;
$CommentsCount = getid3_lib :: LittleEndian2Int ( substr ( $commentdata , $commentdataoffset , 4 ));
$commentdataoffset += 4 ;
$info [ 'avdataoffset' ] = $CommentStartOffset + $commentdataoffset ;
$basicfields = array ( 'TITLE' , 'ARTIST' , 'ALBUM' , 'TRACKNUMBER' , 'GENRE' , 'DATE' , 'DESCRIPTION' , 'COMMENT' );
$ThisFileInfo_ogg_comments_raw = & $info [ 'ogg' ][ 'comments_raw' ];
for ( $i = 0 ; $i < $CommentsCount ; $i ++ ) {
2015-06-28 02:17:25 +02:00
if ( $i >= 10000 ) {
// https://github.com/owncloud/music/issues/212#issuecomment-43082336
2017-07-31 21:50:45 +02:00
$this -> warning ( 'Unexpectedly large number (' . $CommentsCount . ') of Ogg comments - breaking after reading ' . $i . ' comments' );
2015-06-28 02:17:25 +02:00
break ;
}
2013-03-21 05:55:42 +01:00
$ThisFileInfo_ogg_comments_raw [ $i ][ 'dataoffset' ] = $CommentStartOffset + $commentdataoffset ;
if ( $this -> ftell () < ( $ThisFileInfo_ogg_comments_raw [ $i ][ 'dataoffset' ] + 4 )) {
if ( $oggpageinfo = $this -> ParseOggPageHeader ()) {
$info [ 'ogg' ][ 'pageheader' ][ $oggpageinfo [ 'page_seqno' ]] = $oggpageinfo ;
$VorbisCommentPage ++ ;
// First, save what we haven't read yet
$AsYetUnusedData = substr ( $commentdata , $commentdataoffset );
// Then take that data off the end
$commentdata = substr ( $commentdata , 0 , $commentdataoffset );
// Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct
$commentdata .= str_repeat ( " \x00 " , 27 + $info [ 'ogg' ][ 'pageheader' ][ $oggpageinfo [ 'page_seqno' ]][ 'page_segments' ]);
$commentdataoffset += ( 27 + $info [ 'ogg' ][ 'pageheader' ][ $oggpageinfo [ 'page_seqno' ]][ 'page_segments' ]);
// Finally, stick the unused data back on the end
$commentdata .= $AsYetUnusedData ;
//$commentdata .= $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);
$commentdata .= $this -> fread ( $this -> OggPageSegmentLength ( $info [ 'ogg' ][ 'pageheader' ][ $VorbisCommentPage ], 1 ));
}
}
$ThisFileInfo_ogg_comments_raw [ $i ][ 'size' ] = getid3_lib :: LittleEndian2Int ( substr ( $commentdata , $commentdataoffset , 4 ));
// replace avdataoffset with position just after the last vorbiscomment
$info [ 'avdataoffset' ] = $ThisFileInfo_ogg_comments_raw [ $i ][ 'dataoffset' ] + $ThisFileInfo_ogg_comments_raw [ $i ][ 'size' ] + 4 ;
$commentdataoffset += 4 ;
while (( strlen ( $commentdata ) - $commentdataoffset ) < $ThisFileInfo_ogg_comments_raw [ $i ][ 'size' ]) {
if (( $ThisFileInfo_ogg_comments_raw [ $i ][ 'size' ] > $info [ 'avdataend' ]) || ( $ThisFileInfo_ogg_comments_raw [ $i ][ 'size' ] < 0 )) {
2017-07-31 21:50:45 +02:00
$this -> warning ( 'Invalid Ogg comment size (comment #' . $i . ', claims to be ' . number_format ( $ThisFileInfo_ogg_comments_raw [ $i ][ 'size' ]) . ' bytes) - aborting reading comments' );
2013-03-21 05:55:42 +01:00
break 2 ;
}
$VorbisCommentPage ++ ;
2021-11-26 04:06:03 +01:00
if ( $oggpageinfo = $this -> ParseOggPageHeader ()) {
$info [ 'ogg' ][ 'pageheader' ][ $oggpageinfo [ 'page_seqno' ]] = $oggpageinfo ;
// First, save what we haven't read yet
$AsYetUnusedData = substr ( $commentdata , $commentdataoffset );
2013-03-21 05:55:42 +01:00
2021-11-26 04:06:03 +01:00
// Then take that data off the end
$commentdata = substr ( $commentdata , 0 , $commentdataoffset );
2013-03-21 05:55:42 +01:00
2021-11-26 04:06:03 +01:00
// Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct
$commentdata .= str_repeat ( " \x00 " , 27 + $info [ 'ogg' ][ 'pageheader' ][ $oggpageinfo [ 'page_seqno' ]][ 'page_segments' ]);
$commentdataoffset += ( 27 + $info [ 'ogg' ][ 'pageheader' ][ $oggpageinfo [ 'page_seqno' ]][ 'page_segments' ]);
2013-03-21 05:55:42 +01:00
2021-11-26 04:06:03 +01:00
// Finally, stick the unused data back on the end
$commentdata .= $AsYetUnusedData ;
2013-03-21 05:55:42 +01:00
2021-11-26 04:06:03 +01:00
//$commentdata .= $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']);
if ( ! isset ( $info [ 'ogg' ][ 'pageheader' ][ $VorbisCommentPage ])) {
$this -> warning ( 'undefined Vorbis Comment page "' . $VorbisCommentPage . '" at offset ' . $this -> ftell ());
break ;
}
$readlength = self :: OggPageSegmentLength ( $info [ 'ogg' ][ 'pageheader' ][ $VorbisCommentPage ], 1 );
if ( $readlength <= 0 ) {
$this -> warning ( 'invalid length Vorbis Comment page "' . $VorbisCommentPage . '" at offset ' . $this -> ftell ());
break ;
}
$commentdata .= $this -> fread ( $readlength );
2013-03-21 05:55:42 +01:00
2021-11-26 04:06:03 +01:00
//$filebaseoffset += $oggpageinfo['header_end_offset'] - $oggpageinfo['page_start_offset'];
} else {
$this -> warning ( 'failed to ParseOggPageHeader() at offset ' . $this -> ftell ());
2021-10-10 03:16:57 +02:00
break ;
}
2013-03-21 05:55:42 +01:00
}
$ThisFileInfo_ogg_comments_raw [ $i ][ 'offset' ] = $commentdataoffset ;
$commentstring = substr ( $commentdata , $commentdataoffset , $ThisFileInfo_ogg_comments_raw [ $i ][ 'size' ]);
$commentdataoffset += $ThisFileInfo_ogg_comments_raw [ $i ][ 'size' ];
if ( ! $commentstring ) {
// no comment?
2017-07-31 21:50:45 +02:00
$this -> warning ( 'Blank Ogg comment [' . $i . ']' );
2013-03-21 05:55:42 +01:00
} elseif ( strstr ( $commentstring , '=' )) {
$commentexploded = explode ( '=' , $commentstring , 2 );
$ThisFileInfo_ogg_comments_raw [ $i ][ 'key' ] = strtoupper ( $commentexploded [ 0 ]);
$ThisFileInfo_ogg_comments_raw [ $i ][ 'value' ] = ( isset ( $commentexploded [ 1 ]) ? $commentexploded [ 1 ] : '' );
if ( $ThisFileInfo_ogg_comments_raw [ $i ][ 'key' ] == 'METADATA_BLOCK_PICTURE' ) {
// http://wiki.xiph.org/VorbisComment#METADATA_BLOCK_PICTURE
// The unencoded format is that of the FLAC picture block. The fields are stored in big endian order as in FLAC, picture data is stored according to the relevant standard.
// http://flac.sourceforge.net/format.html#metadata_block_picture
$flac = new getid3_flac ( $this -> getid3 );
$flac -> setStringMode ( base64_decode ( $ThisFileInfo_ogg_comments_raw [ $i ][ 'value' ]));
$flac -> parsePICTURE ();
$info [ 'ogg' ][ 'comments' ][ 'picture' ][] = $flac -> getid3 -> info [ 'flac' ][ 'PICTURE' ][ 0 ];
unset ( $flac );
} elseif ( $ThisFileInfo_ogg_comments_raw [ $i ][ 'key' ] == 'COVERART' ) {
$data = base64_decode ( $ThisFileInfo_ogg_comments_raw [ $i ][ 'value' ]);
$this -> notice ( 'Found deprecated COVERART tag, it should be replaced in honor of METADATA_BLOCK_PICTURE structure' );
/** @todo use 'coverartmime' where available */
$imageinfo = getid3_lib :: GetDataImageSize ( $data );
if ( $imageinfo === false || ! isset ( $imageinfo [ 'mime' ])) {
$this -> warning ( 'COVERART vorbiscomment tag contains invalid image' );
continue ;
}
$ogg = new self ( $this -> getid3 );
$ogg -> setStringMode ( $data );
$info [ 'ogg' ][ 'comments' ][ 'picture' ][] = array (
2015-06-28 02:17:25 +02:00
'image_mime' => $imageinfo [ 'mime' ],
'datalength' => strlen ( $data ),
'picturetype' => 'cover art' ,
'image_height' => $imageinfo [ 'height' ],
'image_width' => $imageinfo [ 'width' ],
'data' => $ogg -> saveAttachment ( 'coverart' , 0 , strlen ( $data ), $imageinfo [ 'mime' ]),
2013-03-21 05:55:42 +01:00
);
unset ( $ogg );
} else {
$info [ 'ogg' ][ 'comments' ][ strtolower ( $ThisFileInfo_ogg_comments_raw [ $i ][ 'key' ])][] = $ThisFileInfo_ogg_comments_raw [ $i ][ 'value' ];
}
} else {
2017-07-31 21:50:45 +02:00
$this -> warning ( '[known problem with CDex >= v1.40, < v1.50b7] Invalid Ogg comment name/value pair [' . $i . ']: ' . $commentstring );
2013-03-21 05:55:42 +01:00
}
unset ( $ThisFileInfo_ogg_comments_raw [ $i ]);
}
unset ( $ThisFileInfo_ogg_comments_raw );
// Replay Gain Adjustment
// http://privatewww.essex.ac.uk/~djmrob/replaygain/
if ( isset ( $info [ 'ogg' ][ 'comments' ]) && is_array ( $info [ 'ogg' ][ 'comments' ])) {
foreach ( $info [ 'ogg' ][ 'comments' ] as $index => $commentvalue ) {
switch ( $index ) {
case 'rg_audiophile' :
case 'replaygain_album_gain' :
$info [ 'replay_gain' ][ 'album' ][ 'adjustment' ] = ( double ) $commentvalue [ 0 ];
unset ( $info [ 'ogg' ][ 'comments' ][ $index ]);
break ;
case 'rg_radio' :
case 'replaygain_track_gain' :
$info [ 'replay_gain' ][ 'track' ][ 'adjustment' ] = ( double ) $commentvalue [ 0 ];
unset ( $info [ 'ogg' ][ 'comments' ][ $index ]);
break ;
case 'replaygain_album_peak' :
$info [ 'replay_gain' ][ 'album' ][ 'peak' ] = ( double ) $commentvalue [ 0 ];
unset ( $info [ 'ogg' ][ 'comments' ][ $index ]);
break ;
case 'rg_peak' :
case 'replaygain_track_peak' :
$info [ 'replay_gain' ][ 'track' ][ 'peak' ] = ( double ) $commentvalue [ 0 ];
unset ( $info [ 'ogg' ][ 'comments' ][ $index ]);
break ;
case 'replaygain_reference_loudness' :
$info [ 'replay_gain' ][ 'reference_volume' ] = ( double ) $commentvalue [ 0 ];
unset ( $info [ 'ogg' ][ 'comments' ][ $index ]);
break ;
default :
// do nothing
break ;
}
}
}
$this -> fseek ( $OriginalOffset );
return true ;
}
2019-09-14 21:07:57 +02:00
/**
* @ param int $mode
*
* @ return string | null
*/
2013-03-21 05:55:42 +01:00
public static function SpeexBandModeLookup ( $mode ) {
static $SpeexBandModeLookup = array ();
if ( empty ( $SpeexBandModeLookup )) {
$SpeexBandModeLookup [ 0 ] = 'narrow' ;
$SpeexBandModeLookup [ 1 ] = 'wide' ;
$SpeexBandModeLookup [ 2 ] = 'ultra-wide' ;
}
return ( isset ( $SpeexBandModeLookup [ $mode ]) ? $SpeexBandModeLookup [ $mode ] : null );
}
2019-09-14 21:07:57 +02:00
/**
* @ param array $OggInfoArray
* @ param int $SegmentNumber
*
* @ return int
*/
2013-03-21 05:55:42 +01:00
public static function OggPageSegmentLength ( $OggInfoArray , $SegmentNumber = 1 ) {
2019-09-14 21:07:57 +02:00
$segmentlength = 0 ;
2013-03-21 05:55:42 +01:00
for ( $i = 0 ; $i < $SegmentNumber ; $i ++ ) {
$segmentlength = 0 ;
foreach ( $OggInfoArray [ 'segment_table' ] as $key => $value ) {
$segmentlength += $value ;
if ( $value < 255 ) {
break ;
}
}
}
return $segmentlength ;
}
2019-09-14 21:07:57 +02:00
/**
* @ param int $nominal_bitrate
*
* @ return float
*/
2013-03-21 05:55:42 +01:00
public static function get_quality_from_nominal_bitrate ( $nominal_bitrate ) {
// decrease precision
$nominal_bitrate = $nominal_bitrate / 1000 ;
if ( $nominal_bitrate < 128 ) {
// q-1 to q4
$qval = ( $nominal_bitrate - 64 ) / 16 ;
} elseif ( $nominal_bitrate < 256 ) {
// q4 to q8
$qval = $nominal_bitrate / 32 ;
} elseif ( $nominal_bitrate < 320 ) {
// q8 to q9
$qval = ( $nominal_bitrate + 256 ) / 64 ;
} else {
// q9 to q10
$qval = ( $nominal_bitrate + 1300 ) / 180 ;
}
//return $qval; // 5.031324
//return intval($qval); // 5
return round ( $qval , 1 ); // 5 or 4.9
}
2019-09-14 21:07:57 +02:00
/**
* @ param int $colorspace_id
*
* @ return string | null
*/
2014-09-11 21:07:17 +02:00
public static function TheoraColorSpace ( $colorspace_id ) {
// http://www.theora.org/doc/Theora.pdf (table 6.3)
static $TheoraColorSpaceLookup = array ();
if ( empty ( $TheoraColorSpaceLookup )) {
$TheoraColorSpaceLookup [ 0 ] = 'Undefined' ;
$TheoraColorSpaceLookup [ 1 ] = 'Rec. 470M' ;
$TheoraColorSpaceLookup [ 2 ] = 'Rec. 470BG' ;
$TheoraColorSpaceLookup [ 3 ] = 'Reserved' ;
}
return ( isset ( $TheoraColorSpaceLookup [ $colorspace_id ]) ? $TheoraColorSpaceLookup [ $colorspace_id ] : null );
}
2019-09-14 21:07:57 +02:00
/**
* @ param int $pixelformat_id
*
* @ return string | null
*/
2014-09-11 21:07:17 +02:00
public static function TheoraPixelFormat ( $pixelformat_id ) {
// http://www.theora.org/doc/Theora.pdf (table 6.4)
static $TheoraPixelFormatLookup = array ();
if ( empty ( $TheoraPixelFormatLookup )) {
$TheoraPixelFormatLookup [ 0 ] = '4:2:0' ;
$TheoraPixelFormatLookup [ 1 ] = 'Reserved' ;
$TheoraPixelFormatLookup [ 2 ] = '4:2:2' ;
$TheoraPixelFormatLookup [ 3 ] = '4:4:4' ;
}
return ( isset ( $TheoraPixelFormatLookup [ $pixelformat_id ]) ? $TheoraPixelFormatLookup [ $pixelformat_id ] : null );
}
2015-06-28 02:17:25 +02:00
}