Skip to content
Extraits de code Groupes Projets

Comparer les révisions

Les modifications sont affichées comme si la révision source était fusionnée avec la révision cible. En savoir plus sur la comparaison des révisions.

Source

Sélectionner le projet cible
No results found

Cible

Sélectionner le projet cible
  • spip/medias
  • pierre.laszczak/medias
  • cy.altern/medias
  • MathieuAlphamosa/medias
  • taffit/medias
  • RealET/medias
  • johan/medias
  • Yohooo/medias
  • pierretux/medias
  • placido/medias
  • JLuc/medias
11 résultats
Afficher les modifications
Affichage de
avec 0 ajout et 7778 suppressions
<?php
/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org> //
// available at https://github.com/JamesHeinrich/getID3 //
// or https://www.getid3.org //
// or http://getid3.sourceforge.net //
// see readme.txt for more details //
/////////////////////////////////////////////////////////////////
// //
// module.audio.dsdiff.php //
// module for analyzing Direct Stream Digital Interchange //
// File Format (DSDIFF) files //
// dependencies: NONE //
// ///
/////////////////////////////////////////////////////////////////
if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
exit;
}
class getid3_dsdiff extends getid3_handler
{
/**
* @return bool
*/
public function Analyze() {
$info = &$this->getid3->info;
$this->fseek($info['avdataoffset']);
$DSDIFFheader = $this->fread(4);
// https://dsd-guide.com/sites/default/files/white-papers/DSDIFF_1.5_Spec.pdf
if (substr($DSDIFFheader, 0, 4) != 'FRM8') {
$this->error('Expecting "FRM8" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($DSDIFFheader, 0, 4)).'"');
return false;
}
unset($DSDIFFheader);
$this->fseek($info['avdataoffset']);
$info['encoding'] = 'ISO-8859-1'; // not certain, but assumed
$info['fileformat'] = 'dsdiff';
$info['mime_type'] = 'audio/dsd';
$info['audio']['dataformat'] = 'dsdiff';
$info['audio']['bitrate_mode'] = 'cbr';
$info['audio']['bits_per_sample'] = 1;
$info['dsdiff'] = array();
while (!$this->feof() && ($ChunkHeader = $this->fread(12))) {
if (strlen($ChunkHeader) < 12) {
$this->error('Expecting chunk header at offset '.$thisChunk['offset'].', found insufficient data in file, aborting parsing');
break;
}
$thisChunk = array();
$thisChunk['offset'] = $this->ftell() - 12;
$thisChunk['name'] = substr($ChunkHeader, 0, 4);
if (!preg_match('#^[\\x21-\\x7E]+ *$#', $thisChunk['name'])) {
// "a concatenation of four printable ASCII characters in the range ' ' (space, 0x20) through '~'(0x7E). Space (0x20) cannot precede printing characters; trailing spaces are allowed."
$this->error('Invalid chunk name "'.$thisChunk['name'].'" ('.getid3_lib::PrintHexBytes($thisChunk['name']).') at offset '.$thisChunk['offset'].', aborting parsing');
}
$thisChunk['size'] = getid3_lib::BigEndian2Int(substr($ChunkHeader, 4, 8));
$datasize = $thisChunk['size'] + ($thisChunk['size'] % 2); // "If the data is an odd number of bytes in length, a pad byte must be added at the end. The pad byte is not included in ckDataSize."
switch ($thisChunk['name']) {
case 'FRM8':
$thisChunk['form_type'] = $this->fread(4);
if ($thisChunk['form_type'] != 'DSD ') {
$this->error('Expecting "DSD " at offset '.($this->ftell() - 4).', found "'.getid3_lib::PrintHexBytes($thisChunk['form_type']).'", aborting parsing');
break 2;
}
// do nothing further, prevent skipping subchunks
break;
case 'PROP': // PROPerty chunk
$thisChunk['prop_type'] = $this->fread(4);
if ($thisChunk['prop_type'] != 'SND ') {
$this->error('Expecting "SND " at offset '.($this->ftell() - 4).', found "'.getid3_lib::PrintHexBytes($thisChunk['prop_type']).'", aborting parsing');
break 2;
}
// do nothing further, prevent skipping subchunks
break;
case 'DIIN': // eDIted master INformation chunk
// do nothing, just prevent skipping subchunks
break;
case 'FVER': // Format VERsion chunk
if ($thisChunk['size'] == 4) {
$FVER = $this->fread(4);
$info['dsdiff']['format_version'] = ord($FVER[0]).'.'.ord($FVER[1]).'.'.ord($FVER[2]).'.'.ord($FVER[3]);
unset($FVER);
} else {
$this->warning('Expecting "FVER" chunk to be 4 bytes, found '.$thisChunk['size'].' bytes, skipping chunk');
$this->fseek($datasize, SEEK_CUR);
}
break;
case 'FS ': // sample rate chunk
if ($thisChunk['size'] == 4) {
$info['dsdiff']['sample_rate'] = getid3_lib::BigEndian2Int($this->fread(4));
$info['audio']['sample_rate'] = $info['dsdiff']['sample_rate'];
} else {
$this->warning('Expecting "FVER" chunk to be 4 bytes, found '.$thisChunk['size'].' bytes, skipping chunk');
$this->fseek($datasize, SEEK_CUR);
}
break;
case 'CHNL': // CHaNneLs chunk
$thisChunk['num_channels'] = getid3_lib::BigEndian2Int($this->fread(2));
if ($thisChunk['num_channels'] == 0) {
$this->warning('channel count should be greater than zero, skipping chunk');
$this->fseek($datasize - 2, SEEK_CUR);
}
for ($i = 0; $i < $thisChunk['num_channels']; $i++) {
$thisChunk['channels'][$i] = $this->fread(4);
}
$info['audio']['channels'] = $thisChunk['num_channels'];
break;
case 'CMPR': // CoMPRession type chunk
$thisChunk['compression_type'] = $this->fread(4);
$info['audio']['dataformat'] = trim($thisChunk['compression_type']);
$humanReadableByteLength = getid3_lib::BigEndian2Int($this->fread(1));
$thisChunk['compression_name'] = $this->fread($humanReadableByteLength);
if (($humanReadableByteLength % 2) == 0) {
// need to seek to multiple of 2 bytes, human-readable string length is only one byte long so if the string is an even number of bytes we need to seek past a padding byte after the string
$this->fseek(1, SEEK_CUR);
}
unset($humanReadableByteLength);
break;
case 'ABSS': // ABSolute Start time chunk
$ABSS = $this->fread(8);
$info['dsdiff']['absolute_start_time']['hours'] = getid3_lib::BigEndian2Int(substr($ABSS, 0, 2));
$info['dsdiff']['absolute_start_time']['minutes'] = getid3_lib::BigEndian2Int(substr($ABSS, 2, 1));
$info['dsdiff']['absolute_start_time']['seconds'] = getid3_lib::BigEndian2Int(substr($ABSS, 3, 1));
$info['dsdiff']['absolute_start_time']['samples'] = getid3_lib::BigEndian2Int(substr($ABSS, 4, 4));
unset($ABSS);
break;
case 'LSCO': // LoudSpeaker COnfiguration chunk
// 0 = 2-channel stereo set-up
// 3 = 5-channel set-up according to ITU-R BS.775-1 [ITU]
// 4 = 6-channel set-up, 5-channel set-up according to ITU-R BS.775-1 [ITU], plus additional Low Frequency Enhancement (LFE) loudspeaker. Also known as "5.1 configuration"
// 65535 = Undefined channel set-up
$thisChunk['loundspeaker_config_id'] = getid3_lib::BigEndian2Int($this->fread(2));
break;
case 'COMT': // COMmenTs chunk
$thisChunk['num_comments'] = getid3_lib::BigEndian2Int($this->fread(2));
for ($i = 0; $i < $thisChunk['num_comments']; $i++) {
$thisComment = array();
$COMT = $this->fread(14);
$thisComment['creation_year'] = getid3_lib::BigEndian2Int(substr($COMT, 0, 2));
$thisComment['creation_month'] = getid3_lib::BigEndian2Int(substr($COMT, 2, 1));
$thisComment['creation_day'] = getid3_lib::BigEndian2Int(substr($COMT, 3, 1));
$thisComment['creation_hour'] = getid3_lib::BigEndian2Int(substr($COMT, 4, 1));
$thisComment['creation_minute'] = getid3_lib::BigEndian2Int(substr($COMT, 5, 1));
$thisComment['comment_type_id'] = getid3_lib::BigEndian2Int(substr($COMT, 6, 2));
$thisComment['comment_ref_id'] = getid3_lib::BigEndian2Int(substr($COMT, 8, 2));
$thisComment['string_length'] = getid3_lib::BigEndian2Int(substr($COMT, 10, 4));
$thisComment['comment_text'] = $this->fread($thisComment['string_length']);
if ($thisComment['string_length'] % 2) {
// commentText[] is the description of the Comment. This text must be padded with a byte at the end, if needed, to make it an even number of bytes long. This pad byte, if present, is not included in count.
$this->fseek(1, SEEK_CUR);
}
$thisComment['comment_type'] = $this->DSDIFFcmtType($thisComment['comment_type_id']);
$thisComment['comment_reference'] = $this->DSDIFFcmtRef($thisComment['comment_type_id'], $thisComment['comment_ref_id']);
$thisComment['creation_unix'] = mktime($thisComment['creation_hour'], $thisComment['creation_minute'], 0, $thisComment['creation_month'], $thisComment['creation_day'], $thisComment['creation_year']);
$thisChunk['comments'][$i] = $thisComment;
$commentkey = ($thisComment['comment_reference'] ?: 'comment');
$info['dsdiff']['comments'][$commentkey][] = $thisComment['comment_text'];
unset($thisComment);
}
break;
case 'MARK': // MARKer chunk
$MARK = $this->fread(22);
$thisChunk['marker_hours'] = getid3_lib::BigEndian2Int(substr($MARK, 0, 2));
$thisChunk['marker_minutes'] = getid3_lib::BigEndian2Int(substr($MARK, 2, 1));
$thisChunk['marker_seconds'] = getid3_lib::BigEndian2Int(substr($MARK, 3, 1));
$thisChunk['marker_samples'] = getid3_lib::BigEndian2Int(substr($MARK, 4, 4));
$thisChunk['marker_offset'] = getid3_lib::BigEndian2Int(substr($MARK, 8, 4));
$thisChunk['marker_type_id'] = getid3_lib::BigEndian2Int(substr($MARK, 12, 2));
$thisChunk['marker_channel'] = getid3_lib::BigEndian2Int(substr($MARK, 14, 2));
$thisChunk['marker_flagraw'] = getid3_lib::BigEndian2Int(substr($MARK, 16, 2));
$thisChunk['string_length'] = getid3_lib::BigEndian2Int(substr($MARK, 18, 4));
$thisChunk['description'] = ($thisChunk['string_length'] ? $this->fread($thisChunk['string_length']) : '');
if ($thisChunk['string_length'] % 2) {
// markerText[] is the description of the marker. This text must be padded with a byte at the end, if needed, to make it an even number of bytes long. This pad byte, if present, is not included in count.
$this->fseek(1, SEEK_CUR);
}
$thisChunk['marker_type'] = $this->DSDIFFmarkType($thisChunk['marker_type_id']);
unset($MARK);
break;
case 'DIAR': // artist chunk
case 'DITI': // title chunk
$thisChunk['string_length'] = getid3_lib::BigEndian2Int($this->fread(4));
$thisChunk['description'] = ($thisChunk['string_length'] ? $this->fread($thisChunk['string_length']) : '');
if ($thisChunk['string_length'] % 2) {
// This text must be padded with a byte at the end, if needed, to make it an even number of bytes long. This pad byte, if present, is not included in count.
$this->fseek(1, SEEK_CUR);
}
if ($commentkey = (($thisChunk['name'] == 'DIAR') ? 'artist' : (($thisChunk['name'] == 'DITI') ? 'title' : ''))) {
@$info['dsdiff']['comments'][$commentkey][] = $thisChunk['description'];
}
break;
case 'EMID': // Edited Master ID chunk
if ($thisChunk['size']) {
$thisChunk['identifier'] = $this->fread($thisChunk['size']);
}
break;
case 'ID3 ':
$endOfID3v2 = $this->ftell() + $datasize; // we will need to reset the filepointer after parsing ID3v2
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v2.php', __FILE__, true);
$getid3_temp = new getID3();
$getid3_temp->openfile($this->getid3->filename, null, $this->getid3->fp);
$getid3_id3v2 = new getid3_id3v2($getid3_temp);
$getid3_id3v2->StartingOffset = $this->ftell();
if ($thisChunk['valid'] = $getid3_id3v2->Analyze()) {
$info['id3v2'] = $getid3_temp->info['id3v2'];
}
unset($getid3_temp, $getid3_id3v2);
$this->fseek($endOfID3v2);
break;
case 'DSD ': // DSD sound data chunk
case 'DST ': // DST sound data chunk
// actual audio data, we're not interested, skip
$this->fseek($datasize, SEEK_CUR);
break;
default:
$this->warning('Unhandled chunk "'.$thisChunk['name'].'"');
$this->fseek($datasize, SEEK_CUR);
break;
}
@$info['dsdiff']['chunks'][] = $thisChunk;
//break;
}
if (empty($info['audio']['bitrate']) && !empty($info['audio']['channels']) && !empty($info['audio']['sample_rate']) && !empty($info['audio']['bits_per_sample'])) {
$info['audio']['bitrate'] = $info['audio']['bits_per_sample'] * $info['audio']['sample_rate'] * $info['audio']['channels'];
}
return true;
}
/**
* @param int $cmtType
*
* @return string
*/
public static function DSDIFFcmtType($cmtType) {
static $DSDIFFcmtType = array(
0 => 'General (album) Comment',
1 => 'Channel Comment',
2 => 'Sound Source',
3 => 'File History',
);
return (isset($DSDIFFcmtType[$cmtType]) ? $DSDIFFcmtType[$cmtType] : 'reserved');
}
/**
* @param int $cmtType
* @param int $cmtRef
*
* @return string
*/
public static function DSDIFFcmtRef($cmtType, $cmtRef) {
static $DSDIFFcmtRef = array(
2 => array( // Sound Source
0 => 'DSD recording',
1 => 'Analogue recording',
2 => 'PCM recording',
),
3 => array( // File History
0 => 'comment', // General Remark
1 => 'encodeby', // Name of the operator
2 => 'encoder', // Name or type of the creating machine
3 => 'timezone', // Time zone information
4 => 'revision', // Revision of the file
),
);
switch ($cmtType) {
case 0:
// If the comment type is General Comment the comment reference must be 0
return '';
case 1:
// If the comment type is Channel Comment, the comment reference defines the channel number to which the comment belongs
return ($cmtRef ? 'channel '.$cmtRef : 'all channels');
case 2:
case 3:
return (isset($DSDIFFcmtRef[$cmtType][$cmtRef]) ? $DSDIFFcmtRef[$cmtType][$cmtRef] : 'reserved');
}
return 'unsupported $cmtType='.$cmtType;
}
/**
* @param int $cmtType
*
* @return string
*/
public static function DSDIFFmarkType($markType) {
static $DSDIFFmarkType = array(
0 => 'TrackStart', // Entry point for a Track start
1 => 'TrackStop', // Entry point for ending a Track
2 => 'ProgramStart', // Start point of 2-channel or multi-channel area
3 => 'Obsolete', //
4 => 'Index', // Entry point of an Index
);
return (isset($DSDIFFmarkType[$markType]) ? $DSDIFFmarkType[$markType] : 'reserved');
}
}
<?php
/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org> //
// available at https://github.com/JamesHeinrich/getID3 //
// or https://www.getid3.org //
// or http://getid3.sourceforge.net //
// see readme.txt for more details //
/////////////////////////////////////////////////////////////////
// //
// module.audio.dsf.php //
// module for analyzing dsf/DSF Audio files //
// dependencies: module.tag.id3v2.php //
// ///
/////////////////////////////////////////////////////////////////
if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
exit;
}
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v2.php', __FILE__, true);
class getid3_dsf extends getid3_handler
{
/**
* @return bool
*/
public function Analyze() {
$info = &$this->getid3->info;
$info['fileformat'] = 'dsf';
$info['audio']['dataformat'] = 'dsf';
$info['audio']['lossless'] = true;
$info['audio']['bitrate_mode'] = 'cbr';
$this->fseek($info['avdataoffset']);
$dsfheader = $this->fread(28 + 12);
$headeroffset = 0;
$info['dsf']['dsd']['magic'] = substr($dsfheader, $headeroffset, 4);
$headeroffset += 4;
$magic = 'DSD ';
if ($info['dsf']['dsd']['magic'] != $magic) {
$this->error('Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($info['dsf']['dsd']['magic']).'"');
unset($info['fileformat']);
unset($info['audio']);
unset($info['dsf']);
return false;
}
$info['dsf']['dsd']['dsd_chunk_size'] = getid3_lib::LittleEndian2Int(substr($dsfheader, $headeroffset, 8)); // should be 28
$headeroffset += 8;
$info['dsf']['dsd']['dsf_file_size'] = getid3_lib::LittleEndian2Int(substr($dsfheader, $headeroffset, 8));
$headeroffset += 8;
$info['dsf']['dsd']['meta_chunk_offset'] = getid3_lib::LittleEndian2Int(substr($dsfheader, $headeroffset, 8));
$headeroffset += 8;
$info['dsf']['fmt']['magic'] = substr($dsfheader, $headeroffset, 4);
$headeroffset += 4;
$magic = 'fmt ';
if ($info['dsf']['fmt']['magic'] != $magic) {
$this->error('Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$headeroffset.', found "'.getid3_lib::PrintHexBytes($info['dsf']['fmt']['magic']).'"');
return false;
}
$info['dsf']['fmt']['fmt_chunk_size'] = getid3_lib::LittleEndian2Int(substr($dsfheader, $headeroffset, 8)); // usually 52 bytes
$headeroffset += 8;
$dsfheader .= $this->fread($info['dsf']['fmt']['fmt_chunk_size'] - 12 + 12); // we have already read the entire DSD chunk, plus 12 bytes of FMT. We now want to read the size of FMT, plus 12 bytes into the next chunk to get magic and size.
if (strlen($dsfheader) != ($info['dsf']['dsd']['dsd_chunk_size'] + $info['dsf']['fmt']['fmt_chunk_size'] + 12)) {
$this->error('Expecting '.($info['dsf']['dsd']['dsd_chunk_size'] + $info['dsf']['fmt']['fmt_chunk_size']).' bytes header, found '.strlen($dsfheader).' bytes');
return false;
}
$info['dsf']['fmt']['format_version'] = getid3_lib::LittleEndian2Int(substr($dsfheader, $headeroffset, 4)); // usually "1"
$headeroffset += 4;
$info['dsf']['fmt']['format_id'] = getid3_lib::LittleEndian2Int(substr($dsfheader, $headeroffset, 4)); // usually "0" = "DSD Raw"
$headeroffset += 4;
$info['dsf']['fmt']['channel_type_id'] = getid3_lib::LittleEndian2Int(substr($dsfheader, $headeroffset, 4));
$headeroffset += 4;
$info['dsf']['fmt']['channels'] = getid3_lib::LittleEndian2Int(substr($dsfheader, $headeroffset, 4));
$headeroffset += 4;
$info['dsf']['fmt']['sample_rate'] = getid3_lib::LittleEndian2Int(substr($dsfheader, $headeroffset, 4));
$headeroffset += 4;
$info['dsf']['fmt']['bits_per_sample'] = getid3_lib::LittleEndian2Int(substr($dsfheader, $headeroffset, 4));
$headeroffset += 4;
$info['dsf']['fmt']['sample_count'] = getid3_lib::LittleEndian2Int(substr($dsfheader, $headeroffset, 8));
$headeroffset += 8;
$info['dsf']['fmt']['channel_block_size'] = getid3_lib::LittleEndian2Int(substr($dsfheader, $headeroffset, 4));
$headeroffset += 4;
$info['dsf']['fmt']['reserved'] = getid3_lib::LittleEndian2Int(substr($dsfheader, $headeroffset, 4)); // zero-filled
$headeroffset += 4;
$info['dsf']['data']['magic'] = substr($dsfheader, $headeroffset, 4);
$headeroffset += 4;
$magic = 'data';
if ($info['dsf']['data']['magic'] != $magic) {
$this->error('Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$headeroffset.', found "'.getid3_lib::PrintHexBytes($info['dsf']['data']['magic']).'"');
return false;
}
$info['dsf']['data']['data_chunk_size'] = getid3_lib::LittleEndian2Int(substr($dsfheader, $headeroffset, 8));
$headeroffset += 8;
$info['avdataoffset'] = $headeroffset;
$info['avdataend'] = $info['avdataoffset'] + $info['dsf']['data']['data_chunk_size'];
if ($info['dsf']['dsd']['meta_chunk_offset'] > 0) {
$getid3_id3v2 = new getid3_id3v2($this->getid3);
$getid3_id3v2->StartingOffset = $info['dsf']['dsd']['meta_chunk_offset'];
$getid3_id3v2->Analyze();
unset($getid3_id3v2);
}
$info['dsf']['fmt']['channel_type'] = $this->DSFchannelTypeLookup($info['dsf']['fmt']['channel_type_id']);
$info['audio']['channelmode'] = $info['dsf']['fmt']['channel_type'];
$info['audio']['bits_per_sample'] = $info['dsf']['fmt']['bits_per_sample'];
$info['audio']['sample_rate'] = $info['dsf']['fmt']['sample_rate'];
$info['audio']['channels'] = $info['dsf']['fmt']['channels'];
$info['audio']['bitrate'] = $info['audio']['bits_per_sample'] * $info['audio']['sample_rate'] * $info['audio']['channels'];
$info['playtime_seconds'] = ($info['dsf']['data']['data_chunk_size'] * 8) / $info['audio']['bitrate'];
return true;
}
/**
* @param int $channel_type_id
*
* @return string
*/
public static function DSFchannelTypeLookup($channel_type_id) {
static $DSFchannelTypeLookup = array(
// interleaving order:
1 => 'mono', // 1: Mono
2 => 'stereo', // 1: Front-Left; 2: Front-Right
3 => '3-channel', // 1: Front-Left; 2: Front-Right; 3: Center
4 => 'quad', // 1: Front-Left; 2: Front-Right; 3: Back-Left; 4: Back-Right
5 => '4-channel', // 1: Front-Left; 2: Front-Right; 3: Center; 4: Low-Frequency
6 => '5-channel', // 1: Front-Left; 2: Front-Right; 3: Center; 4: Back-Left 5: Back-Right
7 => '5.1', // 1: Front-Left; 2: Front-Right; 3: Center; 4: Low-Frequency; 5: Back-Left; 6: Back-Right
);
return (isset($DSFchannelTypeLookup[$channel_type_id]) ? $DSFchannelTypeLookup[$channel_type_id] : '');
}
}
<?php
/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org> //
// available at https://github.com/JamesHeinrich/getID3 //
// or https://www.getid3.org //
// or http://getid3.sourceforge.net //
// see readme.txt for more details //
/////////////////////////////////////////////////////////////////
// //
// module.audio.dss.php //
// module for analyzing Digital Speech Standard (DSS) files //
// dependencies: NONE //
// ///
/////////////////////////////////////////////////////////////////
if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
exit;
}
class getid3_dss extends getid3_handler
{
/**
* @return bool
*/
public function Analyze() {
$info = &$this->getid3->info;
$this->fseek($info['avdataoffset']);
$DSSheader = $this->fread(1540);
if (!preg_match('#^[\\x02-\\x08]ds[s2]#', $DSSheader)) {
$this->error('Expecting "[02-08] 64 73 [73|32]" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($DSSheader, 0, 4)).'"');
return false;
}
// some structure information taken from http://cpansearch.perl.org/src/RGIBSON/Audio-DSS-0.02/lib/Audio/DSS.pm
$info['encoding'] = 'ISO-8859-1'; // not certain, but assumed
$info['dss'] = array();
$info['fileformat'] = 'dss';
$info['mime_type'] = 'audio/x-'.substr($DSSheader, 1, 3); // "audio/x-dss" or "audio/x-ds2"
$info['audio']['dataformat'] = substr($DSSheader, 1, 3); // "dss" or "ds2"
$info['audio']['bitrate_mode'] = 'cbr';
$info['dss']['version'] = ord(substr($DSSheader, 0, 1));
$info['dss']['hardware'] = trim(substr($DSSheader, 12, 16)); // identification string for hardware used to create the file, e.g. "DPM 9600", "DS2400"
$info['dss']['unknown1'] = getid3_lib::LittleEndian2Int(substr($DSSheader, 28, 4));
// 32-37 = "FE FF FE FF F7 FF" in all the sample files I've seen
$info['dss']['date_create_unix'] = $this->DSSdateStringToUnixDate(substr($DSSheader, 38, 12));
$info['dss']['date_complete_unix'] = $this->DSSdateStringToUnixDate(substr($DSSheader, 50, 12));
$info['dss']['playtime_sec'] = ((int) substr($DSSheader, 62, 2) * 3600) + ((int) substr($DSSheader, 64, 2) * 60) + (int) substr($DSSheader, 66, 2); // approximate file playtime in HHMMSS
if ($info['dss']['version'] <= 3) {
$info['dss']['playtime_ms'] = getid3_lib::LittleEndian2Int(substr($DSSheader, 512, 4)); // exact file playtime in milliseconds. Has also been observed at offset 530 in one sample file, with something else (unknown) at offset 512
$info['dss']['priority'] = ord(substr($DSSheader, 793, 1));
$info['dss']['comments'] = trim(substr($DSSheader, 798, 100));
$info['dss']['sample_rate_index'] = ord(substr($DSSheader, 1538, 1)); // this isn't certain, this may or may not be where the sample rate info is stored, but it seems consistent on my small selection of sample files
$info['audio']['sample_rate'] = $this->DSSsampleRateLookup($info['dss']['sample_rate_index']);
} else {
$this->getid3->warning('DSS above version 3 not fully supported in this version of getID3. Any additional documentation or format specifications would be welcome. This file is version '.$info['dss']['version']);
}
$info['audio']['bits_per_sample'] = 16; // maybe, maybe not -- most compressed audio formats don't have a fixed bits-per-sample value, but this is a reasonable approximation
$info['audio']['channels'] = 1;
if (!empty($info['dss']['playtime_ms']) && (floor($info['dss']['playtime_ms'] / 1000) == $info['dss']['playtime_sec'])) { // *should* just be playtime_ms / 1000 but at least one sample file has playtime_ms at offset 530 instead of offset 512, so safety check
$info['playtime_seconds'] = $info['dss']['playtime_ms'] / 1000;
} else {
$info['playtime_seconds'] = $info['dss']['playtime_sec'];
if (!empty($info['dss']['playtime_ms'])) {
$this->getid3->warning('playtime_ms ('.number_format($info['dss']['playtime_ms'] / 1000, 3).') does not match playtime_sec ('.number_format($info['dss']['playtime_sec']).') - using playtime_sec value');
}
}
$info['audio']['bitrate'] = ($info['filesize'] * 8) / $info['playtime_seconds'];
return true;
}
/**
* @param string $datestring
*
* @return int|false
*/
public function DSSdateStringToUnixDate($datestring) {
$y = (int) substr($datestring, 0, 2);
$m = substr($datestring, 2, 2);
$d = substr($datestring, 4, 2);
$h = substr($datestring, 6, 2);
$i = substr($datestring, 8, 2);
$s = substr($datestring, 10, 2);
$y += (($y < 95) ? 2000 : 1900);
return mktime($h, $i, $s, $m, $d, $y);
}
/**
* @param int $sample_rate_index
*
* @return int|false
*/
public function DSSsampleRateLookup($sample_rate_index) {
static $dssSampleRateLookup = array(
0x0A => 16000,
0x0C => 11025,
0x0D => 12000,
0x15 => 8000,
);
if (!array_key_exists($sample_rate_index, $dssSampleRateLookup)) {
$this->getid3->warning('unknown sample_rate_index: 0x'.strtoupper(dechex($sample_rate_index)));
return false;
}
return $dssSampleRateLookup[$sample_rate_index];
}
}
<?php
/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org> //
// available at https://github.com/JamesHeinrich/getID3 //
// or https://www.getid3.org //
// or http://getid3.sourceforge.net //
// see readme.txt for more details //
/////////////////////////////////////////////////////////////////
// //
// module.audio.dts.php //
// module for analyzing DTS Audio files //
// dependencies: NONE //
// //
/////////////////////////////////////////////////////////////////
if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
exit;
}
/**
* @tutorial http://wiki.multimedia.cx/index.php?title=DTS
*/
class getid3_dts extends getid3_handler
{
/**
* Default DTS syncword used in native .cpt or .dts formats.
*/
const syncword = "\x7F\xFE\x80\x01";
/**
* @var int
*/
private $readBinDataOffset = 0;
/**
* Possible syncwords indicating bitstream encoding.
*/
public static $syncwords = array(
0 => "\x7F\xFE\x80\x01", // raw big-endian
1 => "\xFE\x7F\x01\x80", // raw little-endian
2 => "\x1F\xFF\xE8\x00", // 14-bit big-endian
3 => "\xFF\x1F\x00\xE8"); // 14-bit little-endian
/**
* @return bool
*/
public function Analyze() {
$info = &$this->getid3->info;
$info['fileformat'] = 'dts';
$this->fseek($info['avdataoffset']);
$DTSheader = $this->fread(20); // we only need 2 words magic + 6 words frame header, but these words may be normal 16-bit words OR 14-bit words with 2 highest bits set to zero, so 8 words can be either 8*16/8 = 16 bytes OR 8*16*(16/14)/8 = 18.3 bytes
// check syncword
$sync = substr($DTSheader, 0, 4);
if (($encoding = array_search($sync, self::$syncwords)) !== false) {
$info['dts']['raw']['magic'] = $sync;
$this->readBinDataOffset = 32;
} elseif ($this->isDependencyFor('matroska')) {
// Matroska contains DTS without syncword encoded as raw big-endian format
$encoding = 0;
$this->readBinDataOffset = 0;
} else {
unset($info['fileformat']);
return $this->error('Expecting "'.implode('| ', array_map('getid3_lib::PrintHexBytes', self::$syncwords)).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($sync).'"');
}
// decode header
$fhBS = '';
for ($word_offset = 0; $word_offset <= strlen($DTSheader); $word_offset += 2) {
switch ($encoding) {
case 0: // raw big-endian
$fhBS .= getid3_lib::BigEndian2Bin( substr($DTSheader, $word_offset, 2) );
break;
case 1: // raw little-endian
$fhBS .= getid3_lib::BigEndian2Bin(strrev(substr($DTSheader, $word_offset, 2)));
break;
case 2: // 14-bit big-endian
$fhBS .= substr(getid3_lib::BigEndian2Bin( substr($DTSheader, $word_offset, 2) ), 2, 14);
break;
case 3: // 14-bit little-endian
$fhBS .= substr(getid3_lib::BigEndian2Bin(strrev(substr($DTSheader, $word_offset, 2))), 2, 14);
break;
}
}
$info['dts']['raw']['frame_type'] = $this->readBinData($fhBS, 1);
$info['dts']['raw']['deficit_samples'] = $this->readBinData($fhBS, 5);
$info['dts']['flags']['crc_present'] = (bool) $this->readBinData($fhBS, 1);
$info['dts']['raw']['pcm_sample_blocks'] = $this->readBinData($fhBS, 7);
$info['dts']['raw']['frame_byte_size'] = $this->readBinData($fhBS, 14);
$info['dts']['raw']['channel_arrangement'] = $this->readBinData($fhBS, 6);
$info['dts']['raw']['sample_frequency'] = $this->readBinData($fhBS, 4);
$info['dts']['raw']['bitrate'] = $this->readBinData($fhBS, 5);
$info['dts']['flags']['embedded_downmix'] = (bool) $this->readBinData($fhBS, 1);
$info['dts']['flags']['dynamicrange'] = (bool) $this->readBinData($fhBS, 1);
$info['dts']['flags']['timestamp'] = (bool) $this->readBinData($fhBS, 1);
$info['dts']['flags']['auxdata'] = (bool) $this->readBinData($fhBS, 1);
$info['dts']['flags']['hdcd'] = (bool) $this->readBinData($fhBS, 1);
$info['dts']['raw']['extension_audio'] = $this->readBinData($fhBS, 3);
$info['dts']['flags']['extended_coding'] = (bool) $this->readBinData($fhBS, 1);
$info['dts']['flags']['audio_sync_insertion'] = (bool) $this->readBinData($fhBS, 1);
$info['dts']['raw']['lfe_effects'] = $this->readBinData($fhBS, 2);
$info['dts']['flags']['predictor_history'] = (bool) $this->readBinData($fhBS, 1);
if ($info['dts']['flags']['crc_present']) {
$info['dts']['raw']['crc16'] = $this->readBinData($fhBS, 16);
}
$info['dts']['flags']['mri_perfect_reconst'] = (bool) $this->readBinData($fhBS, 1);
$info['dts']['raw']['encoder_soft_version'] = $this->readBinData($fhBS, 4);
$info['dts']['raw']['copy_history'] = $this->readBinData($fhBS, 2);
$info['dts']['raw']['bits_per_sample'] = $this->readBinData($fhBS, 2);
$info['dts']['flags']['surround_es'] = (bool) $this->readBinData($fhBS, 1);
$info['dts']['flags']['front_sum_diff'] = (bool) $this->readBinData($fhBS, 1);
$info['dts']['flags']['surround_sum_diff'] = (bool) $this->readBinData($fhBS, 1);
$info['dts']['raw']['dialog_normalization'] = $this->readBinData($fhBS, 4);
$info['dts']['bitrate'] = self::bitrateLookup($info['dts']['raw']['bitrate']);
$info['dts']['bits_per_sample'] = self::bitPerSampleLookup($info['dts']['raw']['bits_per_sample']);
$info['dts']['sample_rate'] = self::sampleRateLookup($info['dts']['raw']['sample_frequency']);
$info['dts']['dialog_normalization'] = self::dialogNormalization($info['dts']['raw']['dialog_normalization'], $info['dts']['raw']['encoder_soft_version']);
$info['dts']['flags']['lossless'] = (($info['dts']['raw']['bitrate'] == 31) ? true : false);
$info['dts']['bitrate_mode'] = (($info['dts']['raw']['bitrate'] == 30) ? 'vbr' : 'cbr');
$info['dts']['channels'] = self::numChannelsLookup($info['dts']['raw']['channel_arrangement']);
$info['dts']['channel_arrangement'] = self::channelArrangementLookup($info['dts']['raw']['channel_arrangement']);
$info['audio']['dataformat'] = 'dts';
$info['audio']['lossless'] = $info['dts']['flags']['lossless'];
$info['audio']['bitrate_mode'] = $info['dts']['bitrate_mode'];
$info['audio']['bits_per_sample'] = $info['dts']['bits_per_sample'];
$info['audio']['sample_rate'] = $info['dts']['sample_rate'];
$info['audio']['channels'] = $info['dts']['channels'];
$info['audio']['bitrate'] = $info['dts']['bitrate'];
if (isset($info['avdataend']) && !empty($info['dts']['bitrate']) && is_numeric($info['dts']['bitrate'])) {
$info['playtime_seconds'] = ($info['avdataend'] - $info['avdataoffset']) / ($info['dts']['bitrate'] / 8);
if (($encoding == 2) || ($encoding == 3)) {
// 14-bit data packed into 16-bit words, so the playtime is wrong because only (14/16) of the bytes in the data portion of the file are used at the specified bitrate
$info['playtime_seconds'] *= (14 / 16);
}
}
return true;
}
/**
* @param string $bin
* @param int $length
*
* @return int
*/
private function readBinData($bin, $length) {
$data = substr($bin, $this->readBinDataOffset, $length);
$this->readBinDataOffset += $length;
return bindec($data);
}
/**
* @param int $index
*
* @return int|string|false
*/
public static function bitrateLookup($index) {
static $lookup = array(
0 => 32000,
1 => 56000,
2 => 64000,
3 => 96000,
4 => 112000,
5 => 128000,
6 => 192000,
7 => 224000,
8 => 256000,
9 => 320000,
10 => 384000,
11 => 448000,
12 => 512000,
13 => 576000,
14 => 640000,
15 => 768000,
16 => 960000,
17 => 1024000,
18 => 1152000,
19 => 1280000,
20 => 1344000,
21 => 1408000,
22 => 1411200,
23 => 1472000,
24 => 1536000,
25 => 1920000,
26 => 2048000,
27 => 3072000,
28 => 3840000,
29 => 'open',
30 => 'variable',
31 => 'lossless',
);
return (isset($lookup[$index]) ? $lookup[$index] : false);
}
/**
* @param int $index
*
* @return int|string|false
*/
public static function sampleRateLookup($index) {
static $lookup = array(
0 => 'invalid',
1 => 8000,
2 => 16000,
3 => 32000,
4 => 'invalid',
5 => 'invalid',
6 => 11025,
7 => 22050,
8 => 44100,
9 => 'invalid',
10 => 'invalid',
11 => 12000,
12 => 24000,
13 => 48000,
14 => 'invalid',
15 => 'invalid',
);
return (isset($lookup[$index]) ? $lookup[$index] : false);
}
/**
* @param int $index
*
* @return int|false
*/
public static function bitPerSampleLookup($index) {
static $lookup = array(
0 => 16,
1 => 20,
2 => 24,
3 => 24,
);
return (isset($lookup[$index]) ? $lookup[$index] : false);
}
/**
* @param int $index
*
* @return int|false
*/
public static function numChannelsLookup($index) {
switch ($index) {
case 0:
return 1;
case 1:
case 2:
case 3:
case 4:
return 2;
case 5:
case 6:
return 3;
case 7:
case 8:
return 4;
case 9:
return 5;
case 10:
case 11:
case 12:
return 6;
case 13:
return 7;
case 14:
case 15:
return 8;
}
return false;
}
/**
* @param int $index
*
* @return string
*/
public static function channelArrangementLookup($index) {
static $lookup = array(
0 => 'A',
1 => 'A + B (dual mono)',
2 => 'L + R (stereo)',
3 => '(L+R) + (L-R) (sum-difference)',
4 => 'LT + RT (left and right total)',
5 => 'C + L + R',
6 => 'L + R + S',
7 => 'C + L + R + S',
8 => 'L + R + SL + SR',
9 => 'C + L + R + SL + SR',
10 => 'CL + CR + L + R + SL + SR',
11 => 'C + L + R+ LR + RR + OV',
12 => 'CF + CR + LF + RF + LR + RR',
13 => 'CL + C + CR + L + R + SL + SR',
14 => 'CL + CR + L + R + SL1 + SL2 + SR1 + SR2',
15 => 'CL + C+ CR + L + R + SL + S + SR',
);
return (isset($lookup[$index]) ? $lookup[$index] : 'user-defined');
}
/**
* @param int $index
* @param int $version
*
* @return int|false
*/
public static function dialogNormalization($index, $version) {
switch ($version) {
case 7:
return 0 - $index;
case 6:
return 0 - 16 - $index;
}
return false;
}
}
<?php
/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org> //
// available at https://github.com/JamesHeinrich/getID3 //
// or https://www.getid3.org //
// or http://getid3.sourceforge.net //
// see readme.txt for more details //
/////////////////////////////////////////////////////////////////
// //
// module.audio.flac.php //
// module for analyzing FLAC and OggFLAC audio files //
// dependencies: module.audio.ogg.php //
// ///
/////////////////////////////////////////////////////////////////
if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
exit;
}
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ogg.php', __FILE__, true);
/**
* @tutorial http://flac.sourceforge.net/format.html
*/
class getid3_flac extends getid3_handler
{
const syncword = 'fLaC';
/**
* @return bool
*/
public function Analyze() {
$info = &$this->getid3->info;
$this->fseek($info['avdataoffset']);
$StreamMarker = $this->fread(4);
if ($StreamMarker != self::syncword) {
return $this->error('Expecting "'.getid3_lib::PrintHexBytes(self::syncword).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($StreamMarker).'"');
}
$info['fileformat'] = 'flac';
$info['audio']['dataformat'] = 'flac';
$info['audio']['bitrate_mode'] = 'vbr';
$info['audio']['lossless'] = true;
// parse flac container
return $this->parseMETAdata();
}
/**
* @return bool
*/
public function parseMETAdata() {
$info = &$this->getid3->info;
do {
$BlockOffset = $this->ftell();
$BlockHeader = $this->fread(4);
$LBFBT = getid3_lib::BigEndian2Int(substr($BlockHeader, 0, 1)); // LBFBT = LastBlockFlag + BlockType
$LastBlockFlag = (bool) ($LBFBT & 0x80);
$BlockType = ($LBFBT & 0x7F);
$BlockLength = getid3_lib::BigEndian2Int(substr($BlockHeader, 1, 3));
$BlockTypeText = self::metaBlockTypeLookup($BlockType);
if (($BlockOffset + 4 + $BlockLength) > $info['avdataend']) {
$this->warning('METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$BlockTypeText.') at offset '.$BlockOffset.' extends beyond end of file');
break;
}
if ($BlockLength < 1) {
if ($BlockTypeText != 'reserved') {
// probably supposed to be zero-length
$this->warning('METADATA_BLOCK_HEADER.BLOCK_LENGTH ('.$BlockTypeText.') at offset '.$BlockOffset.' is zero bytes');
continue;
}
$this->error('METADATA_BLOCK_HEADER.BLOCK_LENGTH ('.$BlockLength.') at offset '.$BlockOffset.' is invalid');
break;
}
$info['flac'][$BlockTypeText]['raw'] = array();
$BlockTypeText_raw = &$info['flac'][$BlockTypeText]['raw'];
$BlockTypeText_raw['offset'] = $BlockOffset;
$BlockTypeText_raw['last_meta_block'] = $LastBlockFlag;
$BlockTypeText_raw['block_type'] = $BlockType;
$BlockTypeText_raw['block_type_text'] = $BlockTypeText;
$BlockTypeText_raw['block_length'] = $BlockLength;
if ($BlockTypeText_raw['block_type'] != 0x06) { // do not read attachment data automatically
$BlockTypeText_raw['block_data'] = $this->fread($BlockLength);
}
switch ($BlockTypeText) {
case 'STREAMINFO': // 0x00
if (!$this->parseSTREAMINFO($BlockTypeText_raw['block_data'])) {
return false;
}
break;
case 'PADDING': // 0x01
unset($info['flac']['PADDING']); // ignore
break;
case 'APPLICATION': // 0x02
if (!$this->parseAPPLICATION($BlockTypeText_raw['block_data'])) {
return false;
}
break;
case 'SEEKTABLE': // 0x03
if (!$this->parseSEEKTABLE($BlockTypeText_raw['block_data'])) {
return false;
}
break;
case 'VORBIS_COMMENT': // 0x04
if (!$this->parseVORBIS_COMMENT($BlockTypeText_raw['block_data'])) {
return false;
}
break;
case 'CUESHEET': // 0x05
if (!$this->parseCUESHEET($BlockTypeText_raw['block_data'])) {
return false;
}
break;
case 'PICTURE': // 0x06
if (!$this->parsePICTURE()) {
return false;
}
break;
default:
$this->warning('Unhandled METADATA_BLOCK_HEADER.BLOCK_TYPE ('.$BlockType.') at offset '.$BlockOffset);
}
unset($info['flac'][$BlockTypeText]['raw']);
$info['avdataoffset'] = $this->ftell();
}
while ($LastBlockFlag === false);
// handle tags
if (!empty($info['flac']['VORBIS_COMMENT']['comments'])) {
$info['flac']['comments'] = $info['flac']['VORBIS_COMMENT']['comments'];
}
if (!empty($info['flac']['VORBIS_COMMENT']['vendor'])) {
$info['audio']['encoder'] = str_replace('reference ', '', $info['flac']['VORBIS_COMMENT']['vendor']);
}
// copy attachments to 'comments' array if nesesary
if (isset($info['flac']['PICTURE']) && ($this->getid3->option_save_attachments !== getID3::ATTACHMENTS_NONE)) {
foreach ($info['flac']['PICTURE'] as $entry) {
if (!empty($entry['data'])) {
if (!isset($info['flac']['comments']['picture'])) {
$info['flac']['comments']['picture'] = array();
}
$comments_picture_data = array();
foreach (array('data', 'image_mime', 'image_width', 'image_height', 'imagetype', 'picturetype', 'description', 'datalength') as $picture_key) {
if (isset($entry[$picture_key])) {
$comments_picture_data[$picture_key] = $entry[$picture_key];
}
}
$info['flac']['comments']['picture'][] = $comments_picture_data;
unset($comments_picture_data);
}
}
}
if (isset($info['flac']['STREAMINFO'])) {
if (!$this->isDependencyFor('matroska')) {
$info['flac']['compressed_audio_bytes'] = $info['avdataend'] - $info['avdataoffset'];
}
$info['flac']['uncompressed_audio_bytes'] = $info['flac']['STREAMINFO']['samples_stream'] * $info['flac']['STREAMINFO']['channels'] * ($info['flac']['STREAMINFO']['bits_per_sample'] / 8);
if ($info['flac']['uncompressed_audio_bytes'] == 0) {
return $this->error('Corrupt FLAC file: uncompressed_audio_bytes == zero');
}
if (!empty($info['flac']['compressed_audio_bytes'])) {
$info['flac']['compression_ratio'] = $info['flac']['compressed_audio_bytes'] / $info['flac']['uncompressed_audio_bytes'];
}
}
// set md5_data_source - built into flac 0.5+
if (isset($info['flac']['STREAMINFO']['audio_signature'])) {
if ($info['flac']['STREAMINFO']['audio_signature'] === str_repeat("\x00", 16)) {
$this->warning('FLAC STREAMINFO.audio_signature is null (known issue with libOggFLAC)');
}
else {
$info['md5_data_source'] = '';
$md5 = $info['flac']['STREAMINFO']['audio_signature'];
for ($i = 0; $i < strlen($md5); $i++) {
$info['md5_data_source'] .= str_pad(dechex(ord($md5[$i])), 2, '00', STR_PAD_LEFT);
}
if (!preg_match('/^[0-9a-f]{32}$/', $info['md5_data_source'])) {
unset($info['md5_data_source']);
}
}
}
if (isset($info['flac']['STREAMINFO']['bits_per_sample'])) {
$info['audio']['bits_per_sample'] = $info['flac']['STREAMINFO']['bits_per_sample'];
if ($info['audio']['bits_per_sample'] == 8) {
// special case
// must invert sign bit on all data bytes before MD5'ing to match FLAC's calculated value
// MD5sum calculates on unsigned bytes, but FLAC calculated MD5 on 8-bit audio data as signed
$this->warning('FLAC calculates MD5 data strangely on 8-bit audio, so the stored md5_data_source value will not match the decoded WAV file');
}
}
return true;
}
/**
* @param string $BlockData
*
* @return array
*/
public static function parseSTREAMINFOdata($BlockData) {
$streaminfo = array();
$streaminfo['min_block_size'] = getid3_lib::BigEndian2Int(substr($BlockData, 0, 2));
$streaminfo['max_block_size'] = getid3_lib::BigEndian2Int(substr($BlockData, 2, 2));
$streaminfo['min_frame_size'] = getid3_lib::BigEndian2Int(substr($BlockData, 4, 3));
$streaminfo['max_frame_size'] = getid3_lib::BigEndian2Int(substr($BlockData, 7, 3));
$SRCSBSS = getid3_lib::BigEndian2Bin(substr($BlockData, 10, 8));
$streaminfo['sample_rate'] = getid3_lib::Bin2Dec(substr($SRCSBSS, 0, 20));
$streaminfo['channels'] = getid3_lib::Bin2Dec(substr($SRCSBSS, 20, 3)) + 1;
$streaminfo['bits_per_sample'] = getid3_lib::Bin2Dec(substr($SRCSBSS, 23, 5)) + 1;
$streaminfo['samples_stream'] = getid3_lib::Bin2Dec(substr($SRCSBSS, 28, 36));
$streaminfo['audio_signature'] = substr($BlockData, 18, 16);
return $streaminfo;
}
/**
* @param string $BlockData
*
* @return bool
*/
private function parseSTREAMINFO($BlockData) {
$info = &$this->getid3->info;
$info['flac']['STREAMINFO'] = self::parseSTREAMINFOdata($BlockData);
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'];
$info['playtime_seconds'] = $info['flac']['STREAMINFO']['samples_stream'] / $info['flac']['STREAMINFO']['sample_rate'];
if ($info['playtime_seconds'] > 0) {
if (!$this->isDependencyFor('matroska')) {
$info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
}
else {
$this->warning('Cannot determine audio bitrate because total stream size is unknown');
}
}
} else {
return $this->error('Corrupt METAdata block: STREAMINFO');
}
return true;
}
/**
* @param string $BlockData
*
* @return bool
*/
private function parseAPPLICATION($BlockData) {
$info = &$this->getid3->info;
$ApplicationID = getid3_lib::BigEndian2Int(substr($BlockData, 0, 4));
$info['flac']['APPLICATION'][$ApplicationID]['name'] = self::applicationIDLookup($ApplicationID);
$info['flac']['APPLICATION'][$ApplicationID]['data'] = substr($BlockData, 4);
return true;
}
/**
* @param string $BlockData
*
* @return bool
*/
private function parseSEEKTABLE($BlockData) {
$info = &$this->getid3->info;
$offset = 0;
$BlockLength = strlen($BlockData);
$placeholderpattern = str_repeat("\xFF", 8);
while ($offset < $BlockLength) {
$SampleNumberString = substr($BlockData, $offset, 8);
$offset += 8;
if ($SampleNumberString == $placeholderpattern) {
// placeholder point
getid3_lib::safe_inc($info['flac']['SEEKTABLE']['placeholders'], 1);
$offset += 10;
} else {
$SampleNumber = getid3_lib::BigEndian2Int($SampleNumberString);
$info['flac']['SEEKTABLE'][$SampleNumber]['offset'] = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 8));
$offset += 8;
$info['flac']['SEEKTABLE'][$SampleNumber]['samples'] = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 2));
$offset += 2;
}
}
return true;
}
/**
* @param string $BlockData
*
* @return bool
*/
private function parseVORBIS_COMMENT($BlockData) {
$info = &$this->getid3->info;
$getid3_ogg = new getid3_ogg($this->getid3);
if ($this->isDependencyFor('matroska')) {
$getid3_ogg->setStringMode($this->data_string);
}
$getid3_ogg->ParseVorbisComments();
if (isset($info['ogg'])) {
unset($info['ogg']['comments_raw']);
$info['flac']['VORBIS_COMMENT'] = $info['ogg'];
unset($info['ogg']);
}
unset($getid3_ogg);
return true;
}
/**
* @param string $BlockData
*
* @return bool
*/
private function parseCUESHEET($BlockData) {
$info = &$this->getid3->info;
$offset = 0;
$info['flac']['CUESHEET']['media_catalog_number'] = trim(substr($BlockData, $offset, 128), "\0");
$offset += 128;
$info['flac']['CUESHEET']['lead_in_samples'] = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 8));
$offset += 8;
$info['flac']['CUESHEET']['flags']['is_cd'] = (bool) (getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1)) & 0x80);
$offset += 1;
$offset += 258; // reserved
$info['flac']['CUESHEET']['number_tracks'] = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1));
$offset += 1;
for ($track = 0; $track < $info['flac']['CUESHEET']['number_tracks']; $track++) {
$TrackSampleOffset = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 8));
$offset += 8;
$TrackNumber = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1));
$offset += 1;
$info['flac']['CUESHEET']['tracks'][$TrackNumber]['sample_offset'] = $TrackSampleOffset;
$info['flac']['CUESHEET']['tracks'][$TrackNumber]['isrc'] = substr($BlockData, $offset, 12);
$offset += 12;
$TrackFlagsRaw = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1));
$offset += 1;
$info['flac']['CUESHEET']['tracks'][$TrackNumber]['flags']['is_audio'] = (bool) ($TrackFlagsRaw & 0x80);
$info['flac']['CUESHEET']['tracks'][$TrackNumber]['flags']['pre_emphasis'] = (bool) ($TrackFlagsRaw & 0x40);
$offset += 13; // reserved
$info['flac']['CUESHEET']['tracks'][$TrackNumber]['index_points'] = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1));
$offset += 1;
for ($index = 0; $index < $info['flac']['CUESHEET']['tracks'][$TrackNumber]['index_points']; $index++) {
$IndexSampleOffset = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 8));
$offset += 8;
$IndexNumber = getid3_lib::BigEndian2Int(substr($BlockData, $offset, 1));
$offset += 1;
$offset += 3; // reserved
$info['flac']['CUESHEET']['tracks'][$TrackNumber]['indexes'][$IndexNumber] = $IndexSampleOffset;
}
}
return true;
}
/**
* Parse METADATA_BLOCK_PICTURE flac structure and extract attachment
* External usage: audio.ogg
*
* @return bool
*/
public function parsePICTURE() {
$info = &$this->getid3->info;
$picture['typeid'] = getid3_lib::BigEndian2Int($this->fread(4));
$picture['picturetype'] = self::pictureTypeLookup($picture['typeid']);
$picture['image_mime'] = $this->fread(getid3_lib::BigEndian2Int($this->fread(4)));
$descr_length = getid3_lib::BigEndian2Int($this->fread(4));
if ($descr_length) {
$picture['description'] = $this->fread($descr_length);
}
$picture['image_width'] = getid3_lib::BigEndian2Int($this->fread(4));
$picture['image_height'] = getid3_lib::BigEndian2Int($this->fread(4));
$picture['color_depth'] = getid3_lib::BigEndian2Int($this->fread(4));
$picture['colors_indexed'] = getid3_lib::BigEndian2Int($this->fread(4));
$picture['datalength'] = getid3_lib::BigEndian2Int($this->fread(4));
if ($picture['image_mime'] == '-->') {
$picture['data'] = $this->fread($picture['datalength']);
} else {
$picture['data'] = $this->saveAttachment(
str_replace('/', '_', $picture['picturetype']).'_'.$this->ftell(),
$this->ftell(),
$picture['datalength'],
$picture['image_mime']);
}
$info['flac']['PICTURE'][] = $picture;
return true;
}
/**
* @param int $blocktype
*
* @return string
*/
public static function metaBlockTypeLookup($blocktype) {
static $lookup = array(
0 => 'STREAMINFO',
1 => 'PADDING',
2 => 'APPLICATION',
3 => 'SEEKTABLE',
4 => 'VORBIS_COMMENT',
5 => 'CUESHEET',
6 => 'PICTURE',
);
return (isset($lookup[$blocktype]) ? $lookup[$blocktype] : 'reserved');
}
/**
* @param int $applicationid
*
* @return string
*/
public static function applicationIDLookup($applicationid) {
// http://flac.sourceforge.net/id.html
static $lookup = array(
0x41544348 => 'FlacFile', // "ATCH"
0x42534F4C => 'beSolo', // "BSOL"
0x42554753 => 'Bugs Player', // "BUGS"
0x43756573 => 'GoldWave cue points (specification)', // "Cues"
0x46696361 => 'CUE Splitter', // "Fica"
0x46746F6C => 'flac-tools', // "Ftol"
0x4D4F5442 => 'MOTB MetaCzar', // "MOTB"
0x4D505345 => 'MP3 Stream Editor', // "MPSE"
0x4D754D4C => 'MusicML: Music Metadata Language', // "MuML"
0x52494646 => 'Sound Devices RIFF chunk storage', // "RIFF"
0x5346464C => 'Sound Font FLAC', // "SFFL"
0x534F4E59 => 'Sony Creative Software', // "SONY"
0x5351455A => 'flacsqueeze', // "SQEZ"
0x54745776 => 'TwistedWave', // "TtWv"
0x55495453 => 'UITS Embedding tools', // "UITS"
0x61696666 => 'FLAC AIFF chunk storage', // "aiff"
0x696D6167 => 'flac-image application for storing arbitrary files in APPLICATION metadata blocks', // "imag"
0x7065656D => 'Parseable Embedded Extensible Metadata (specification)', // "peem"
0x71667374 => 'QFLAC Studio', // "qfst"
0x72696666 => 'FLAC RIFF chunk storage', // "riff"
0x74756E65 => 'TagTuner', // "tune"
0x78626174 => 'XBAT', // "xbat"
0x786D6364 => 'xmcd', // "xmcd"
);
return (isset($lookup[$applicationid]) ? $lookup[$applicationid] : 'reserved');
}
/**
* @param int $type_id
*
* @return string
*/
public static function pictureTypeLookup($type_id) {
static $lookup = array (
0 => 'Other',
1 => '32x32 pixels \'file icon\' (PNG only)',
2 => 'Other file icon',
3 => 'Cover (front)',
4 => 'Cover (back)',
5 => 'Leaflet page',
6 => 'Media (e.g. label side of CD)',
7 => 'Lead artist/lead performer/soloist',
8 => 'Artist/performer',
9 => 'Conductor',
10 => 'Band/Orchestra',
11 => 'Composer',
12 => 'Lyricist/text writer',
13 => 'Recording Location',
14 => 'During recording',
15 => 'During performance',
16 => 'Movie/video screen capture',
17 => 'A bright coloured fish',
18 => 'Illustration',
19 => 'Band/artist logotype',
20 => 'Publisher/Studio logotype',
);
return (isset($lookup[$type_id]) ? $lookup[$type_id] : 'reserved');
}
}
<?php
/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org> //
// available at https://github.com/JamesHeinrich/getID3 //
// or https://www.getid3.org //
// or http://getid3.sourceforge.net //
// see readme.txt for more details //
/////////////////////////////////////////////////////////////////
// //
// module.audio.la.php //
// module for analyzing LA (LosslessAudio) audio files //
// dependencies: module.audio.riff.php //
// ///
/////////////////////////////////////////////////////////////////
if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
exit;
}
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, true);
class getid3_la extends getid3_handler
{
/**
* @return bool
*/
public function Analyze() {
$info = &$this->getid3->info;
$offset = 0;
$this->fseek($info['avdataoffset']);
$rawdata = $this->fread($this->getid3->fread_buffer_size());
switch (substr($rawdata, $offset, 4)) {
case 'LA02':
case 'LA03':
case 'LA04':
$info['fileformat'] = 'la';
$info['audio']['dataformat'] = 'la';
$info['audio']['lossless'] = true;
$info['la']['version_major'] = (int) substr($rawdata, $offset + 2, 1);
$info['la']['version_minor'] = (int) substr($rawdata, $offset + 3, 1);
$info['la']['version'] = (float) $info['la']['version_major'] + ($info['la']['version_minor'] / 10);
$offset += 4;
$info['la']['uncompressed_size'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4));
$offset += 4;
if ($info['la']['uncompressed_size'] == 0) {
$this->error('Corrupt LA file: uncompressed_size == zero');
return false;
}
$WAVEchunk = substr($rawdata, $offset, 4);
if ($WAVEchunk !== 'WAVE') {
$this->error('Expected "WAVE" ('.getid3_lib::PrintHexBytes('WAVE').') at offset '.$offset.', found "'.$WAVEchunk.'" ('.getid3_lib::PrintHexBytes($WAVEchunk).') instead.');
return false;
}
$offset += 4;
$info['la']['fmt_size'] = 24;
if ($info['la']['version'] >= 0.3) {
$info['la']['fmt_size'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4));
$info['la']['header_size'] = 49 + $info['la']['fmt_size'] - 24;
$offset += 4;
} else {
// version 0.2 didn't support additional data blocks
$info['la']['header_size'] = 41;
}
$fmt_chunk = substr($rawdata, $offset, 4);
if ($fmt_chunk !== 'fmt ') {
$this->error('Expected "fmt " ('.getid3_lib::PrintHexBytes('fmt ').') at offset '.$offset.', found "'.$fmt_chunk.'" ('.getid3_lib::PrintHexBytes($fmt_chunk).') instead.');
return false;
}
$offset += 4;
$fmt_size = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4));
$offset += 4;
$info['la']['raw']['format'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 2));
$offset += 2;
$info['la']['channels'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 2));
$offset += 2;
if ($info['la']['channels'] == 0) {
$this->error('Corrupt LA file: channels == zero');
return false;
}
$info['la']['sample_rate'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4));
$offset += 4;
if ($info['la']['sample_rate'] == 0) {
$this->error('Corrupt LA file: sample_rate == zero');
return false;
}
$info['la']['bytes_per_second'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4));
$offset += 4;
$info['la']['bytes_per_sample'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 2));
$offset += 2;
$info['la']['bits_per_sample'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 2));
$offset += 2;
$info['la']['samples'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4));
$offset += 4;
$info['la']['raw']['flags'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 1));
$offset += 1;
$info['la']['flags']['seekable'] = (bool) ($info['la']['raw']['flags'] & 0x01);
if ($info['la']['version'] >= 0.4) {
$info['la']['flags']['high_compression'] = (bool) ($info['la']['raw']['flags'] & 0x02);
}
$info['la']['original_crc'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4));
$offset += 4;
// mikeØbevin*de
// Basically, the blocksize/seekevery are 61440/19 in La0.4 and 73728/16
// in earlier versions. A seekpoint is added every blocksize * seekevery
// samples, so 4 * int(totalSamples / (blockSize * seekEvery)) should
// give the number of bytes used for the seekpoints. Of course, if seeking
// is disabled, there are no seekpoints stored.
if ($info['la']['version'] >= 0.4) {
$info['la']['blocksize'] = 61440;
$info['la']['seekevery'] = 19;
} else {
$info['la']['blocksize'] = 73728;
$info['la']['seekevery'] = 16;
}
$info['la']['seekpoint_count'] = 0;
if ($info['la']['flags']['seekable']) {
$info['la']['seekpoint_count'] = floor($info['la']['samples'] / ($info['la']['blocksize'] * $info['la']['seekevery']));
for ($i = 0; $i < $info['la']['seekpoint_count']; $i++) {
$info['la']['seekpoints'][] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4));
$offset += 4;
}
}
if ($info['la']['version'] >= 0.3) {
// Following the main header information, the program outputs all of the
// seekpoints. Following these is what I called the 'footer start',
// i.e. the position immediately after the La audio data is finished.
$info['la']['footerstart'] = getid3_lib::LittleEndian2Int(substr($rawdata, $offset, 4));
$offset += 4;
if ($info['la']['footerstart'] > $info['filesize']) {
$this->warning('FooterStart value points to offset '.$info['la']['footerstart'].' which is beyond end-of-file ('.$info['filesize'].')');
$info['la']['footerstart'] = $info['filesize'];
}
} else {
// La v0.2 didn't have FooterStart value
$info['la']['footerstart'] = $info['avdataend'];
}
if ($info['la']['footerstart'] < $info['avdataend']) {
if ($RIFFtempfilename = tempnam(GETID3_TEMP_DIR, 'id3')) {
if ($RIFF_fp = fopen($RIFFtempfilename, 'w+b')) {
$RIFFdata = 'WAVE';
if ($info['la']['version'] == 0.2) {
$RIFFdata .= substr($rawdata, 12, 24);
} else {
$RIFFdata .= substr($rawdata, 16, 24);
}
if ($info['la']['footerstart'] < $info['avdataend']) {
$this->fseek($info['la']['footerstart']);
$RIFFdata .= $this->fread($info['avdataend'] - $info['la']['footerstart']);
}
$RIFFdata = 'RIFF'.getid3_lib::LittleEndian2String(strlen($RIFFdata), 4, false).$RIFFdata;
fwrite($RIFF_fp, $RIFFdata, strlen($RIFFdata));
fclose($RIFF_fp);
$getid3_temp = new getID3();
$getid3_temp->openfile($RIFFtempfilename);
$getid3_riff = new getid3_riff($getid3_temp);
$getid3_riff->Analyze();
if (empty($getid3_temp->info['error'])) {
$info['riff'] = $getid3_temp->info['riff'];
} else {
$this->warning('Error parsing RIFF portion of La file: '.implode($getid3_temp->info['error']));
}
unset($getid3_temp, $getid3_riff);
}
unlink($RIFFtempfilename);
}
}
// $info['avdataoffset'] should be zero to begin with, but just in case it's not, include the addition anyway
$info['avdataend'] = $info['avdataoffset'] + $info['la']['footerstart'];
$info['avdataoffset'] = $info['avdataoffset'] + $offset;
$info['la']['compression_ratio'] = (float) (($info['avdataend'] - $info['avdataoffset']) / $info['la']['uncompressed_size']);
$info['playtime_seconds'] = (float) ($info['la']['samples'] / $info['la']['sample_rate']) / $info['la']['channels'];
if ($info['playtime_seconds'] == 0) {
$this->error('Corrupt LA file: playtime_seconds == zero');
return false;
}
$info['audio']['bitrate'] = ($info['avdataend'] - $info['avdataoffset']) * 8 / $info['playtime_seconds'];
//$info['audio']['codec'] = $info['la']['codec'];
$info['audio']['bits_per_sample'] = $info['la']['bits_per_sample'];
break;
default:
if (substr($rawdata, $offset, 2) == 'LA') {
$this->error('This version of getID3() ['.$this->getid3->version().'] does not support LA version '.substr($rawdata, $offset + 2, 1).'.'.substr($rawdata, $offset + 3, 1).' which this appears to be - check http://getid3.sourceforge.net for updates.');
} else {
$this->error('Not a LA (Lossless-Audio) file');
}
return false;
}
$info['audio']['channels'] = $info['la']['channels'];
$info['audio']['sample_rate'] = (int) $info['la']['sample_rate'];
$info['audio']['encoder'] = 'LA v'.$info['la']['version'];
return true;
}
}
<?php
/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org> //
// available at https://github.com/JamesHeinrich/getID3 //
// or https://www.getid3.org //
// or http://getid3.sourceforge.net //
// see readme.txt for more details //
/////////////////////////////////////////////////////////////////
// //
// module.audio.lpac.php //
// module for analyzing LPAC Audio files //
// dependencies: module.audio-video.riff.php //
// ///
/////////////////////////////////////////////////////////////////
if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
exit;
}
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, true);
class getid3_lpac extends getid3_handler
{
/**
* @return bool
*/
public function Analyze() {
$info = &$this->getid3->info;
$this->fseek($info['avdataoffset']);
$LPACheader = $this->fread(14);
$StreamMarker = substr($LPACheader, 0, 4);
if ($StreamMarker != 'LPAC') {
$this->error('Expected "LPAC" at offset '.$info['avdataoffset'].', found "'.$StreamMarker.'"');
return false;
}
$info['avdataoffset'] += 14;
$info['fileformat'] = 'lpac';
$info['audio']['dataformat'] = 'lpac';
$info['audio']['lossless'] = true;
$info['audio']['bitrate_mode'] = 'vbr';
$info['lpac']['file_version'] = getid3_lib::BigEndian2Int(substr($LPACheader, 4, 1));
$flags['audio_type'] = getid3_lib::BigEndian2Int(substr($LPACheader, 5, 1));
$info['lpac']['total_samples']= getid3_lib::BigEndian2Int(substr($LPACheader, 6, 4));
$flags['parameters'] = getid3_lib::BigEndian2Int(substr($LPACheader, 10, 4));
$info['lpac']['flags']['is_wave'] = (bool) ($flags['audio_type'] & 0x40);
$info['lpac']['flags']['stereo'] = (bool) ($flags['audio_type'] & 0x04);
$info['lpac']['flags']['24_bit'] = (bool) ($flags['audio_type'] & 0x02);
$info['lpac']['flags']['16_bit'] = (bool) ($flags['audio_type'] & 0x01);
if ($info['lpac']['flags']['24_bit'] && $info['lpac']['flags']['16_bit']) {
$this->warning('24-bit and 16-bit flags cannot both be set');
}
$info['lpac']['flags']['fast_compress'] = (bool) ($flags['parameters'] & 0x40000000);
$info['lpac']['flags']['random_access'] = (bool) ($flags['parameters'] & 0x08000000);
$info['lpac']['block_length'] = pow(2, (($flags['parameters'] & 0x07000000) >> 24)) * 256;
$info['lpac']['flags']['adaptive_prediction_order'] = (bool) ($flags['parameters'] & 0x00800000);
$info['lpac']['flags']['adaptive_quantization'] = (bool) ($flags['parameters'] & 0x00400000);
$info['lpac']['flags']['joint_stereo'] = (bool) ($flags['parameters'] & 0x00040000);
$info['lpac']['quantization'] = ($flags['parameters'] & 0x00001F00) >> 8;
$info['lpac']['max_prediction_order'] = ($flags['parameters'] & 0x0000003F);
if ($info['lpac']['flags']['fast_compress'] && ($info['lpac']['max_prediction_order'] != 3)) {
$this->warning('max_prediction_order expected to be "3" if fast_compress is true, actual value is "'.$info['lpac']['max_prediction_order'].'"');
}
switch ($info['lpac']['file_version']) {
case 6:
if ($info['lpac']['flags']['adaptive_quantization']) {
$this->warning('adaptive_quantization expected to be false in LPAC file stucture v6, actually true');
}
if ($info['lpac']['quantization'] != 20) {
$this->warning('Quantization expected to be 20 in LPAC file stucture v6, actually '.$info['lpac']['flags']['Q']);
}
break;
default:
//$this->warning('This version of getID3() ['.$this->getid3->version().'] only supports LPAC file format version 6, this file is version '.$info['lpac']['file_version'].' - please report to info@getid3.org');
break;
}
$getid3_temp = new getID3();
$getid3_temp->openfile($this->getid3->filename, $this->getid3->info['filesize'], $this->getid3->fp);
$getid3_temp->info = $info;
$getid3_riff = new getid3_riff($getid3_temp);
$getid3_riff->Analyze();
$info['avdataoffset'] = $getid3_temp->info['avdataoffset'];
$info['riff'] = $getid3_temp->info['riff'];
$info['error'] = $getid3_temp->info['error'];
$info['warning'] = $getid3_temp->info['warning'];
$info['lpac']['comments']['comment'] = $getid3_temp->info['comments'];
$info['audio']['sample_rate'] = $getid3_temp->info['audio']['sample_rate'];
unset($getid3_temp, $getid3_riff);
$info['audio']['channels'] = ($info['lpac']['flags']['stereo'] ? 2 : 1);
if ($info['lpac']['flags']['24_bit']) {
$info['audio']['bits_per_sample'] = $info['riff']['audio'][0]['bits_per_sample'];
} elseif ($info['lpac']['flags']['16_bit']) {
$info['audio']['bits_per_sample'] = 16;
} else {
$info['audio']['bits_per_sample'] = 8;
}
if ($info['lpac']['flags']['fast_compress']) {
// fast
$info['audio']['encoder_options'] = '-1';
} else {
switch ($info['lpac']['max_prediction_order']) {
case 20: // simple
$info['audio']['encoder_options'] = '-2';
break;
case 30: // medium
$info['audio']['encoder_options'] = '-3';
break;
case 40: // high
$info['audio']['encoder_options'] = '-4';
break;
case 60: // extrahigh
$info['audio']['encoder_options'] = '-5';
break;
}
}
$info['playtime_seconds'] = $info['lpac']['total_samples'] / $info['audio']['sample_rate'];
$info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
return true;
}
}
<?php
/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org> //
// available at https://github.com/JamesHeinrich/getID3 //
// or https://www.getid3.org //
// or http://getid3.sourceforge.net //
// see readme.txt for more details //
/////////////////////////////////////////////////////////////////
// //
// module.audio.midi.php //
// module for Midi Audio files //
// dependencies: NONE //
// ///
/////////////////////////////////////////////////////////////////
if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
exit;
}
define('GETID3_MIDI_MAGIC_MTHD', 'MThd'); // MIDI file header magic
define('GETID3_MIDI_MAGIC_MTRK', 'MTrk'); // MIDI track header magic
class getid3_midi extends getid3_handler
{
/**
* @var bool
*/
public $scanwholefile = true;
/**
* @return bool
*/
public function Analyze() {
$info = &$this->getid3->info;
// shortcut
$info['midi']['raw'] = array();
$thisfile_midi = &$info['midi'];
$thisfile_midi_raw = &$thisfile_midi['raw'];
$info['fileformat'] = 'midi';
$info['audio']['dataformat'] = 'midi';
$this->fseek($info['avdataoffset']);
$MIDIdata = $this->fread($this->getid3->fread_buffer_size());
$offset = 0;
$MIDIheaderID = substr($MIDIdata, $offset, 4); // 'MThd'
if ($MIDIheaderID != GETID3_MIDI_MAGIC_MTHD) {
$this->error('Expecting "'.getid3_lib::PrintHexBytes(GETID3_MIDI_MAGIC_MTHD).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($MIDIheaderID).'"');
unset($info['fileformat']);
return false;
}
$offset += 4;
$thisfile_midi_raw['headersize'] = getid3_lib::BigEndian2Int(substr($MIDIdata, $offset, 4));
$offset += 4;
$thisfile_midi_raw['fileformat'] = getid3_lib::BigEndian2Int(substr($MIDIdata, $offset, 2));
$offset += 2;
$thisfile_midi_raw['tracks'] = getid3_lib::BigEndian2Int(substr($MIDIdata, $offset, 2));
$offset += 2;
$thisfile_midi_raw['ticksperqnote'] = getid3_lib::BigEndian2Int(substr($MIDIdata, $offset, 2));
$offset += 2;
for ($i = 0; $i < $thisfile_midi_raw['tracks']; $i++) {
while ((strlen($MIDIdata) - $offset) < 8) {
if ($buffer = $this->fread($this->getid3->fread_buffer_size())) {
$MIDIdata .= $buffer;
} else {
$this->warning('only processed '.($i - 1).' of '.$thisfile_midi_raw['tracks'].' tracks');
$this->error('Unabled to read more file data at '.$this->ftell().' (trying to seek to : '.$offset.'), was expecting at least 8 more bytes');
return false;
}
}
$trackID = substr($MIDIdata, $offset, 4);
$offset += 4;
if ($trackID == GETID3_MIDI_MAGIC_MTRK) {
$tracksize = getid3_lib::BigEndian2Int(substr($MIDIdata, $offset, 4));
$offset += 4;
//$thisfile_midi['tracks'][$i]['size'] = $tracksize;
$trackdataarray[$i] = substr($MIDIdata, $offset, $tracksize);
$offset += $tracksize;
} else {
$this->error('Expecting "'.getid3_lib::PrintHexBytes(GETID3_MIDI_MAGIC_MTRK).'" at '.($offset - 4).', found "'.getid3_lib::PrintHexBytes($trackID).'" instead');
return false;
}
}
if (!isset($trackdataarray) || !is_array($trackdataarray)) {
$this->error('Cannot find MIDI track information');
unset($thisfile_midi);
unset($info['fileformat']);
return false;
}
if ($this->scanwholefile) { // this can take quite a long time, so have the option to bypass it if speed is very important
$thisfile_midi['totalticks'] = 0;
$info['playtime_seconds'] = 0;
$CurrentMicroSecondsPerBeat = 500000; // 120 beats per minute; 60,000,000 microseconds per minute -> 500,000 microseconds per beat
$CurrentBeatsPerMinute = 120; // 120 beats per minute; 60,000,000 microseconds per minute -> 500,000 microseconds per beat
$MicroSecondsPerQuarterNoteAfter = array ();
$MIDIevents = array();
foreach ($trackdataarray as $tracknumber => $trackdata) {
$eventsoffset = 0;
$LastIssuedMIDIcommand = 0;
$LastIssuedMIDIchannel = 0;
$CumulativeDeltaTime = 0;
$TicksAtCurrentBPM = 0;
while ($eventsoffset < strlen($trackdata)) {
$eventid = 0;
if (isset($MIDIevents[$tracknumber]) && is_array($MIDIevents[$tracknumber])) {
$eventid = count($MIDIevents[$tracknumber]);
}
$deltatime = 0;
for ($i = 0; $i < 4; $i++) {
$deltatimebyte = ord(substr($trackdata, $eventsoffset++, 1));
$deltatime = ($deltatime << 7) + ($deltatimebyte & 0x7F);
if ($deltatimebyte & 0x80) {
// another byte follows
} else {
break;
}
}
$CumulativeDeltaTime += $deltatime;
$TicksAtCurrentBPM += $deltatime;
$MIDIevents[$tracknumber][$eventid]['deltatime'] = $deltatime;
$MIDI_event_channel = ord(substr($trackdata, $eventsoffset++, 1));
if ($MIDI_event_channel & 0x80) {
// OK, normal event - MIDI command has MSB set
$LastIssuedMIDIcommand = $MIDI_event_channel >> 4;
$LastIssuedMIDIchannel = $MIDI_event_channel & 0x0F;
} else {
// running event - assume last command
$eventsoffset--;
}
$MIDIevents[$tracknumber][$eventid]['eventid'] = $LastIssuedMIDIcommand;
$MIDIevents[$tracknumber][$eventid]['channel'] = $LastIssuedMIDIchannel;
if ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x08) { // Note off (key is released)
$notenumber = ord(substr($trackdata, $eventsoffset++, 1));
$velocity = ord(substr($trackdata, $eventsoffset++, 1));
} elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x09) { // Note on (key is pressed)
$notenumber = ord(substr($trackdata, $eventsoffset++, 1));
$velocity = ord(substr($trackdata, $eventsoffset++, 1));
} elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x0A) { // Key after-touch
$notenumber = ord(substr($trackdata, $eventsoffset++, 1));
$velocity = ord(substr($trackdata, $eventsoffset++, 1));
} elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x0B) { // Control Change
$controllernum = ord(substr($trackdata, $eventsoffset++, 1));
$newvalue = ord(substr($trackdata, $eventsoffset++, 1));
} elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x0C) { // Program (patch) change
$newprogramnum = ord(substr($trackdata, $eventsoffset++, 1));
$thisfile_midi_raw['track'][$tracknumber]['instrumentid'] = $newprogramnum;
if ($tracknumber == 10) {
$thisfile_midi_raw['track'][$tracknumber]['instrument'] = $this->GeneralMIDIpercussionLookup($newprogramnum);
} else {
$thisfile_midi_raw['track'][$tracknumber]['instrument'] = $this->GeneralMIDIinstrumentLookup($newprogramnum);
}
} elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x0D) { // Channel after-touch
$channelnumber = ord(substr($trackdata, $eventsoffset++, 1));
} elseif ($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x0E) { // Pitch wheel change (2000H is normal or no change)
$changeLSB = ord(substr($trackdata, $eventsoffset++, 1));
$changeMSB = ord(substr($trackdata, $eventsoffset++, 1));
$pitchwheelchange = (($changeMSB & 0x7F) << 7) & ($changeLSB & 0x7F);
} elseif (($MIDIevents[$tracknumber][$eventid]['eventid'] == 0x0F) && ($MIDIevents[$tracknumber][$eventid]['channel'] == 0x0F)) {
$METAeventCommand = ord(substr($trackdata, $eventsoffset++, 1));
$METAeventLength = ord(substr($trackdata, $eventsoffset++, 1));
$METAeventData = substr($trackdata, $eventsoffset, $METAeventLength);
$eventsoffset += $METAeventLength;
switch ($METAeventCommand) {
case 0x00: // Set track sequence number
$track_sequence_number = getid3_lib::BigEndian2Int(substr($METAeventData, 0, $METAeventLength));
//$thisfile_midi_raw['events'][$tracknumber][$eventid]['seqno'] = $track_sequence_number;
break;
case 0x01: // Text: generic
$text_generic = substr($METAeventData, 0, $METAeventLength);
//$thisfile_midi_raw['events'][$tracknumber][$eventid]['text'] = $text_generic;
$thisfile_midi['comments']['comment'][] = $text_generic;
break;
case 0x02: // Text: copyright
$text_copyright = substr($METAeventData, 0, $METAeventLength);
//$thisfile_midi_raw['events'][$tracknumber][$eventid]['copyright'] = $text_copyright;
$thisfile_midi['comments']['copyright'][] = $text_copyright;
break;
case 0x03: // Text: track name
$text_trackname = substr($METAeventData, 0, $METAeventLength);
$thisfile_midi_raw['track'][$tracknumber]['name'] = $text_trackname;
break;
case 0x04: // Text: track instrument name
$text_instrument = substr($METAeventData, 0, $METAeventLength);
//$thisfile_midi_raw['events'][$tracknumber][$eventid]['instrument'] = $text_instrument;
break;
case 0x05: // Text: lyrics
$text_lyrics = substr($METAeventData, 0, $METAeventLength);
//$thisfile_midi_raw['events'][$tracknumber][$eventid]['lyrics'] = $text_lyrics;
if (!isset($thisfile_midi['lyrics'])) {
$thisfile_midi['lyrics'] = '';
}
$thisfile_midi['lyrics'] .= $text_lyrics."\n";
break;
case 0x06: // Text: marker
$text_marker = substr($METAeventData, 0, $METAeventLength);
//$thisfile_midi_raw['events'][$tracknumber][$eventid]['marker'] = $text_marker;
break;
case 0x07: // Text: cue point
$text_cuepoint = substr($METAeventData, 0, $METAeventLength);
//$thisfile_midi_raw['events'][$tracknumber][$eventid]['cuepoint'] = $text_cuepoint;
break;
case 0x2F: // End Of Track
//$thisfile_midi_raw['events'][$tracknumber][$eventid]['EOT'] = $CumulativeDeltaTime;
break;
case 0x51: // Tempo: microseconds / quarter note
$CurrentMicroSecondsPerBeat = getid3_lib::BigEndian2Int(substr($METAeventData, 0, $METAeventLength));
if ($CurrentMicroSecondsPerBeat == 0) {
$this->error('Corrupt MIDI file: CurrentMicroSecondsPerBeat == zero');
return false;
}
$thisfile_midi_raw['events'][$tracknumber][$CumulativeDeltaTime]['us_qnote'] = $CurrentMicroSecondsPerBeat;
$CurrentBeatsPerMinute = (1000000 / $CurrentMicroSecondsPerBeat) * 60;
$MicroSecondsPerQuarterNoteAfter[$CumulativeDeltaTime] = $CurrentMicroSecondsPerBeat;
$TicksAtCurrentBPM = 0;
break;
case 0x58: // Time signature
$timesig_numerator = getid3_lib::BigEndian2Int($METAeventData[0]);
$timesig_denominator = pow(2, getid3_lib::BigEndian2Int($METAeventData[1])); // $02 -> x/4, $03 -> x/8, etc
$timesig_32inqnote = getid3_lib::BigEndian2Int($METAeventData[2]); // number of 32nd notes to the quarter note
//$thisfile_midi_raw['events'][$tracknumber][$eventid]['timesig_32inqnote'] = $timesig_32inqnote;
//$thisfile_midi_raw['events'][$tracknumber][$eventid]['timesig_numerator'] = $timesig_numerator;
//$thisfile_midi_raw['events'][$tracknumber][$eventid]['timesig_denominator'] = $timesig_denominator;
//$thisfile_midi_raw['events'][$tracknumber][$eventid]['timesig_text'] = $timesig_numerator.'/'.$timesig_denominator;
$thisfile_midi['timesignature'][] = $timesig_numerator.'/'.$timesig_denominator;
break;
case 0x59: // Keysignature
$keysig_sharpsflats = getid3_lib::BigEndian2Int($METAeventData[0]);
if ($keysig_sharpsflats & 0x80) {
// (-7 -> 7 flats, 0 ->key of C, 7 -> 7 sharps)
$keysig_sharpsflats -= 256;
}
$keysig_majorminor = getid3_lib::BigEndian2Int($METAeventData[1]); // 0 -> major, 1 -> minor
$keysigs = array(-7=>'Cb', -6=>'Gb', -5=>'Db', -4=>'Ab', -3=>'Eb', -2=>'Bb', -1=>'F', 0=>'C', 1=>'G', 2=>'D', 3=>'A', 4=>'E', 5=>'B', 6=>'F#', 7=>'C#');
//$thisfile_midi_raw['events'][$tracknumber][$eventid]['keysig_sharps'] = (($keysig_sharpsflats > 0) ? abs($keysig_sharpsflats) : 0);
//$thisfile_midi_raw['events'][$tracknumber][$eventid]['keysig_flats'] = (($keysig_sharpsflats < 0) ? abs($keysig_sharpsflats) : 0);
//$thisfile_midi_raw['events'][$tracknumber][$eventid]['keysig_minor'] = (bool) $keysig_majorminor;
//$thisfile_midi_raw['events'][$tracknumber][$eventid]['keysig_text'] = $keysigs[$keysig_sharpsflats].' '.($thisfile_midi_raw['events'][$tracknumber][$eventid]['keysig_minor'] ? 'minor' : 'major');
// $keysigs[$keysig_sharpsflats] gets an int key (correct) - $keysigs["$keysig_sharpsflats"] gets a string key (incorrect)
$thisfile_midi['keysignature'][] = $keysigs[$keysig_sharpsflats].' '.((bool) $keysig_majorminor ? 'minor' : 'major');
break;
case 0x7F: // Sequencer specific information
$custom_data = substr($METAeventData, 0, $METAeventLength);
break;
default:
$this->warning('Unhandled META Event Command: '.$METAeventCommand);
break;
}
} else {
$this->warning('Unhandled MIDI Event ID: '.$MIDIevents[$tracknumber][$eventid]['eventid'].' + Channel ID: '.$MIDIevents[$tracknumber][$eventid]['channel']);
}
}
if (($tracknumber > 0) || (count($trackdataarray) == 1)) {
$thisfile_midi['totalticks'] = max($thisfile_midi['totalticks'], $CumulativeDeltaTime);
}
}
$previoustickoffset = null;
$prevmicrosecondsperbeat = null;
ksort($MicroSecondsPerQuarterNoteAfter);
foreach ($MicroSecondsPerQuarterNoteAfter as $tickoffset => $microsecondsperbeat) {
if (is_null($previoustickoffset)) {
$prevmicrosecondsperbeat = $microsecondsperbeat;
$previoustickoffset = $tickoffset;
continue;
}
if ($thisfile_midi['totalticks'] > $tickoffset) {
if ($thisfile_midi_raw['ticksperqnote'] == 0) {
$this->error('Corrupt MIDI file: ticksperqnote == zero');
return false;
}
$info['playtime_seconds'] += (($tickoffset - $previoustickoffset) / $thisfile_midi_raw['ticksperqnote']) * ($prevmicrosecondsperbeat / 1000000);
$prevmicrosecondsperbeat = $microsecondsperbeat;
$previoustickoffset = $tickoffset;
}
}
if ($thisfile_midi['totalticks'] > $previoustickoffset) {
if ($thisfile_midi_raw['ticksperqnote'] == 0) {
$this->error('Corrupt MIDI file: ticksperqnote == zero');
return false;
}
$info['playtime_seconds'] += (($thisfile_midi['totalticks'] - $previoustickoffset) / $thisfile_midi_raw['ticksperqnote']) * ($prevmicrosecondsperbeat / 1000000);
}
}
if (!empty($info['playtime_seconds'])) {
$info['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
}
if (!empty($thisfile_midi['lyrics'])) {
$thisfile_midi['comments']['lyrics'][] = $thisfile_midi['lyrics'];
}
return true;
}
/**
* @param int $instrumentid
*
* @return string
*/
public function GeneralMIDIinstrumentLookup($instrumentid) {
$begin = __LINE__;
/** This is not a comment!
0 Acoustic Grand
1 Bright Acoustic
2 Electric Grand
3 Honky-Tonk
4 Electric Piano 1
5 Electric Piano 2
6 Harpsichord
7 Clavier
8 Celesta
9 Glockenspiel
10 Music Box
11 Vibraphone
12 Marimba
13 Xylophone
14 Tubular Bells
15 Dulcimer
16 Drawbar Organ
17 Percussive Organ
18 Rock Organ
19 Church Organ
20 Reed Organ
21 Accordian
22 Harmonica
23 Tango Accordian
24 Acoustic Guitar (nylon)
25 Acoustic Guitar (steel)
26 Electric Guitar (jazz)
27 Electric Guitar (clean)
28 Electric Guitar (muted)
29 Overdriven Guitar
30 Distortion Guitar
31 Guitar Harmonics
32 Acoustic Bass
33 Electric Bass (finger)
34 Electric Bass (pick)
35 Fretless Bass
36 Slap Bass 1
37 Slap Bass 2
38 Synth Bass 1
39 Synth Bass 2
40 Violin
41 Viola
42 Cello
43 Contrabass
44 Tremolo Strings
45 Pizzicato Strings
46 Orchestral Strings
47 Timpani
48 String Ensemble 1
49 String Ensemble 2
50 SynthStrings 1
51 SynthStrings 2
52 Choir Aahs
53 Voice Oohs
54 Synth Voice
55 Orchestra Hit
56 Trumpet
57 Trombone
58 Tuba
59 Muted Trumpet
60 French Horn
61 Brass Section
62 SynthBrass 1
63 SynthBrass 2
64 Soprano Sax
65 Alto Sax
66 Tenor Sax
67 Baritone Sax
68 Oboe
69 English Horn
70 Bassoon
71 Clarinet
72 Piccolo
73 Flute
74 Recorder
75 Pan Flute
76 Blown Bottle
77 Shakuhachi
78 Whistle
79 Ocarina
80 Lead 1 (square)
81 Lead 2 (sawtooth)
82 Lead 3 (calliope)
83 Lead 4 (chiff)
84 Lead 5 (charang)
85 Lead 6 (voice)
86 Lead 7 (fifths)
87 Lead 8 (bass + lead)
88 Pad 1 (new age)
89 Pad 2 (warm)
90 Pad 3 (polysynth)
91 Pad 4 (choir)
92 Pad 5 (bowed)
93 Pad 6 (metallic)
94 Pad 7 (halo)
95 Pad 8 (sweep)
96 FX 1 (rain)
97 FX 2 (soundtrack)
98 FX 3 (crystal)
99 FX 4 (atmosphere)
100 FX 5 (brightness)
101 FX 6 (goblins)
102 FX 7 (echoes)
103 FX 8 (sci-fi)
104 Sitar
105 Banjo
106 Shamisen
107 Koto
108 Kalimba
109 Bagpipe
110 Fiddle
111 Shanai
112 Tinkle Bell
113 Agogo
114 Steel Drums
115 Woodblock
116 Taiko Drum
117 Melodic Tom
118 Synth Drum
119 Reverse Cymbal
120 Guitar Fret Noise
121 Breath Noise
122 Seashore
123 Bird Tweet
124 Telephone Ring
125 Helicopter
126 Applause
127 Gunshot
*/
return getid3_lib::EmbeddedLookup($instrumentid, $begin, __LINE__, __FILE__, 'GeneralMIDIinstrument');
}
/**
* @param int $instrumentid
*
* @return string
*/
public function GeneralMIDIpercussionLookup($instrumentid) {
$begin = __LINE__;
/** This is not a comment!
35 Acoustic Bass Drum
36 Bass Drum 1
37 Side Stick
38 Acoustic Snare
39 Hand Clap
40 Electric Snare
41 Low Floor Tom
42 Closed Hi-Hat
43 High Floor Tom
44 Pedal Hi-Hat
45 Low Tom
46 Open Hi-Hat
47 Low-Mid Tom
48 Hi-Mid Tom
49 Crash Cymbal 1
50 High Tom
51 Ride Cymbal 1
52 Chinese Cymbal
53 Ride Bell
54 Tambourine
55 Splash Cymbal
56 Cowbell
57 Crash Cymbal 2
59 Ride Cymbal 2
60 Hi Bongo
61 Low Bongo
62 Mute Hi Conga
63 Open Hi Conga
64 Low Conga
65 High Timbale
66 Low Timbale
67 High Agogo
68 Low Agogo
69 Cabasa
70 Maracas
71 Short Whistle
72 Long Whistle
73 Short Guiro
74 Long Guiro
75 Claves
76 Hi Wood Block
77 Low Wood Block
78 Mute Cuica
79 Open Cuica
80 Mute Triangle
81 Open Triangle
*/
return getid3_lib::EmbeddedLookup($instrumentid, $begin, __LINE__, __FILE__, 'GeneralMIDIpercussion');
}
}
<?php
/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org> //
// available at https://github.com/JamesHeinrich/getID3 //
// or https://www.getid3.org //
// or http://getid3.sourceforge.net //
// see readme.txt for more details //
/////////////////////////////////////////////////////////////////
// //
// module.audio.mod.php //
// module for analyzing MOD Audio files //
// dependencies: NONE //
// ///
/////////////////////////////////////////////////////////////////
if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
exit;
}
class getid3_mod extends getid3_handler
{
/**
* @return bool
*/
public function Analyze() {
$info = &$this->getid3->info;
$this->fseek($info['avdataoffset']);
$fileheader = $this->fread(1088);
if (preg_match('#^IMPM#', $fileheader)) {
return $this->getITheaderFilepointer();
} elseif (preg_match('#^Extended Module#', $fileheader)) {
return $this->getXMheaderFilepointer();
} elseif (preg_match('#^.{44}SCRM#', $fileheader)) {
return $this->getS3MheaderFilepointer();
} elseif (preg_match('#^.{1080}(M\\.K\\.|M!K!|FLT4|FLT8|[5-9]CHN|[1-3][0-9]CH)#', $fileheader)) {
return $this->getMODheaderFilepointer();
}
$this->error('This is not a known type of MOD file');
return false;
}
/**
* @return bool
*/
public function getMODheaderFilepointer() {
$info = &$this->getid3->info;
$this->fseek($info['avdataoffset'] + 1080);
$FormatID = $this->fread(4);
if (!preg_match('#^(M.K.|[5-9]CHN|[1-3][0-9]CH)$#', $FormatID)) {
$this->error('This is not a known type of MOD file');
return false;
}
$info['fileformat'] = 'mod';
$this->error('MOD parsing not enabled in this version of getID3() ['.$this->getid3->version().']');
return false;
}
/**
* @return bool
*/
public function getXMheaderFilepointer() {
$info = &$this->getid3->info;
$this->fseek($info['avdataoffset']);
$FormatID = $this->fread(15);
if (!preg_match('#^Extended Module$#', $FormatID)) {
$this->error('This is not a known type of XM-MOD file');
return false;
}
$info['fileformat'] = 'xm';
$this->error('XM-MOD parsing not enabled in this version of getID3() ['.$this->getid3->version().']');
return false;
}
/**
* @return bool
*/
public function getS3MheaderFilepointer() {
$info = &$this->getid3->info;
$this->fseek($info['avdataoffset'] + 44);
$FormatID = $this->fread(4);
if (!preg_match('#^SCRM$#', $FormatID)) {
$this->error('This is not a ScreamTracker MOD file');
return false;
}
$info['fileformat'] = 's3m';
$this->error('ScreamTracker parsing not enabled in this version of getID3() ['.$this->getid3->version().']');
return false;
}
/**
* @return bool
*/
public function getITheaderFilepointer() {
$info = &$this->getid3->info;
$this->fseek($info['avdataoffset']);
$FormatID = $this->fread(4);
if (!preg_match('#^IMPM$#', $FormatID)) {
$this->error('This is not an ImpulseTracker MOD file');
return false;
}
$info['fileformat'] = 'it';
$this->error('ImpulseTracker parsing not enabled in this version of getID3() ['.$this->getid3->version().']');
return false;
}
}
<?php
/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org> //
// available at https://github.com/JamesHeinrich/getID3 //
// or https://www.getid3.org //
// or http://getid3.sourceforge.net //
// see readme.txt for more details //
/////////////////////////////////////////////////////////////////
// //
// module.audio.monkey.php //
// module for analyzing Monkey's Audio files //
// dependencies: NONE //
// ///
/////////////////////////////////////////////////////////////////
if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
exit;
}
class getid3_monkey extends getid3_handler
{
/**
* @return bool
*/
public function Analyze() {
$info = &$this->getid3->info;
// based loosely on code from TMonkey by Jurgen Faul <jfaulØgmx*de>
// http://jfaul.de/atl or http://j-faul.virtualave.net/atl/atl.html
$info['fileformat'] = 'mac';
$info['audio']['dataformat'] = 'mac';
$info['audio']['bitrate_mode'] = 'vbr';
$info['audio']['lossless'] = true;
$info['monkeys_audio']['raw'] = array();
$thisfile_monkeysaudio = &$info['monkeys_audio'];
$thisfile_monkeysaudio_raw = &$thisfile_monkeysaudio['raw'];
$this->fseek($info['avdataoffset']);
$MACheaderData = $this->fread(74);
$thisfile_monkeysaudio_raw['magic'] = substr($MACheaderData, 0, 4);
$magic = 'MAC ';
if ($thisfile_monkeysaudio_raw['magic'] != $magic) {
$this->error('Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($thisfile_monkeysaudio_raw['magic']).'"');
unset($info['fileformat']);
return false;
}
$thisfile_monkeysaudio_raw['nVersion'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, 4, 2)); // appears to be uint32 in 3.98+
if ($thisfile_monkeysaudio_raw['nVersion'] < 3980) {
$thisfile_monkeysaudio_raw['nCompressionLevel'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, 6, 2));
$thisfile_monkeysaudio_raw['nFormatFlags'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, 8, 2));
$thisfile_monkeysaudio_raw['nChannels'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, 10, 2));
$thisfile_monkeysaudio_raw['nSampleRate'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, 12, 4));
$thisfile_monkeysaudio_raw['nHeaderDataBytes'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, 16, 4));
$thisfile_monkeysaudio_raw['nWAVTerminatingBytes'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, 20, 4));
$thisfile_monkeysaudio_raw['nTotalFrames'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, 24, 4));
$thisfile_monkeysaudio_raw['nFinalFrameSamples'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, 28, 4));
$thisfile_monkeysaudio_raw['nPeakLevel'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, 32, 4));
$thisfile_monkeysaudio_raw['nSeekElements'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, 38, 2));
$offset = 8;
} else {
$offset = 8;
// APE_DESCRIPTOR
$thisfile_monkeysaudio_raw['nDescriptorBytes'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 4));
$offset += 4;
$thisfile_monkeysaudio_raw['nHeaderBytes'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 4));
$offset += 4;
$thisfile_monkeysaudio_raw['nSeekTableBytes'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 4));
$offset += 4;
$thisfile_monkeysaudio_raw['nHeaderDataBytes'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 4));
$offset += 4;
$thisfile_monkeysaudio_raw['nAPEFrameDataBytes'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 4));
$offset += 4;
$thisfile_monkeysaudio_raw['nAPEFrameDataBytesHigh'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 4));
$offset += 4;
$thisfile_monkeysaudio_raw['nTerminatingDataBytes'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 4));
$offset += 4;
$thisfile_monkeysaudio_raw['cFileMD5'] = substr($MACheaderData, $offset, 16);
$offset += 16;
// APE_HEADER
$thisfile_monkeysaudio_raw['nCompressionLevel'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 2));
$offset += 2;
$thisfile_monkeysaudio_raw['nFormatFlags'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 2));
$offset += 2;
$thisfile_monkeysaudio_raw['nBlocksPerFrame'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 4));
$offset += 4;
$thisfile_monkeysaudio_raw['nFinalFrameBlocks'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 4));
$offset += 4;
$thisfile_monkeysaudio_raw['nTotalFrames'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 4));
$offset += 4;
$thisfile_monkeysaudio_raw['nBitsPerSample'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 2));
$offset += 2;
$thisfile_monkeysaudio_raw['nChannels'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 2));
$offset += 2;
$thisfile_monkeysaudio_raw['nSampleRate'] = getid3_lib::LittleEndian2Int(substr($MACheaderData, $offset, 4));
$offset += 4;
}
$thisfile_monkeysaudio['flags']['8-bit'] = (bool) ($thisfile_monkeysaudio_raw['nFormatFlags'] & 0x0001);
$thisfile_monkeysaudio['flags']['crc-32'] = (bool) ($thisfile_monkeysaudio_raw['nFormatFlags'] & 0x0002);
$thisfile_monkeysaudio['flags']['peak_level'] = (bool) ($thisfile_monkeysaudio_raw['nFormatFlags'] & 0x0004);
$thisfile_monkeysaudio['flags']['24-bit'] = (bool) ($thisfile_monkeysaudio_raw['nFormatFlags'] & 0x0008);
$thisfile_monkeysaudio['flags']['seek_elements'] = (bool) ($thisfile_monkeysaudio_raw['nFormatFlags'] & 0x0010);
$thisfile_monkeysaudio['flags']['no_wav_header'] = (bool) ($thisfile_monkeysaudio_raw['nFormatFlags'] & 0x0020);
$thisfile_monkeysaudio['version'] = $thisfile_monkeysaudio_raw['nVersion'] / 1000;
$thisfile_monkeysaudio['compression'] = $this->MonkeyCompressionLevelNameLookup($thisfile_monkeysaudio_raw['nCompressionLevel']);
if ($thisfile_monkeysaudio_raw['nVersion'] < 3980) {
$thisfile_monkeysaudio['samples_per_frame'] = $this->MonkeySamplesPerFrame($thisfile_monkeysaudio_raw['nVersion'], $thisfile_monkeysaudio_raw['nCompressionLevel']);
}
$thisfile_monkeysaudio['bits_per_sample'] = ($thisfile_monkeysaudio['flags']['24-bit'] ? 24 : ($thisfile_monkeysaudio['flags']['8-bit'] ? 8 : 16));
$thisfile_monkeysaudio['channels'] = $thisfile_monkeysaudio_raw['nChannels'];
$info['audio']['channels'] = $thisfile_monkeysaudio['channels'];
$thisfile_monkeysaudio['sample_rate'] = $thisfile_monkeysaudio_raw['nSampleRate'];
if ($thisfile_monkeysaudio['sample_rate'] == 0) {
$this->error('Corrupt MAC file: frequency == zero');
return false;
}
$info['audio']['sample_rate'] = $thisfile_monkeysaudio['sample_rate'];
if ($thisfile_monkeysaudio['flags']['peak_level']) {
$thisfile_monkeysaudio['peak_level'] = $thisfile_monkeysaudio_raw['nPeakLevel'];
$thisfile_monkeysaudio['peak_ratio'] = $thisfile_monkeysaudio['peak_level'] / pow(2, $thisfile_monkeysaudio['bits_per_sample'] - 1);
}
if ($thisfile_monkeysaudio_raw['nVersion'] >= 3980) {
$thisfile_monkeysaudio['samples'] = (($thisfile_monkeysaudio_raw['nTotalFrames'] - 1) * $thisfile_monkeysaudio_raw['nBlocksPerFrame']) + $thisfile_monkeysaudio_raw['nFinalFrameBlocks'];
} else {
$thisfile_monkeysaudio['samples'] = (($thisfile_monkeysaudio_raw['nTotalFrames'] - 1) * $thisfile_monkeysaudio['samples_per_frame']) + $thisfile_monkeysaudio_raw['nFinalFrameSamples'];
}
$thisfile_monkeysaudio['playtime'] = $thisfile_monkeysaudio['samples'] / $thisfile_monkeysaudio['sample_rate'];
if ($thisfile_monkeysaudio['playtime'] == 0) {
$this->error('Corrupt MAC file: playtime == zero');
return false;
}
$info['playtime_seconds'] = $thisfile_monkeysaudio['playtime'];
$thisfile_monkeysaudio['compressed_size'] = $info['avdataend'] - $info['avdataoffset'];
$thisfile_monkeysaudio['uncompressed_size'] = $thisfile_monkeysaudio['samples'] * $thisfile_monkeysaudio['channels'] * ($thisfile_monkeysaudio['bits_per_sample'] / 8);
if ($thisfile_monkeysaudio['uncompressed_size'] == 0) {
$this->error('Corrupt MAC file: uncompressed_size == zero');
return false;
}
$thisfile_monkeysaudio['compression_ratio'] = $thisfile_monkeysaudio['compressed_size'] / ($thisfile_monkeysaudio['uncompressed_size'] + $thisfile_monkeysaudio_raw['nHeaderDataBytes']);
$thisfile_monkeysaudio['bitrate'] = (($thisfile_monkeysaudio['samples'] * $thisfile_monkeysaudio['channels'] * $thisfile_monkeysaudio['bits_per_sample']) / $thisfile_monkeysaudio['playtime']) * $thisfile_monkeysaudio['compression_ratio'];
$info['audio']['bitrate'] = $thisfile_monkeysaudio['bitrate'];
// add size of MAC header to avdataoffset
if ($thisfile_monkeysaudio_raw['nVersion'] >= 3980) {
$info['avdataoffset'] += $thisfile_monkeysaudio_raw['nDescriptorBytes'];
$info['avdataoffset'] += $thisfile_monkeysaudio_raw['nHeaderBytes'];
$info['avdataoffset'] += $thisfile_monkeysaudio_raw['nSeekTableBytes'];
$info['avdataoffset'] += $thisfile_monkeysaudio_raw['nHeaderDataBytes'];
$info['avdataend'] -= $thisfile_monkeysaudio_raw['nTerminatingDataBytes'];
} else {
$info['avdataoffset'] += $offset;
}
if ($thisfile_monkeysaudio_raw['nVersion'] >= 3980) {
if ($thisfile_monkeysaudio_raw['cFileMD5'] === str_repeat("\x00", 16)) {
//$this->warning('cFileMD5 is null');
} else {
$info['md5_data_source'] = '';
$md5 = $thisfile_monkeysaudio_raw['cFileMD5'];
for ($i = 0; $i < strlen($md5); $i++) {
$info['md5_data_source'] .= str_pad(dechex(ord($md5[$i])), 2, '00', STR_PAD_LEFT);
}
if (!preg_match('/^[0-9a-f]{32}$/', $info['md5_data_source'])) {
unset($info['md5_data_source']);
}
}
}
$info['audio']['bits_per_sample'] = $thisfile_monkeysaudio['bits_per_sample'];
$info['audio']['encoder'] = 'MAC v'.number_format($thisfile_monkeysaudio['version'], 2);
$info['audio']['encoder_options'] = ucfirst($thisfile_monkeysaudio['compression']).' compression';
return true;
}
/**
* @param int $compressionlevel
*
* @return string
*/
public function MonkeyCompressionLevelNameLookup($compressionlevel) {
static $MonkeyCompressionLevelNameLookup = array(
0 => 'unknown',
1000 => 'fast',
2000 => 'normal',
3000 => 'high',
4000 => 'extra-high',
5000 => 'insane'
);
return (isset($MonkeyCompressionLevelNameLookup[$compressionlevel]) ? $MonkeyCompressionLevelNameLookup[$compressionlevel] : 'invalid');
}
/**
* @param int $versionid
* @param int $compressionlevel
*
* @return int
*/
public function MonkeySamplesPerFrame($versionid, $compressionlevel) {
if ($versionid >= 3950) {
return 73728 * 4;
} elseif ($versionid >= 3900) {
return 73728;
} elseif (($versionid >= 3800) && ($compressionlevel == 4000)) {
return 73728;
} else {
return 9216;
}
}
}
<?php
/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org> //
// available at https://github.com/JamesHeinrich/getID3 //
// or https://www.getid3.org //
// or http://getid3.sourceforge.net //
// see readme.txt for more details //
/////////////////////////////////////////////////////////////////
// //
// module.audio.mp3.php //
// module for analyzing MP3 files //
// dependencies: NONE //
// ///
/////////////////////////////////////////////////////////////////
if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
exit;
}
// number of frames to scan to determine if MPEG-audio sequence is valid
// Lower this number to 5-20 for faster scanning
// Increase this number to 50+ for most accurate detection of valid VBR/CBR
// mpeg-audio streams
define('GETID3_MP3_VALID_CHECK_FRAMES', 35);
class getid3_mp3 extends getid3_handler
{
/**
* Forces getID3() to scan the file byte-by-byte and log all the valid audio frame headers - extremely slow,
* unrecommended, but may provide data from otherwise-unusable files.
*
* @var bool
*/
public $allow_bruteforce = false;
/**
* @return bool
*/
public function Analyze() {
$info = &$this->getid3->info;
$initialOffset = $info['avdataoffset'];
if (!$this->getOnlyMPEGaudioInfo($info['avdataoffset'])) {
if ($this->allow_bruteforce) {
$this->error('Rescanning file in BruteForce mode');
$this->getOnlyMPEGaudioInfoBruteForce();
}
}
if (isset($info['mpeg']['audio']['bitrate_mode'])) {
$info['audio']['bitrate_mode'] = strtolower($info['mpeg']['audio']['bitrate_mode']);
}
if (((isset($info['id3v2']['headerlength']) && ($info['avdataoffset'] > $info['id3v2']['headerlength'])) || (!isset($info['id3v2']) && ($info['avdataoffset'] > 0) && ($info['avdataoffset'] != $initialOffset)))) {
$synchoffsetwarning = 'Unknown data before synch ';
if (isset($info['id3v2']['headerlength'])) {
$synchoffsetwarning .= '(ID3v2 header ends at '.$info['id3v2']['headerlength'].', then '.($info['avdataoffset'] - $info['id3v2']['headerlength']).' bytes garbage, ';
} elseif ($initialOffset > 0) {
$synchoffsetwarning .= '(should be at '.$initialOffset.', ';
} else {
$synchoffsetwarning .= '(should be at beginning of file, ';
}
$synchoffsetwarning .= 'synch detected at '.$info['avdataoffset'].')';
if (isset($info['audio']['bitrate_mode']) && ($info['audio']['bitrate_mode'] == 'cbr')) {
if (!empty($info['id3v2']['headerlength']) && (($info['avdataoffset'] - $info['id3v2']['headerlength']) == $info['mpeg']['audio']['framelength'])) {
$synchoffsetwarning .= '. This is a known problem with some versions of LAME (3.90-3.92) DLL in CBR mode.';
$info['audio']['codec'] = 'LAME';
$CurrentDataLAMEversionString = 'LAME3.';
} elseif (empty($info['id3v2']['headerlength']) && ($info['avdataoffset'] == $info['mpeg']['audio']['framelength'])) {
$synchoffsetwarning .= '. This is a known problem with some versions of LAME (3.90 - 3.92) DLL in CBR mode.';
$info['audio']['codec'] = 'LAME';
$CurrentDataLAMEversionString = 'LAME3.';
}
}
$this->warning($synchoffsetwarning);
}
if (isset($info['mpeg']['audio']['LAME'])) {
$info['audio']['codec'] = 'LAME';
if (!empty($info['mpeg']['audio']['LAME']['long_version'])) {
$info['audio']['encoder'] = rtrim($info['mpeg']['audio']['LAME']['long_version'], "\x00");
} elseif (!empty($info['mpeg']['audio']['LAME']['short_version'])) {
$info['audio']['encoder'] = rtrim($info['mpeg']['audio']['LAME']['short_version'], "\x00");
}
}
$CurrentDataLAMEversionString = (!empty($CurrentDataLAMEversionString) ? $CurrentDataLAMEversionString : (isset($info['audio']['encoder']) ? $info['audio']['encoder'] : ''));
if (!empty($CurrentDataLAMEversionString) && (substr($CurrentDataLAMEversionString, 0, 6) == 'LAME3.') && !preg_match('[0-9\)]', substr($CurrentDataLAMEversionString, -1))) {
// a version number of LAME that does not end with a number like "LAME3.92"
// or with a closing parenthesis like "LAME3.88 (alpha)"
// or a version of LAME with the LAMEtag-not-filled-in-DLL-mode bug (3.90-3.92)
// not sure what the actual last frame length will be, but will be less than or equal to 1441
$PossiblyLongerLAMEversion_FrameLength = 1441;
// Not sure what version of LAME this is - look in padding of last frame for longer version string
$PossibleLAMEversionStringOffset = $info['avdataend'] - $PossiblyLongerLAMEversion_FrameLength;
$this->fseek($PossibleLAMEversionStringOffset);
$PossiblyLongerLAMEversion_Data = $this->fread($PossiblyLongerLAMEversion_FrameLength);
switch (substr($CurrentDataLAMEversionString, -1)) {
case 'a':
case 'b':
// "LAME3.94a" will have a longer version string of "LAME3.94 (alpha)" for example
// need to trim off "a" to match longer string
$CurrentDataLAMEversionString = substr($CurrentDataLAMEversionString, 0, -1);
break;
}
if (($PossiblyLongerLAMEversion_String = strstr($PossiblyLongerLAMEversion_Data, $CurrentDataLAMEversionString)) !== false) {
if (substr($PossiblyLongerLAMEversion_String, 0, strlen($CurrentDataLAMEversionString)) == $CurrentDataLAMEversionString) {
$PossiblyLongerLAMEversion_NewString = substr($PossiblyLongerLAMEversion_String, 0, strspn($PossiblyLongerLAMEversion_String, 'LAME0123456789., (abcdefghijklmnopqrstuvwxyzJFSOND)')); //"LAME3.90.3" "LAME3.87 (beta 1, Sep 27 2000)" "LAME3.88 (beta)"
if (empty($info['audio']['encoder']) || (strlen($PossiblyLongerLAMEversion_NewString) > strlen($info['audio']['encoder']))) {
$info['audio']['encoder'] = $PossiblyLongerLAMEversion_NewString;
}
}
}
}
if (!empty($info['audio']['encoder'])) {
$info['audio']['encoder'] = rtrim($info['audio']['encoder'], "\x00 ");
}
switch (isset($info['mpeg']['audio']['layer']) ? $info['mpeg']['audio']['layer'] : '') {
case 1:
case 2:
$info['audio']['dataformat'] = 'mp'.$info['mpeg']['audio']['layer'];
break;
}
if (isset($info['fileformat']) && ($info['fileformat'] == 'mp3')) {
switch ($info['audio']['dataformat']) {
case 'mp1':
case 'mp2':
case 'mp3':
$info['fileformat'] = $info['audio']['dataformat'];
break;
default:
$this->warning('Expecting [audio][dataformat] to be mp1/mp2/mp3 when fileformat == mp3, [audio][dataformat] actually "'.$info['audio']['dataformat'].'"');
break;
}
}
if (empty($info['fileformat'])) {
unset($info['fileformat']);
unset($info['audio']['bitrate_mode']);
unset($info['avdataoffset']);
unset($info['avdataend']);
return false;
}
$info['mime_type'] = 'audio/mpeg';
$info['audio']['lossless'] = false;
// Calculate playtime
if (!isset($info['playtime_seconds']) && isset($info['audio']['bitrate']) && ($info['audio']['bitrate'] > 0)) {
// https://github.com/JamesHeinrich/getID3/issues/161
// VBR header frame contains ~0.026s of silent audio data, but is not actually part of the original encoding and should be ignored
$xingVBRheaderFrameLength = ((isset($info['mpeg']['audio']['VBR_frames']) && isset($info['mpeg']['audio']['framelength'])) ? $info['mpeg']['audio']['framelength'] : 0);
$info['playtime_seconds'] = ($info['avdataend'] - $info['avdataoffset'] - $xingVBRheaderFrameLength) * 8 / $info['audio']['bitrate'];
}
$info['audio']['encoder_options'] = $this->GuessEncoderOptions();
return true;
}
/**
* @return string
*/
public function GuessEncoderOptions() {
// shortcuts
$info = &$this->getid3->info;
$thisfile_mpeg_audio = array();
$thisfile_mpeg_audio_lame = array();
if (!empty($info['mpeg']['audio'])) {
$thisfile_mpeg_audio = &$info['mpeg']['audio'];
if (!empty($thisfile_mpeg_audio['LAME'])) {
$thisfile_mpeg_audio_lame = &$thisfile_mpeg_audio['LAME'];
}
}
$encoder_options = '';
static $NamedPresetBitrates = array(16, 24, 40, 56, 112, 128, 160, 192, 256);
if (isset($thisfile_mpeg_audio['VBR_method']) && ($thisfile_mpeg_audio['VBR_method'] == 'Fraunhofer') && !empty($thisfile_mpeg_audio['VBR_quality'])) {
$encoder_options = 'VBR q'.$thisfile_mpeg_audio['VBR_quality'];
} elseif (!empty($thisfile_mpeg_audio_lame['preset_used']) && isset($thisfile_mpeg_audio_lame['preset_used_id']) && (!in_array($thisfile_mpeg_audio_lame['preset_used_id'], $NamedPresetBitrates))) {
$encoder_options = $thisfile_mpeg_audio_lame['preset_used'];
} elseif (!empty($thisfile_mpeg_audio_lame['vbr_quality'])) {
static $KnownEncoderValues = array();
if (empty($KnownEncoderValues)) {
//$KnownEncoderValues[abrbitrate_minbitrate][vbr_quality][raw_vbr_method][raw_noise_shaping][raw_stereo_mode][ath_type][lowpass_frequency] = 'preset name';
$KnownEncoderValues[0xFF][58][1][1][3][2][20500] = '--alt-preset insane'; // 3.90, 3.90.1, 3.92
$KnownEncoderValues[0xFF][58][1][1][3][2][20600] = '--alt-preset insane'; // 3.90.2, 3.90.3, 3.91
$KnownEncoderValues[0xFF][57][1][1][3][4][20500] = '--alt-preset insane'; // 3.94, 3.95
$KnownEncoderValues['**'][78][3][2][3][2][19500] = '--alt-preset extreme'; // 3.90, 3.90.1, 3.92
$KnownEncoderValues['**'][78][3][2][3][2][19600] = '--alt-preset extreme'; // 3.90.2, 3.91
$KnownEncoderValues['**'][78][3][1][3][2][19600] = '--alt-preset extreme'; // 3.90.3
$KnownEncoderValues['**'][78][4][2][3][2][19500] = '--alt-preset fast extreme'; // 3.90, 3.90.1, 3.92
$KnownEncoderValues['**'][78][4][2][3][2][19600] = '--alt-preset fast extreme'; // 3.90.2, 3.90.3, 3.91
$KnownEncoderValues['**'][78][3][2][3][4][19000] = '--alt-preset standard'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92
$KnownEncoderValues['**'][78][3][1][3][4][19000] = '--alt-preset standard'; // 3.90.3
$KnownEncoderValues['**'][78][4][2][3][4][19000] = '--alt-preset fast standard'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92
$KnownEncoderValues['**'][78][4][1][3][4][19000] = '--alt-preset fast standard'; // 3.90.3
$KnownEncoderValues['**'][88][4][1][3][3][19500] = '--r3mix'; // 3.90, 3.90.1, 3.92
$KnownEncoderValues['**'][88][4][1][3][3][19600] = '--r3mix'; // 3.90.2, 3.90.3, 3.91
$KnownEncoderValues['**'][67][4][1][3][4][18000] = '--r3mix'; // 3.94, 3.95
$KnownEncoderValues['**'][68][3][2][3][4][18000] = '--alt-preset medium'; // 3.90.3
$KnownEncoderValues['**'][68][4][2][3][4][18000] = '--alt-preset fast medium'; // 3.90.3
$KnownEncoderValues[0xFF][99][1][1][1][2][0] = '--preset studio'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92
$KnownEncoderValues[0xFF][58][2][1][3][2][20600] = '--preset studio'; // 3.90.3, 3.93.1
$KnownEncoderValues[0xFF][58][2][1][3][2][20500] = '--preset studio'; // 3.93
$KnownEncoderValues[0xFF][57][2][1][3][4][20500] = '--preset studio'; // 3.94, 3.95
$KnownEncoderValues[0xC0][88][1][1][1][2][0] = '--preset cd'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92
$KnownEncoderValues[0xC0][58][2][2][3][2][19600] = '--preset cd'; // 3.90.3, 3.93.1
$KnownEncoderValues[0xC0][58][2][2][3][2][19500] = '--preset cd'; // 3.93
$KnownEncoderValues[0xC0][57][2][1][3][4][19500] = '--preset cd'; // 3.94, 3.95
$KnownEncoderValues[0xA0][78][1][1][3][2][18000] = '--preset hifi'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92
$KnownEncoderValues[0xA0][58][2][2][3][2][18000] = '--preset hifi'; // 3.90.3, 3.93, 3.93.1
$KnownEncoderValues[0xA0][57][2][1][3][4][18000] = '--preset hifi'; // 3.94, 3.95
$KnownEncoderValues[0x80][67][1][1][3][2][18000] = '--preset tape'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92
$KnownEncoderValues[0x80][67][1][1][3][2][15000] = '--preset radio'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92
$KnownEncoderValues[0x70][67][1][1][3][2][15000] = '--preset fm'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92
$KnownEncoderValues[0x70][58][2][2][3][2][16000] = '--preset tape/radio/fm'; // 3.90.3, 3.93, 3.93.1
$KnownEncoderValues[0x70][57][2][1][3][4][16000] = '--preset tape/radio/fm'; // 3.94, 3.95
$KnownEncoderValues[0x38][58][2][2][0][2][10000] = '--preset voice'; // 3.90.3, 3.93, 3.93.1
$KnownEncoderValues[0x38][57][2][1][0][4][15000] = '--preset voice'; // 3.94, 3.95
$KnownEncoderValues[0x38][57][2][1][0][4][16000] = '--preset voice'; // 3.94a14
$KnownEncoderValues[0x28][65][1][1][0][2][7500] = '--preset mw-us'; // 3.90, 3.90.1, 3.92
$KnownEncoderValues[0x28][65][1][1][0][2][7600] = '--preset mw-us'; // 3.90.2, 3.91
$KnownEncoderValues[0x28][58][2][2][0][2][7000] = '--preset mw-us'; // 3.90.3, 3.93, 3.93.1
$KnownEncoderValues[0x28][57][2][1][0][4][10500] = '--preset mw-us'; // 3.94, 3.95
$KnownEncoderValues[0x28][57][2][1][0][4][11200] = '--preset mw-us'; // 3.94a14
$KnownEncoderValues[0x28][57][2][1][0][4][8800] = '--preset mw-us'; // 3.94a15
$KnownEncoderValues[0x18][58][2][2][0][2][4000] = '--preset phon+/lw/mw-eu/sw'; // 3.90.3, 3.93.1
$KnownEncoderValues[0x18][58][2][2][0][2][3900] = '--preset phon+/lw/mw-eu/sw'; // 3.93
$KnownEncoderValues[0x18][57][2][1][0][4][5900] = '--preset phon+/lw/mw-eu/sw'; // 3.94, 3.95
$KnownEncoderValues[0x18][57][2][1][0][4][6200] = '--preset phon+/lw/mw-eu/sw'; // 3.94a14
$KnownEncoderValues[0x18][57][2][1][0][4][3200] = '--preset phon+/lw/mw-eu/sw'; // 3.94a15
$KnownEncoderValues[0x10][58][2][2][0][2][3800] = '--preset phone'; // 3.90.3, 3.93.1
$KnownEncoderValues[0x10][58][2][2][0][2][3700] = '--preset phone'; // 3.93
$KnownEncoderValues[0x10][57][2][1][0][4][5600] = '--preset phone'; // 3.94, 3.95
}
if (isset($KnownEncoderValues[$thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate']][$thisfile_mpeg_audio_lame['vbr_quality']][$thisfile_mpeg_audio_lame['raw']['vbr_method']][$thisfile_mpeg_audio_lame['raw']['noise_shaping']][$thisfile_mpeg_audio_lame['raw']['stereo_mode']][$thisfile_mpeg_audio_lame['ath_type']][$thisfile_mpeg_audio_lame['lowpass_frequency']])) {
$encoder_options = $KnownEncoderValues[$thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate']][$thisfile_mpeg_audio_lame['vbr_quality']][$thisfile_mpeg_audio_lame['raw']['vbr_method']][$thisfile_mpeg_audio_lame['raw']['noise_shaping']][$thisfile_mpeg_audio_lame['raw']['stereo_mode']][$thisfile_mpeg_audio_lame['ath_type']][$thisfile_mpeg_audio_lame['lowpass_frequency']];
} elseif (isset($KnownEncoderValues['**'][$thisfile_mpeg_audio_lame['vbr_quality']][$thisfile_mpeg_audio_lame['raw']['vbr_method']][$thisfile_mpeg_audio_lame['raw']['noise_shaping']][$thisfile_mpeg_audio_lame['raw']['stereo_mode']][$thisfile_mpeg_audio_lame['ath_type']][$thisfile_mpeg_audio_lame['lowpass_frequency']])) {
$encoder_options = $KnownEncoderValues['**'][$thisfile_mpeg_audio_lame['vbr_quality']][$thisfile_mpeg_audio_lame['raw']['vbr_method']][$thisfile_mpeg_audio_lame['raw']['noise_shaping']][$thisfile_mpeg_audio_lame['raw']['stereo_mode']][$thisfile_mpeg_audio_lame['ath_type']][$thisfile_mpeg_audio_lame['lowpass_frequency']];
} elseif ($info['audio']['bitrate_mode'] == 'vbr') {
// http://gabriel.mp3-tech.org/mp3infotag.html
// int Quality = (100 - 10 * gfp->VBR_q - gfp->quality)h
$LAME_V_value = 10 - ceil($thisfile_mpeg_audio_lame['vbr_quality'] / 10);
$LAME_q_value = 100 - $thisfile_mpeg_audio_lame['vbr_quality'] - ($LAME_V_value * 10);
$encoder_options = '-V'.$LAME_V_value.' -q'.$LAME_q_value;
} elseif ($info['audio']['bitrate_mode'] == 'cbr') {
$encoder_options = strtoupper($info['audio']['bitrate_mode']).ceil($info['audio']['bitrate'] / 1000);
} else {
$encoder_options = strtoupper($info['audio']['bitrate_mode']);
}
} elseif (!empty($thisfile_mpeg_audio_lame['bitrate_abr'])) {
$encoder_options = 'ABR'.$thisfile_mpeg_audio_lame['bitrate_abr'];
} elseif (!empty($info['audio']['bitrate'])) {
if ($info['audio']['bitrate_mode'] == 'cbr') {
$encoder_options = strtoupper($info['audio']['bitrate_mode']).ceil($info['audio']['bitrate'] / 1000);
} else {
$encoder_options = strtoupper($info['audio']['bitrate_mode']);
}
}
if (!empty($thisfile_mpeg_audio_lame['bitrate_min'])) {
$encoder_options .= ' -b'.$thisfile_mpeg_audio_lame['bitrate_min'];
}
if (!empty($thisfile_mpeg_audio_lame['encoding_flags']['nogap_prev']) || !empty($thisfile_mpeg_audio_lame['encoding_flags']['nogap_next'])) {
$encoder_options .= ' --nogap';
}
if (!empty($thisfile_mpeg_audio_lame['lowpass_frequency'])) {
$ExplodedOptions = explode(' ', $encoder_options, 4);
if ($ExplodedOptions[0] == '--r3mix') {
$ExplodedOptions[1] = 'r3mix';
}
switch ($ExplodedOptions[0]) {
case '--preset':
case '--alt-preset':
case '--r3mix':
if ($ExplodedOptions[1] == 'fast') {
$ExplodedOptions[1] .= ' '.$ExplodedOptions[2];
}
switch ($ExplodedOptions[1]) {
case 'portable':
case 'medium':
case 'standard':
case 'extreme':
case 'insane':
case 'fast portable':
case 'fast medium':
case 'fast standard':
case 'fast extreme':
case 'fast insane':
case 'r3mix':
static $ExpectedLowpass = array(
'insane|20500' => 20500,
'insane|20600' => 20600, // 3.90.2, 3.90.3, 3.91
'medium|18000' => 18000,
'fast medium|18000' => 18000,
'extreme|19500' => 19500, // 3.90, 3.90.1, 3.92, 3.95
'extreme|19600' => 19600, // 3.90.2, 3.90.3, 3.91, 3.93.1
'fast extreme|19500' => 19500, // 3.90, 3.90.1, 3.92, 3.95
'fast extreme|19600' => 19600, // 3.90.2, 3.90.3, 3.91, 3.93.1
'standard|19000' => 19000,
'fast standard|19000' => 19000,
'r3mix|19500' => 19500, // 3.90, 3.90.1, 3.92
'r3mix|19600' => 19600, // 3.90.2, 3.90.3, 3.91
'r3mix|18000' => 18000, // 3.94, 3.95
);
if (!isset($ExpectedLowpass[$ExplodedOptions[1].'|'.$thisfile_mpeg_audio_lame['lowpass_frequency']]) && ($thisfile_mpeg_audio_lame['lowpass_frequency'] < 22050) && (round($thisfile_mpeg_audio_lame['lowpass_frequency'] / 1000) < round($thisfile_mpeg_audio['sample_rate'] / 2000))) {
$encoder_options .= ' --lowpass '.$thisfile_mpeg_audio_lame['lowpass_frequency'];
}
break;
default:
break;
}
break;
}
}
if (isset($thisfile_mpeg_audio_lame['raw']['source_sample_freq'])) {
if (($thisfile_mpeg_audio['sample_rate'] == 44100) && ($thisfile_mpeg_audio_lame['raw']['source_sample_freq'] != 1)) {
$encoder_options .= ' --resample 44100';
} elseif (($thisfile_mpeg_audio['sample_rate'] == 48000) && ($thisfile_mpeg_audio_lame['raw']['source_sample_freq'] != 2)) {
$encoder_options .= ' --resample 48000';
} elseif ($thisfile_mpeg_audio['sample_rate'] < 44100) {
switch ($thisfile_mpeg_audio_lame['raw']['source_sample_freq']) {
case 0: // <= 32000
// may or may not be same as source frequency - ignore
break;
case 1: // 44100
case 2: // 48000
case 3: // 48000+
$ExplodedOptions = explode(' ', $encoder_options, 4);
switch ($ExplodedOptions[0]) {
case '--preset':
case '--alt-preset':
switch ($ExplodedOptions[1]) {
case 'fast':
case 'portable':
case 'medium':
case 'standard':
case 'extreme':
case 'insane':
$encoder_options .= ' --resample '.$thisfile_mpeg_audio['sample_rate'];
break;
default:
static $ExpectedResampledRate = array(
'phon+/lw/mw-eu/sw|16000' => 16000,
'mw-us|24000' => 24000, // 3.95
'mw-us|32000' => 32000, // 3.93
'mw-us|16000' => 16000, // 3.92
'phone|16000' => 16000,
'phone|11025' => 11025, // 3.94a15
'radio|32000' => 32000, // 3.94a15
'fm/radio|32000' => 32000, // 3.92
'fm|32000' => 32000, // 3.90
'voice|32000' => 32000);
if (!isset($ExpectedResampledRate[$ExplodedOptions[1].'|'.$thisfile_mpeg_audio['sample_rate']])) {
$encoder_options .= ' --resample '.$thisfile_mpeg_audio['sample_rate'];
}
break;
}
break;
case '--r3mix':
default:
$encoder_options .= ' --resample '.$thisfile_mpeg_audio['sample_rate'];
break;
}
break;
}
}
}
if (empty($encoder_options) && !empty($info['audio']['bitrate']) && !empty($info['audio']['bitrate_mode'])) {
//$encoder_options = strtoupper($info['audio']['bitrate_mode']).ceil($info['audio']['bitrate'] / 1000);
$encoder_options = strtoupper($info['audio']['bitrate_mode']);
}
return $encoder_options;
}
/**
* @param int $offset
* @param array $info
* @param bool $recursivesearch
* @param bool $ScanAsCBR
* @param bool $FastMPEGheaderScan
*
* @return bool
*/
public function decodeMPEGaudioHeader($offset, &$info, $recursivesearch=true, $ScanAsCBR=false, $FastMPEGheaderScan=false) {
static $MPEGaudioVersionLookup;
static $MPEGaudioLayerLookup;
static $MPEGaudioBitrateLookup;
static $MPEGaudioFrequencyLookup;
static $MPEGaudioChannelModeLookup;
static $MPEGaudioModeExtensionLookup;
static $MPEGaudioEmphasisLookup;
if (empty($MPEGaudioVersionLookup)) {
$MPEGaudioVersionLookup = self::MPEGaudioVersionArray();
$MPEGaudioLayerLookup = self::MPEGaudioLayerArray();
$MPEGaudioBitrateLookup = self::MPEGaudioBitrateArray();
$MPEGaudioFrequencyLookup = self::MPEGaudioFrequencyArray();
$MPEGaudioChannelModeLookup = self::MPEGaudioChannelModeArray();
$MPEGaudioModeExtensionLookup = self::MPEGaudioModeExtensionArray();
$MPEGaudioEmphasisLookup = self::MPEGaudioEmphasisArray();
}
if ($this->fseek($offset) != 0) {
$this->error('decodeMPEGaudioHeader() failed to seek to next offset at '.$offset);
return false;
}
//$headerstring = $this->fread(1441); // worst-case max length = 32kHz @ 320kbps layer 3 = 1441 bytes/frame
$headerstring = $this->fread(226); // LAME header at offset 36 + 190 bytes of Xing/LAME data
// MP3 audio frame structure:
// $aa $aa $aa $aa [$bb $bb] $cc...
// where $aa..$aa is the four-byte mpeg-audio header (below)
// $bb $bb is the optional 2-byte CRC
// and $cc... is the audio data
$head4 = substr($headerstring, 0, 4);
$head4_key = getid3_lib::PrintHexBytes($head4, true, false, false);
static $MPEGaudioHeaderDecodeCache = array();
if (isset($MPEGaudioHeaderDecodeCache[$head4_key])) {
$MPEGheaderRawArray = $MPEGaudioHeaderDecodeCache[$head4_key];
} else {
$MPEGheaderRawArray = self::MPEGaudioHeaderDecode($head4);
$MPEGaudioHeaderDecodeCache[$head4_key] = $MPEGheaderRawArray;
}
static $MPEGaudioHeaderValidCache = array();
if (!isset($MPEGaudioHeaderValidCache[$head4_key])) { // Not in cache
//$MPEGaudioHeaderValidCache[$head4_key] = self::MPEGaudioHeaderValid($MPEGheaderRawArray, false, true); // allow badly-formatted freeformat (from LAME 3.90 - 3.93.1)
$MPEGaudioHeaderValidCache[$head4_key] = self::MPEGaudioHeaderValid($MPEGheaderRawArray, false, false);
}
// shortcut
if (!isset($info['mpeg']['audio'])) {
$info['mpeg']['audio'] = array();
}
$thisfile_mpeg_audio = &$info['mpeg']['audio'];
if ($MPEGaudioHeaderValidCache[$head4_key]) {
$thisfile_mpeg_audio['raw'] = $MPEGheaderRawArray;
} else {
$this->error('Invalid MPEG audio header ('.getid3_lib::PrintHexBytes($head4).') at offset '.$offset);
return false;
}
if (!$FastMPEGheaderScan) {
$thisfile_mpeg_audio['version'] = $MPEGaudioVersionLookup[$thisfile_mpeg_audio['raw']['version']];
$thisfile_mpeg_audio['layer'] = $MPEGaudioLayerLookup[$thisfile_mpeg_audio['raw']['layer']];
$thisfile_mpeg_audio['channelmode'] = $MPEGaudioChannelModeLookup[$thisfile_mpeg_audio['raw']['channelmode']];
$thisfile_mpeg_audio['channels'] = (($thisfile_mpeg_audio['channelmode'] == 'mono') ? 1 : 2);
$thisfile_mpeg_audio['sample_rate'] = $MPEGaudioFrequencyLookup[$thisfile_mpeg_audio['version']][$thisfile_mpeg_audio['raw']['sample_rate']];
$thisfile_mpeg_audio['protection'] = !$thisfile_mpeg_audio['raw']['protection'];
$thisfile_mpeg_audio['private'] = (bool) $thisfile_mpeg_audio['raw']['private'];
$thisfile_mpeg_audio['modeextension'] = $MPEGaudioModeExtensionLookup[$thisfile_mpeg_audio['layer']][$thisfile_mpeg_audio['raw']['modeextension']];
$thisfile_mpeg_audio['copyright'] = (bool) $thisfile_mpeg_audio['raw']['copyright'];
$thisfile_mpeg_audio['original'] = (bool) $thisfile_mpeg_audio['raw']['original'];
$thisfile_mpeg_audio['emphasis'] = $MPEGaudioEmphasisLookup[$thisfile_mpeg_audio['raw']['emphasis']];
$info['audio']['channels'] = $thisfile_mpeg_audio['channels'];
$info['audio']['sample_rate'] = $thisfile_mpeg_audio['sample_rate'];
if ($thisfile_mpeg_audio['protection']) {
$thisfile_mpeg_audio['crc'] = getid3_lib::BigEndian2Int(substr($headerstring, 4, 2));
}
}
if ($thisfile_mpeg_audio['raw']['bitrate'] == 15) {
// http://www.hydrogenaudio.org/?act=ST&f=16&t=9682&st=0
$this->warning('Invalid bitrate index (15), this is a known bug in free-format MP3s encoded by LAME v3.90 - 3.93.1');
$thisfile_mpeg_audio['raw']['bitrate'] = 0;
}
$thisfile_mpeg_audio['padding'] = (bool) $thisfile_mpeg_audio['raw']['padding'];
$thisfile_mpeg_audio['bitrate'] = $MPEGaudioBitrateLookup[$thisfile_mpeg_audio['version']][$thisfile_mpeg_audio['layer']][$thisfile_mpeg_audio['raw']['bitrate']];
if (($thisfile_mpeg_audio['bitrate'] == 'free') && ($offset == $info['avdataoffset'])) {
// only skip multiple frame check if free-format bitstream found at beginning of file
// otherwise is quite possibly simply corrupted data
$recursivesearch = false;
}
// For Layer 2 there are some combinations of bitrate and mode which are not allowed.
if (!$FastMPEGheaderScan && ($thisfile_mpeg_audio['layer'] == '2')) {
$info['audio']['dataformat'] = 'mp2';
switch ($thisfile_mpeg_audio['channelmode']) {
case 'mono':
if (($thisfile_mpeg_audio['bitrate'] == 'free') || ($thisfile_mpeg_audio['bitrate'] <= 192000)) {
// these are ok
} else {
$this->error($thisfile_mpeg_audio['bitrate'].'kbps not allowed in Layer 2, '.$thisfile_mpeg_audio['channelmode'].'.');
return false;
}
break;
case 'stereo':
case 'joint stereo':
case 'dual channel':
if (($thisfile_mpeg_audio['bitrate'] == 'free') || ($thisfile_mpeg_audio['bitrate'] == 64000) || ($thisfile_mpeg_audio['bitrate'] >= 96000)) {
// these are ok
} else {
$this->error(intval(round($thisfile_mpeg_audio['bitrate'] / 1000)).'kbps not allowed in Layer 2, '.$thisfile_mpeg_audio['channelmode'].'.');
return false;
}
break;
}
}
if ($info['audio']['sample_rate'] > 0) {
$thisfile_mpeg_audio['framelength'] = self::MPEGaudioFrameLength($thisfile_mpeg_audio['bitrate'], $thisfile_mpeg_audio['version'], $thisfile_mpeg_audio['layer'], (int) $thisfile_mpeg_audio['padding'], $info['audio']['sample_rate']);
}
$nextframetestoffset = $offset + 1;
if ($thisfile_mpeg_audio['bitrate'] != 'free') {
$info['audio']['bitrate'] = $thisfile_mpeg_audio['bitrate'];
if (isset($thisfile_mpeg_audio['framelength'])) {
$nextframetestoffset = $offset + $thisfile_mpeg_audio['framelength'];
} else {
$this->error('Frame at offset('.$offset.') is has an invalid frame length.');
return false;
}
}
$ExpectedNumberOfAudioBytes = 0;
////////////////////////////////////////////////////////////////////////////////////
// Variable-bitrate headers
if (substr($headerstring, 4 + 32, 4) == 'VBRI') {
// Fraunhofer VBR header is hardcoded 'VBRI' at offset 0x24 (36)
// specs taken from http://minnie.tuhs.org/pipermail/mp3encoder/2001-January/001800.html
$thisfile_mpeg_audio['bitrate_mode'] = 'vbr';
$thisfile_mpeg_audio['VBR_method'] = 'Fraunhofer';
$info['audio']['codec'] = 'Fraunhofer';
$SideInfoData = substr($headerstring, 4 + 2, 32);
$FraunhoferVBROffset = 36;
$thisfile_mpeg_audio['VBR_encoder_version'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 4, 2)); // VbriVersion
$thisfile_mpeg_audio['VBR_encoder_delay'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 6, 2)); // VbriDelay
$thisfile_mpeg_audio['VBR_quality'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 8, 2)); // VbriQuality
$thisfile_mpeg_audio['VBR_bytes'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 10, 4)); // VbriStreamBytes
$thisfile_mpeg_audio['VBR_frames'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 14, 4)); // VbriStreamFrames
$thisfile_mpeg_audio['VBR_seek_offsets'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 18, 2)); // VbriTableSize
$thisfile_mpeg_audio['VBR_seek_scale'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 20, 2)); // VbriTableScale
$thisfile_mpeg_audio['VBR_entry_bytes'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 22, 2)); // VbriEntryBytes
$thisfile_mpeg_audio['VBR_entry_frames'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 24, 2)); // VbriEntryFrames
$ExpectedNumberOfAudioBytes = $thisfile_mpeg_audio['VBR_bytes'];
$previousbyteoffset = $offset;
for ($i = 0; $i < $thisfile_mpeg_audio['VBR_seek_offsets']; $i++) {
$Fraunhofer_OffsetN = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset, $thisfile_mpeg_audio['VBR_entry_bytes']));
$FraunhoferVBROffset += $thisfile_mpeg_audio['VBR_entry_bytes'];
$thisfile_mpeg_audio['VBR_offsets_relative'][$i] = ($Fraunhofer_OffsetN * $thisfile_mpeg_audio['VBR_seek_scale']);
$thisfile_mpeg_audio['VBR_offsets_absolute'][$i] = ($Fraunhofer_OffsetN * $thisfile_mpeg_audio['VBR_seek_scale']) + $previousbyteoffset;
$previousbyteoffset += $Fraunhofer_OffsetN;
}
} else {
// Xing VBR header is hardcoded 'Xing' at a offset 0x0D (13), 0x15 (21) or 0x24 (36)
// depending on MPEG layer and number of channels
$VBRidOffset = self::XingVBRidOffset($thisfile_mpeg_audio['version'], $thisfile_mpeg_audio['channelmode']);
$SideInfoData = substr($headerstring, 4 + 2, $VBRidOffset - 4);
if ((substr($headerstring, $VBRidOffset, strlen('Xing')) == 'Xing') || (substr($headerstring, $VBRidOffset, strlen('Info')) == 'Info')) {
// 'Xing' is traditional Xing VBR frame
// 'Info' is LAME-encoded CBR (This was done to avoid CBR files to be recognized as traditional Xing VBR files by some decoders.)
// 'Info' *can* legally be used to specify a VBR file as well, however.
// http://www.multiweb.cz/twoinches/MP3inside.htm
//00..03 = "Xing" or "Info"
//04..07 = Flags:
// 0x01 Frames Flag set if value for number of frames in file is stored
// 0x02 Bytes Flag set if value for filesize in bytes is stored
// 0x04 TOC Flag set if values for TOC are stored
// 0x08 VBR Scale Flag set if values for VBR scale is stored
//08..11 Frames: Number of frames in file (including the first Xing/Info one)
//12..15 Bytes: File length in Bytes
//16..115 TOC (Table of Contents):
// Contains of 100 indexes (one Byte length) for easier lookup in file. Approximately solves problem with moving inside file.
// Each Byte has a value according this formula:
// (TOC[i] / 256) * fileLenInBytes
// So if song lasts eg. 240 sec. and you want to jump to 60. sec. (and file is 5 000 000 Bytes length) you can use:
// TOC[(60/240)*100] = TOC[25]
// and corresponding Byte in file is then approximately at:
// (TOC[25]/256) * 5000000
//116..119 VBR Scale
// should be safe to leave this at 'vbr' and let it be overriden to 'cbr' if a CBR preset/mode is used by LAME
// if (substr($headerstring, $VBRidOffset, strlen('Info')) == 'Xing') {
$thisfile_mpeg_audio['bitrate_mode'] = 'vbr';
$thisfile_mpeg_audio['VBR_method'] = 'Xing';
// } else {
// $ScanAsCBR = true;
// $thisfile_mpeg_audio['bitrate_mode'] = 'cbr';
// }
$thisfile_mpeg_audio['xing_flags_raw'] = getid3_lib::BigEndian2Int(substr($headerstring, $VBRidOffset + 4, 4));
$thisfile_mpeg_audio['xing_flags']['frames'] = (bool) ($thisfile_mpeg_audio['xing_flags_raw'] & 0x00000001);
$thisfile_mpeg_audio['xing_flags']['bytes'] = (bool) ($thisfile_mpeg_audio['xing_flags_raw'] & 0x00000002);
$thisfile_mpeg_audio['xing_flags']['toc'] = (bool) ($thisfile_mpeg_audio['xing_flags_raw'] & 0x00000004);
$thisfile_mpeg_audio['xing_flags']['vbr_scale'] = (bool) ($thisfile_mpeg_audio['xing_flags_raw'] & 0x00000008);
if ($thisfile_mpeg_audio['xing_flags']['frames']) {
$thisfile_mpeg_audio['VBR_frames'] = getid3_lib::BigEndian2Int(substr($headerstring, $VBRidOffset + 8, 4));
//$thisfile_mpeg_audio['VBR_frames']--; // don't count header Xing/Info frame
}
if ($thisfile_mpeg_audio['xing_flags']['bytes']) {
$thisfile_mpeg_audio['VBR_bytes'] = getid3_lib::BigEndian2Int(substr($headerstring, $VBRidOffset + 12, 4));
}
//if (($thisfile_mpeg_audio['bitrate'] == 'free') && !empty($thisfile_mpeg_audio['VBR_frames']) && !empty($thisfile_mpeg_audio['VBR_bytes'])) {
//if (!empty($thisfile_mpeg_audio['VBR_frames']) && !empty($thisfile_mpeg_audio['VBR_bytes'])) {
if (!empty($thisfile_mpeg_audio['VBR_frames'])) {
$used_filesize = 0;
if (!empty($thisfile_mpeg_audio['VBR_bytes'])) {
$used_filesize = $thisfile_mpeg_audio['VBR_bytes'];
} elseif (!empty($info['filesize'])) {
$used_filesize = $info['filesize'];
$used_filesize -= (isset($info['id3v2']['headerlength']) ? intval($info['id3v2']['headerlength']) : 0);
$used_filesize -= (isset($info['id3v1']) ? 128 : 0);
$used_filesize -= (isset($info['tag_offset_end']) ? $info['tag_offset_end'] - $info['tag_offset_start'] : 0);
$this->warning('MP3.Xing header missing VBR_bytes, assuming MPEG audio portion of file is '.number_format($used_filesize).' bytes');
}
$framelengthfloat = $used_filesize / $thisfile_mpeg_audio['VBR_frames'];
if ($thisfile_mpeg_audio['layer'] == '1') {
// BitRate = (((FrameLengthInBytes / 4) - Padding) * SampleRate) / 12
//$info['audio']['bitrate'] = ((($framelengthfloat / 4) - intval($thisfile_mpeg_audio['padding'])) * $thisfile_mpeg_audio['sample_rate']) / 12;
$info['audio']['bitrate'] = ($framelengthfloat / 4) * $thisfile_mpeg_audio['sample_rate'] * (2 / $info['audio']['channels']) / 12;
} else {
// Bitrate = ((FrameLengthInBytes - Padding) * SampleRate) / 144
//$info['audio']['bitrate'] = (($framelengthfloat - intval($thisfile_mpeg_audio['padding'])) * $thisfile_mpeg_audio['sample_rate']) / 144;
$info['audio']['bitrate'] = $framelengthfloat * $thisfile_mpeg_audio['sample_rate'] * (2 / $info['audio']['channels']) / 144;
}
$thisfile_mpeg_audio['framelength'] = floor($framelengthfloat);
}
if ($thisfile_mpeg_audio['xing_flags']['toc']) {
$LAMEtocData = substr($headerstring, $VBRidOffset + 16, 100);
for ($i = 0; $i < 100; $i++) {
$thisfile_mpeg_audio['toc'][$i] = ord($LAMEtocData[$i]);
}
}
if ($thisfile_mpeg_audio['xing_flags']['vbr_scale']) {
$thisfile_mpeg_audio['VBR_scale'] = getid3_lib::BigEndian2Int(substr($headerstring, $VBRidOffset + 116, 4));
}
// http://gabriel.mp3-tech.org/mp3infotag.html
if (substr($headerstring, $VBRidOffset + 120, 4) == 'LAME') {
// shortcut
$thisfile_mpeg_audio['LAME'] = array();
$thisfile_mpeg_audio_lame = &$thisfile_mpeg_audio['LAME'];
$thisfile_mpeg_audio_lame['long_version'] = substr($headerstring, $VBRidOffset + 120, 20);
$thisfile_mpeg_audio_lame['short_version'] = substr($thisfile_mpeg_audio_lame['long_version'], 0, 9);
$thisfile_mpeg_audio_lame['numeric_version'] = str_replace('LAME', '', $thisfile_mpeg_audio_lame['short_version']);
if (preg_match('#^LAME([0-9\\.a-z]+)#', $thisfile_mpeg_audio_lame['long_version'], $matches)) {
$thisfile_mpeg_audio_lame['short_version'] = $matches[0];
$thisfile_mpeg_audio_lame['numeric_version'] = $matches[1];
}
foreach (explode('.', $thisfile_mpeg_audio_lame['numeric_version']) as $key => $number) {
$thisfile_mpeg_audio_lame['integer_version'][$key] = intval($number);
}
//if ($thisfile_mpeg_audio_lame['short_version'] >= 'LAME3.90') {
if ((($thisfile_mpeg_audio_lame['integer_version'][0] * 1000) + $thisfile_mpeg_audio_lame['integer_version'][1]) >= 3090) { // cannot use string version compare, may have "LAME3.90" or "LAME3.100" -- see https://github.com/JamesHeinrich/getID3/issues/207
// extra 11 chars are not part of version string when LAMEtag present
unset($thisfile_mpeg_audio_lame['long_version']);
// It the LAME tag was only introduced in LAME v3.90
// http://www.hydrogenaudio.org/?act=ST&f=15&t=9933
// Offsets of various bytes in http://gabriel.mp3-tech.org/mp3infotag.html
// are assuming a 'Xing' identifier offset of 0x24, which is the case for
// MPEG-1 non-mono, but not for other combinations
$LAMEtagOffsetContant = $VBRidOffset - 0x24;
// shortcuts
$thisfile_mpeg_audio_lame['RGAD'] = array('track'=>array(), 'album'=>array());
$thisfile_mpeg_audio_lame_RGAD = &$thisfile_mpeg_audio_lame['RGAD'];
$thisfile_mpeg_audio_lame_RGAD_track = &$thisfile_mpeg_audio_lame_RGAD['track'];
$thisfile_mpeg_audio_lame_RGAD_album = &$thisfile_mpeg_audio_lame_RGAD['album'];
$thisfile_mpeg_audio_lame['raw'] = array();
$thisfile_mpeg_audio_lame_raw = &$thisfile_mpeg_audio_lame['raw'];
// byte $9B VBR Quality
// This field is there to indicate a quality level, although the scale was not precised in the original Xing specifications.
// Actually overwrites original Xing bytes
unset($thisfile_mpeg_audio['VBR_scale']);
$thisfile_mpeg_audio_lame['vbr_quality'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0x9B, 1));
// bytes $9C-$A4 Encoder short VersionString
$thisfile_mpeg_audio_lame['short_version'] = substr($headerstring, $LAMEtagOffsetContant + 0x9C, 9);
// byte $A5 Info Tag revision + VBR method
$LAMEtagRevisionVBRmethod = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xA5, 1));
$thisfile_mpeg_audio_lame['tag_revision'] = ($LAMEtagRevisionVBRmethod & 0xF0) >> 4;
$thisfile_mpeg_audio_lame_raw['vbr_method'] = $LAMEtagRevisionVBRmethod & 0x0F;
$thisfile_mpeg_audio_lame['vbr_method'] = self::LAMEvbrMethodLookup($thisfile_mpeg_audio_lame_raw['vbr_method']);
$thisfile_mpeg_audio['bitrate_mode'] = substr($thisfile_mpeg_audio_lame['vbr_method'], 0, 3); // usually either 'cbr' or 'vbr', but truncates 'vbr-old / vbr-rh' to 'vbr'
// byte $A6 Lowpass filter value
$thisfile_mpeg_audio_lame['lowpass_frequency'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xA6, 1)) * 100;
// bytes $A7-$AE Replay Gain
// http://privatewww.essex.ac.uk/~djmrob/replaygain/rg_data_format.html
// bytes $A7-$AA : 32 bit floating point "Peak signal amplitude"
if ($thisfile_mpeg_audio_lame['short_version'] >= 'LAME3.94b') {
// LAME 3.94a16 and later - 9.23 fixed point
// ie 0x0059E2EE / (2^23) = 5890798 / 8388608 = 0.7022378444671630859375
$thisfile_mpeg_audio_lame_RGAD['peak_amplitude'] = (float) ((getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xA7, 4))) / 8388608);
} else {
// LAME 3.94a15 and earlier - 32-bit floating point
// Actually 3.94a16 will fall in here too and be WRONG, but is hard to detect 3.94a16 vs 3.94a15
$thisfile_mpeg_audio_lame_RGAD['peak_amplitude'] = getid3_lib::LittleEndian2Float(substr($headerstring, $LAMEtagOffsetContant + 0xA7, 4));
}
if ($thisfile_mpeg_audio_lame_RGAD['peak_amplitude'] == 0) {
unset($thisfile_mpeg_audio_lame_RGAD['peak_amplitude']);
} else {
$thisfile_mpeg_audio_lame_RGAD['peak_db'] = getid3_lib::RGADamplitude2dB($thisfile_mpeg_audio_lame_RGAD['peak_amplitude']);
}
$thisfile_mpeg_audio_lame_raw['RGAD_track'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xAB, 2));
$thisfile_mpeg_audio_lame_raw['RGAD_album'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xAD, 2));
if ($thisfile_mpeg_audio_lame_raw['RGAD_track'] != 0) {
$thisfile_mpeg_audio_lame_RGAD_track['raw']['name'] = ($thisfile_mpeg_audio_lame_raw['RGAD_track'] & 0xE000) >> 13;
$thisfile_mpeg_audio_lame_RGAD_track['raw']['originator'] = ($thisfile_mpeg_audio_lame_raw['RGAD_track'] & 0x1C00) >> 10;
$thisfile_mpeg_audio_lame_RGAD_track['raw']['sign_bit'] = ($thisfile_mpeg_audio_lame_raw['RGAD_track'] & 0x0200) >> 9;
$thisfile_mpeg_audio_lame_RGAD_track['raw']['gain_adjust'] = $thisfile_mpeg_audio_lame_raw['RGAD_track'] & 0x01FF;
$thisfile_mpeg_audio_lame_RGAD_track['name'] = getid3_lib::RGADnameLookup($thisfile_mpeg_audio_lame_RGAD_track['raw']['name']);
$thisfile_mpeg_audio_lame_RGAD_track['originator'] = getid3_lib::RGADoriginatorLookup($thisfile_mpeg_audio_lame_RGAD_track['raw']['originator']);
$thisfile_mpeg_audio_lame_RGAD_track['gain_db'] = getid3_lib::RGADadjustmentLookup($thisfile_mpeg_audio_lame_RGAD_track['raw']['gain_adjust'], $thisfile_mpeg_audio_lame_RGAD_track['raw']['sign_bit']);
if (!empty($thisfile_mpeg_audio_lame_RGAD['peak_amplitude'])) {
$info['replay_gain']['track']['peak'] = $thisfile_mpeg_audio_lame_RGAD['peak_amplitude'];
}
$info['replay_gain']['track']['originator'] = $thisfile_mpeg_audio_lame_RGAD_track['originator'];
$info['replay_gain']['track']['adjustment'] = $thisfile_mpeg_audio_lame_RGAD_track['gain_db'];
} else {
unset($thisfile_mpeg_audio_lame_RGAD['track']);
}
if ($thisfile_mpeg_audio_lame_raw['RGAD_album'] != 0) {
$thisfile_mpeg_audio_lame_RGAD_album['raw']['name'] = ($thisfile_mpeg_audio_lame_raw['RGAD_album'] & 0xE000) >> 13;
$thisfile_mpeg_audio_lame_RGAD_album['raw']['originator'] = ($thisfile_mpeg_audio_lame_raw['RGAD_album'] & 0x1C00) >> 10;
$thisfile_mpeg_audio_lame_RGAD_album['raw']['sign_bit'] = ($thisfile_mpeg_audio_lame_raw['RGAD_album'] & 0x0200) >> 9;
$thisfile_mpeg_audio_lame_RGAD_album['raw']['gain_adjust'] = $thisfile_mpeg_audio_lame_raw['RGAD_album'] & 0x01FF;
$thisfile_mpeg_audio_lame_RGAD_album['name'] = getid3_lib::RGADnameLookup($thisfile_mpeg_audio_lame_RGAD_album['raw']['name']);
$thisfile_mpeg_audio_lame_RGAD_album['originator'] = getid3_lib::RGADoriginatorLookup($thisfile_mpeg_audio_lame_RGAD_album['raw']['originator']);
$thisfile_mpeg_audio_lame_RGAD_album['gain_db'] = getid3_lib::RGADadjustmentLookup($thisfile_mpeg_audio_lame_RGAD_album['raw']['gain_adjust'], $thisfile_mpeg_audio_lame_RGAD_album['raw']['sign_bit']);
if (!empty($thisfile_mpeg_audio_lame_RGAD['peak_amplitude'])) {
$info['replay_gain']['album']['peak'] = $thisfile_mpeg_audio_lame_RGAD['peak_amplitude'];
}
$info['replay_gain']['album']['originator'] = $thisfile_mpeg_audio_lame_RGAD_album['originator'];
$info['replay_gain']['album']['adjustment'] = $thisfile_mpeg_audio_lame_RGAD_album['gain_db'];
} else {
unset($thisfile_mpeg_audio_lame_RGAD['album']);
}
if (empty($thisfile_mpeg_audio_lame_RGAD)) {
unset($thisfile_mpeg_audio_lame['RGAD']);
}
// byte $AF Encoding flags + ATH Type
$EncodingFlagsATHtype = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xAF, 1));
$thisfile_mpeg_audio_lame['encoding_flags']['nspsytune'] = (bool) ($EncodingFlagsATHtype & 0x10);
$thisfile_mpeg_audio_lame['encoding_flags']['nssafejoint'] = (bool) ($EncodingFlagsATHtype & 0x20);
$thisfile_mpeg_audio_lame['encoding_flags']['nogap_next'] = (bool) ($EncodingFlagsATHtype & 0x40);
$thisfile_mpeg_audio_lame['encoding_flags']['nogap_prev'] = (bool) ($EncodingFlagsATHtype & 0x80);
$thisfile_mpeg_audio_lame['ath_type'] = $EncodingFlagsATHtype & 0x0F;
// byte $B0 if ABR {specified bitrate} else {minimal bitrate}
$thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB0, 1));
if ($thisfile_mpeg_audio_lame_raw['vbr_method'] == 2) { // Average BitRate (ABR)
$thisfile_mpeg_audio_lame['bitrate_abr'] = $thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate'];
} elseif ($thisfile_mpeg_audio_lame_raw['vbr_method'] == 1) { // Constant BitRate (CBR)
// ignore
} elseif ($thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate'] > 0) { // Variable BitRate (VBR) - minimum bitrate
$thisfile_mpeg_audio_lame['bitrate_min'] = $thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate'];
}
// bytes $B1-$B3 Encoder delays
$EncoderDelays = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB1, 3));
$thisfile_mpeg_audio_lame['encoder_delay'] = ($EncoderDelays & 0xFFF000) >> 12;
$thisfile_mpeg_audio_lame['end_padding'] = $EncoderDelays & 0x000FFF;
// byte $B4 Misc
$MiscByte = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB4, 1));
$thisfile_mpeg_audio_lame_raw['noise_shaping'] = ($MiscByte & 0x03);
$thisfile_mpeg_audio_lame_raw['stereo_mode'] = ($MiscByte & 0x1C) >> 2;
$thisfile_mpeg_audio_lame_raw['not_optimal_quality'] = ($MiscByte & 0x20) >> 5;
$thisfile_mpeg_audio_lame_raw['source_sample_freq'] = ($MiscByte & 0xC0) >> 6;
$thisfile_mpeg_audio_lame['noise_shaping'] = $thisfile_mpeg_audio_lame_raw['noise_shaping'];
$thisfile_mpeg_audio_lame['stereo_mode'] = self::LAMEmiscStereoModeLookup($thisfile_mpeg_audio_lame_raw['stereo_mode']);
$thisfile_mpeg_audio_lame['not_optimal_quality'] = (bool) $thisfile_mpeg_audio_lame_raw['not_optimal_quality'];
$thisfile_mpeg_audio_lame['source_sample_freq'] = self::LAMEmiscSourceSampleFrequencyLookup($thisfile_mpeg_audio_lame_raw['source_sample_freq']);
// byte $B5 MP3 Gain
$thisfile_mpeg_audio_lame_raw['mp3_gain'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB5, 1), false, true);
$thisfile_mpeg_audio_lame['mp3_gain_db'] = (getid3_lib::RGADamplitude2dB(2) / 4) * $thisfile_mpeg_audio_lame_raw['mp3_gain'];
$thisfile_mpeg_audio_lame['mp3_gain_factor'] = pow(2, ($thisfile_mpeg_audio_lame['mp3_gain_db'] / 6));
// bytes $B6-$B7 Preset and surround info
$PresetSurroundBytes = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB6, 2));
// Reserved = ($PresetSurroundBytes & 0xC000);
$thisfile_mpeg_audio_lame_raw['surround_info'] = ($PresetSurroundBytes & 0x3800);
$thisfile_mpeg_audio_lame['surround_info'] = self::LAMEsurroundInfoLookup($thisfile_mpeg_audio_lame_raw['surround_info']);
$thisfile_mpeg_audio_lame['preset_used_id'] = ($PresetSurroundBytes & 0x07FF);
$thisfile_mpeg_audio_lame['preset_used'] = self::LAMEpresetUsedLookup($thisfile_mpeg_audio_lame);
if (!empty($thisfile_mpeg_audio_lame['preset_used_id']) && empty($thisfile_mpeg_audio_lame['preset_used'])) {
$this->warning('Unknown LAME preset used ('.$thisfile_mpeg_audio_lame['preset_used_id'].') - please report to info@getid3.org');
}
if (($thisfile_mpeg_audio_lame['short_version'] == 'LAME3.90.') && !empty($thisfile_mpeg_audio_lame['preset_used_id'])) {
// this may change if 3.90.4 ever comes out
$thisfile_mpeg_audio_lame['short_version'] = 'LAME3.90.3';
}
// bytes $B8-$BB MusicLength
$thisfile_mpeg_audio_lame['audio_bytes'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB8, 4));
$ExpectedNumberOfAudioBytes = (($thisfile_mpeg_audio_lame['audio_bytes'] > 0) ? $thisfile_mpeg_audio_lame['audio_bytes'] : $thisfile_mpeg_audio['VBR_bytes']);
// bytes $BC-$BD MusicCRC
$thisfile_mpeg_audio_lame['music_crc'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xBC, 2));
// bytes $BE-$BF CRC-16 of Info Tag
$thisfile_mpeg_audio_lame['lame_tag_crc'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xBE, 2));
// LAME CBR
if ($thisfile_mpeg_audio_lame_raw['vbr_method'] == 1) {
$thisfile_mpeg_audio['bitrate_mode'] = 'cbr';
$thisfile_mpeg_audio['bitrate'] = self::ClosestStandardMP3Bitrate($thisfile_mpeg_audio['bitrate']);
$info['audio']['bitrate'] = $thisfile_mpeg_audio['bitrate'];
//if (empty($thisfile_mpeg_audio['bitrate']) || (!empty($thisfile_mpeg_audio_lame['bitrate_min']) && ($thisfile_mpeg_audio_lame['bitrate_min'] != 255))) {
// $thisfile_mpeg_audio['bitrate'] = $thisfile_mpeg_audio_lame['bitrate_min'];
//}
}
}
}
} else {
// not Fraunhofer or Xing VBR methods, most likely CBR (but could be VBR with no header)
$thisfile_mpeg_audio['bitrate_mode'] = 'cbr';
if ($recursivesearch) {
$thisfile_mpeg_audio['bitrate_mode'] = 'vbr';
if ($this->RecursiveFrameScanning($offset, $nextframetestoffset, true)) {
$recursivesearch = false;
$thisfile_mpeg_audio['bitrate_mode'] = 'cbr';
}
if ($thisfile_mpeg_audio['bitrate_mode'] == 'vbr') {
$this->warning('VBR file with no VBR header. Bitrate values calculated from actual frame bitrates.');
}
}
}
}
if (($ExpectedNumberOfAudioBytes > 0) && ($ExpectedNumberOfAudioBytes != ($info['avdataend'] - $info['avdataoffset']))) {
if ($ExpectedNumberOfAudioBytes > ($info['avdataend'] - $info['avdataoffset'])) {
if ($this->isDependencyFor('matroska') || $this->isDependencyFor('riff')) {
// ignore, audio data is broken into chunks so will always be data "missing"
}
elseif (($ExpectedNumberOfAudioBytes - ($info['avdataend'] - $info['avdataoffset'])) == 1) {
$this->warning('Last byte of data truncated (this is a known bug in Meracl ID3 Tag Writer before v1.3.5)');
}
else {
$this->warning('Probable truncated file: expecting '.$ExpectedNumberOfAudioBytes.' bytes of audio data, only found '.($info['avdataend'] - $info['avdataoffset']).' (short by '.($ExpectedNumberOfAudioBytes - ($info['avdataend'] - $info['avdataoffset'])).' bytes)');
}
} else {
if ((($info['avdataend'] - $info['avdataoffset']) - $ExpectedNumberOfAudioBytes) == 1) {
// $prenullbytefileoffset = $this->ftell();
// $this->fseek($info['avdataend']);
// $PossibleNullByte = $this->fread(1);
// $this->fseek($prenullbytefileoffset);
// if ($PossibleNullByte === "\x00") {
$info['avdataend']--;
// $this->warning('Extra null byte at end of MP3 data assumed to be RIFF padding and therefore ignored');
// } else {
// $this->warning('Too much data in file: expecting '.$ExpectedNumberOfAudioBytes.' bytes of audio data, found '.($info['avdataend'] - $info['avdataoffset']).' ('.(($info['avdataend'] - $info['avdataoffset']) - $ExpectedNumberOfAudioBytes).' bytes too many)');
// }
} else {
$this->warning('Too much data in file: expecting '.$ExpectedNumberOfAudioBytes.' bytes of audio data, found '.($info['avdataend'] - $info['avdataoffset']).' ('.(($info['avdataend'] - $info['avdataoffset']) - $ExpectedNumberOfAudioBytes).' bytes too many)');
}
}
}
if (($thisfile_mpeg_audio['bitrate'] == 'free') && empty($info['audio']['bitrate'])) {
if (($offset == $info['avdataoffset']) && empty($thisfile_mpeg_audio['VBR_frames'])) {
$framebytelength = $this->FreeFormatFrameLength($offset, true);
if ($framebytelength > 0) {
$thisfile_mpeg_audio['framelength'] = $framebytelength;
if ($thisfile_mpeg_audio['layer'] == '1') {
// BitRate = (((FrameLengthInBytes / 4) - Padding) * SampleRate) / 12
$info['audio']['bitrate'] = ((($framebytelength / 4) - intval($thisfile_mpeg_audio['padding'])) * $thisfile_mpeg_audio['sample_rate']) / 12;
} else {
// Bitrate = ((FrameLengthInBytes - Padding) * SampleRate) / 144
$info['audio']['bitrate'] = (($framebytelength - intval($thisfile_mpeg_audio['padding'])) * $thisfile_mpeg_audio['sample_rate']) / 144;
}
} else {
$this->error('Error calculating frame length of free-format MP3 without Xing/LAME header');
}
}
}
if (isset($thisfile_mpeg_audio['VBR_frames']) ? $thisfile_mpeg_audio['VBR_frames'] : '') {
switch ($thisfile_mpeg_audio['bitrate_mode']) {
case 'vbr':
case 'abr':
$bytes_per_frame = 1152;
if (($thisfile_mpeg_audio['version'] == '1') && ($thisfile_mpeg_audio['layer'] == 1)) {
$bytes_per_frame = 384;
} elseif ((($thisfile_mpeg_audio['version'] == '2') || ($thisfile_mpeg_audio['version'] == '2.5')) && ($thisfile_mpeg_audio['layer'] == 3)) {
$bytes_per_frame = 576;
}
$thisfile_mpeg_audio['VBR_bitrate'] = (isset($thisfile_mpeg_audio['VBR_bytes']) ? (($thisfile_mpeg_audio['VBR_bytes'] / $thisfile_mpeg_audio['VBR_frames']) * 8) * ($info['audio']['sample_rate'] / $bytes_per_frame) : 0);
if ($thisfile_mpeg_audio['VBR_bitrate'] > 0) {
$info['audio']['bitrate'] = $thisfile_mpeg_audio['VBR_bitrate'];
$thisfile_mpeg_audio['bitrate'] = $thisfile_mpeg_audio['VBR_bitrate']; // to avoid confusion
}
break;
}
}
// End variable-bitrate headers
////////////////////////////////////////////////////////////////////////////////////
if ($recursivesearch) {
if (!$this->RecursiveFrameScanning($offset, $nextframetestoffset, $ScanAsCBR)) {
return false;
}
}
//if (false) {
// // experimental side info parsing section - not returning anything useful yet
//
// $SideInfoBitstream = getid3_lib::BigEndian2Bin($SideInfoData);
// $SideInfoOffset = 0;
//
// if ($thisfile_mpeg_audio['version'] == '1') {
// if ($thisfile_mpeg_audio['channelmode'] == 'mono') {
// // MPEG-1 (mono)
// $thisfile_mpeg_audio['side_info']['main_data_begin'] = substr($SideInfoBitstream, $SideInfoOffset, 9);
// $SideInfoOffset += 9;
// $SideInfoOffset += 5;
// } else {
// // MPEG-1 (stereo, joint-stereo, dual-channel)
// $thisfile_mpeg_audio['side_info']['main_data_begin'] = substr($SideInfoBitstream, $SideInfoOffset, 9);
// $SideInfoOffset += 9;
// $SideInfoOffset += 3;
// }
// } else { // 2 or 2.5
// if ($thisfile_mpeg_audio['channelmode'] == 'mono') {
// // MPEG-2, MPEG-2.5 (mono)
// $thisfile_mpeg_audio['side_info']['main_data_begin'] = substr($SideInfoBitstream, $SideInfoOffset, 8);
// $SideInfoOffset += 8;
// $SideInfoOffset += 1;
// } else {
// // MPEG-2, MPEG-2.5 (stereo, joint-stereo, dual-channel)
// $thisfile_mpeg_audio['side_info']['main_data_begin'] = substr($SideInfoBitstream, $SideInfoOffset, 8);
// $SideInfoOffset += 8;
// $SideInfoOffset += 2;
// }
// }
//
// if ($thisfile_mpeg_audio['version'] == '1') {
// for ($channel = 0; $channel < $info['audio']['channels']; $channel++) {
// for ($scfsi_band = 0; $scfsi_band < 4; $scfsi_band++) {
// $thisfile_mpeg_audio['scfsi'][$channel][$scfsi_band] = substr($SideInfoBitstream, $SideInfoOffset, 1);
// $SideInfoOffset += 2;
// }
// }
// }
// for ($granule = 0; $granule < (($thisfile_mpeg_audio['version'] == '1') ? 2 : 1); $granule++) {
// for ($channel = 0; $channel < $info['audio']['channels']; $channel++) {
// $thisfile_mpeg_audio['part2_3_length'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 12);
// $SideInfoOffset += 12;
// $thisfile_mpeg_audio['big_values'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 9);
// $SideInfoOffset += 9;
// $thisfile_mpeg_audio['global_gain'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 8);
// $SideInfoOffset += 8;
// if ($thisfile_mpeg_audio['version'] == '1') {
// $thisfile_mpeg_audio['scalefac_compress'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 4);
// $SideInfoOffset += 4;
// } else {
// $thisfile_mpeg_audio['scalefac_compress'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 9);
// $SideInfoOffset += 9;
// }
// $thisfile_mpeg_audio['window_switching_flag'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1);
// $SideInfoOffset += 1;
//
// if ($thisfile_mpeg_audio['window_switching_flag'][$granule][$channel] == '1') {
//
// $thisfile_mpeg_audio['block_type'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 2);
// $SideInfoOffset += 2;
// $thisfile_mpeg_audio['mixed_block_flag'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1);
// $SideInfoOffset += 1;
//
// for ($region = 0; $region < 2; $region++) {
// $thisfile_mpeg_audio['table_select'][$granule][$channel][$region] = substr($SideInfoBitstream, $SideInfoOffset, 5);
// $SideInfoOffset += 5;
// }
// $thisfile_mpeg_audio['table_select'][$granule][$channel][2] = 0;
//
// for ($window = 0; $window < 3; $window++) {
// $thisfile_mpeg_audio['subblock_gain'][$granule][$channel][$window] = substr($SideInfoBitstream, $SideInfoOffset, 3);
// $SideInfoOffset += 3;
// }
//
// } else {
//
// for ($region = 0; $region < 3; $region++) {
// $thisfile_mpeg_audio['table_select'][$granule][$channel][$region] = substr($SideInfoBitstream, $SideInfoOffset, 5);
// $SideInfoOffset += 5;
// }
//
// $thisfile_mpeg_audio['region0_count'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 4);
// $SideInfoOffset += 4;
// $thisfile_mpeg_audio['region1_count'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 3);
// $SideInfoOffset += 3;
// $thisfile_mpeg_audio['block_type'][$granule][$channel] = 0;
// }
//
// if ($thisfile_mpeg_audio['version'] == '1') {
// $thisfile_mpeg_audio['preflag'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1);
// $SideInfoOffset += 1;
// }
// $thisfile_mpeg_audio['scalefac_scale'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1);
// $SideInfoOffset += 1;
// $thisfile_mpeg_audio['count1table_select'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1);
// $SideInfoOffset += 1;
// }
// }
//}
return true;
}
/**
* @param int $offset
* @param int $nextframetestoffset
* @param bool $ScanAsCBR
*
* @return bool
*/
public function RecursiveFrameScanning(&$offset, &$nextframetestoffset, $ScanAsCBR) {
$info = &$this->getid3->info;
$firstframetestarray = array('error' => array(), 'warning'=> array(), 'avdataend' => $info['avdataend'], 'avdataoffset' => $info['avdataoffset']);
$this->decodeMPEGaudioHeader($offset, $firstframetestarray, false);
for ($i = 0; $i < GETID3_MP3_VALID_CHECK_FRAMES; $i++) {
// check next GETID3_MP3_VALID_CHECK_FRAMES frames for validity, to make sure we haven't run across a false synch
if (($nextframetestoffset + 4) >= $info['avdataend']) {
// end of file
return true;
}
$nextframetestarray = array('error' => array(), 'warning' => array(), 'avdataend' => $info['avdataend'], 'avdataoffset'=>$info['avdataoffset']);
if ($this->decodeMPEGaudioHeader($nextframetestoffset, $nextframetestarray, false)) {
if ($ScanAsCBR) {
// force CBR mode, used for trying to pick out invalid audio streams with valid(?) VBR headers, or VBR streams with no VBR header
if (!isset($nextframetestarray['mpeg']['audio']['bitrate']) || !isset($firstframetestarray['mpeg']['audio']['bitrate']) || ($nextframetestarray['mpeg']['audio']['bitrate'] != $firstframetestarray['mpeg']['audio']['bitrate'])) {
return false;
}
}
// next frame is OK, get ready to check the one after that
if (isset($nextframetestarray['mpeg']['audio']['framelength']) && ($nextframetestarray['mpeg']['audio']['framelength'] > 0)) {
$nextframetestoffset += $nextframetestarray['mpeg']['audio']['framelength'];
} else {
$this->error('Frame at offset ('.$offset.') is has an invalid frame length.');
return false;
}
} elseif (!empty($firstframetestarray['mpeg']['audio']['framelength']) && (($nextframetestoffset + $firstframetestarray['mpeg']['audio']['framelength']) > $info['avdataend'])) {
// it's not the end of the file, but there's not enough data left for another frame, so assume it's garbage/padding and return OK
return true;
} else {
// next frame is not valid, note the error and fail, so scanning can contiue for a valid frame sequence
$this->warning('Frame at offset ('.$offset.') is valid, but the next one at ('.$nextframetestoffset.') is not.');
return false;
}
}
return true;
}
/**
* @param int $offset
* @param bool $deepscan
*
* @return int|false
*/
public function FreeFormatFrameLength($offset, $deepscan=false) {
$info = &$this->getid3->info;
$this->fseek($offset);
$MPEGaudioData = $this->fread(32768);
$SyncPattern1 = substr($MPEGaudioData, 0, 4);
// may be different pattern due to padding
$SyncPattern2 = $SyncPattern1[0].$SyncPattern1[1].chr(ord($SyncPattern1[2]) | 0x02).$SyncPattern1[3];
if ($SyncPattern2 === $SyncPattern1) {
$SyncPattern2 = $SyncPattern1[0].$SyncPattern1[1].chr(ord($SyncPattern1[2]) & 0xFD).$SyncPattern1[3];
}
$framelength = false;
$framelength1 = strpos($MPEGaudioData, $SyncPattern1, 4);
$framelength2 = strpos($MPEGaudioData, $SyncPattern2, 4);
if ($framelength1 > 4) {
$framelength = $framelength1;
}
if (($framelength2 > 4) && ($framelength2 < $framelength1)) {
$framelength = $framelength2;
}
if (!$framelength) {
// LAME 3.88 has a different value for modeextension on the first frame vs the rest
$framelength1 = strpos($MPEGaudioData, substr($SyncPattern1, 0, 3), 4);
$framelength2 = strpos($MPEGaudioData, substr($SyncPattern2, 0, 3), 4);
if ($framelength1 > 4) {
$framelength = $framelength1;
}
if (($framelength2 > 4) && ($framelength2 < $framelength1)) {
$framelength = $framelength2;
}
if (!$framelength) {
$this->error('Cannot find next free-format synch pattern ('.getid3_lib::PrintHexBytes($SyncPattern1).' or '.getid3_lib::PrintHexBytes($SyncPattern2).') after offset '.$offset);
return false;
} else {
$this->warning('ModeExtension varies between first frame and other frames (known free-format issue in LAME 3.88)');
$info['audio']['codec'] = 'LAME';
$info['audio']['encoder'] = 'LAME3.88';
$SyncPattern1 = substr($SyncPattern1, 0, 3);
$SyncPattern2 = substr($SyncPattern2, 0, 3);
}
}
if ($deepscan) {
$ActualFrameLengthValues = array();
$nextoffset = $offset + $framelength;
while ($nextoffset < ($info['avdataend'] - 6)) {
$this->fseek($nextoffset - 1);
$NextSyncPattern = $this->fread(6);
if ((substr($NextSyncPattern, 1, strlen($SyncPattern1)) == $SyncPattern1) || (substr($NextSyncPattern, 1, strlen($SyncPattern2)) == $SyncPattern2)) {
// good - found where expected
$ActualFrameLengthValues[] = $framelength;
} elseif ((substr($NextSyncPattern, 0, strlen($SyncPattern1)) == $SyncPattern1) || (substr($NextSyncPattern, 0, strlen($SyncPattern2)) == $SyncPattern2)) {
// ok - found one byte earlier than expected (last frame wasn't padded, first frame was)
$ActualFrameLengthValues[] = ($framelength - 1);
$nextoffset--;
} elseif ((substr($NextSyncPattern, 2, strlen($SyncPattern1)) == $SyncPattern1) || (substr($NextSyncPattern, 2, strlen($SyncPattern2)) == $SyncPattern2)) {
// ok - found one byte later than expected (last frame was padded, first frame wasn't)
$ActualFrameLengthValues[] = ($framelength + 1);
$nextoffset++;
} else {
$this->error('Did not find expected free-format sync pattern at offset '.$nextoffset);
return false;
}
$nextoffset += $framelength;
}
if (count($ActualFrameLengthValues) > 0) {
$framelength = intval(round(array_sum($ActualFrameLengthValues) / count($ActualFrameLengthValues)));
}
}
return $framelength;
}
/**
* @return bool
*/
public function getOnlyMPEGaudioInfoBruteForce() {
$MPEGaudioHeaderDecodeCache = array();
$MPEGaudioHeaderValidCache = array();
$MPEGaudioHeaderLengthCache = array();
$MPEGaudioVersionLookup = self::MPEGaudioVersionArray();
$MPEGaudioLayerLookup = self::MPEGaudioLayerArray();
$MPEGaudioBitrateLookup = self::MPEGaudioBitrateArray();
$MPEGaudioFrequencyLookup = self::MPEGaudioFrequencyArray();
$MPEGaudioChannelModeLookup = self::MPEGaudioChannelModeArray();
$MPEGaudioModeExtensionLookup = self::MPEGaudioModeExtensionArray();
$MPEGaudioEmphasisLookup = self::MPEGaudioEmphasisArray();
$LongMPEGversionLookup = array();
$LongMPEGlayerLookup = array();
$LongMPEGbitrateLookup = array();
$LongMPEGpaddingLookup = array();
$LongMPEGfrequencyLookup = array();
$Distribution['bitrate'] = array();
$Distribution['frequency'] = array();
$Distribution['layer'] = array();
$Distribution['version'] = array();
$Distribution['padding'] = array();
$info = &$this->getid3->info;
$this->fseek($info['avdataoffset']);
$max_frames_scan = 5000;
$frames_scanned = 0;
$previousvalidframe = $info['avdataoffset'];
while ($this->ftell() < $info['avdataend']) {
set_time_limit(30);
$head4 = $this->fread(4);
if (strlen($head4) < 4) {
break;
}
if ($head4[0] != "\xFF") {
for ($i = 1; $i < 4; $i++) {
if ($head4[$i] == "\xFF") {
$this->fseek($i - 4, SEEK_CUR);
continue 2;
}
}
continue;
}
if (!isset($MPEGaudioHeaderDecodeCache[$head4])) {
$MPEGaudioHeaderDecodeCache[$head4] = self::MPEGaudioHeaderDecode($head4);
}
if (!isset($MPEGaudioHeaderValidCache[$head4])) {
$MPEGaudioHeaderValidCache[$head4] = self::MPEGaudioHeaderValid($MPEGaudioHeaderDecodeCache[$head4], false, false);
}
if ($MPEGaudioHeaderValidCache[$head4]) {
if (!isset($MPEGaudioHeaderLengthCache[$head4])) {
$LongMPEGversionLookup[$head4] = $MPEGaudioVersionLookup[$MPEGaudioHeaderDecodeCache[$head4]['version']];
$LongMPEGlayerLookup[$head4] = $MPEGaudioLayerLookup[$MPEGaudioHeaderDecodeCache[$head4]['layer']];
$LongMPEGbitrateLookup[$head4] = $MPEGaudioBitrateLookup[$LongMPEGversionLookup[$head4]][$LongMPEGlayerLookup[$head4]][$MPEGaudioHeaderDecodeCache[$head4]['bitrate']];
$LongMPEGpaddingLookup[$head4] = (bool) $MPEGaudioHeaderDecodeCache[$head4]['padding'];
$LongMPEGfrequencyLookup[$head4] = $MPEGaudioFrequencyLookup[$LongMPEGversionLookup[$head4]][$MPEGaudioHeaderDecodeCache[$head4]['sample_rate']];
$MPEGaudioHeaderLengthCache[$head4] = self::MPEGaudioFrameLength(
$LongMPEGbitrateLookup[$head4],
$LongMPEGversionLookup[$head4],
$LongMPEGlayerLookup[$head4],
$LongMPEGpaddingLookup[$head4],
$LongMPEGfrequencyLookup[$head4]);
}
if ($MPEGaudioHeaderLengthCache[$head4] > 4) {
$WhereWeWere = $this->ftell();
$this->fseek($MPEGaudioHeaderLengthCache[$head4] - 4, SEEK_CUR);
$next4 = $this->fread(4);
if ($next4[0] == "\xFF") {
if (!isset($MPEGaudioHeaderDecodeCache[$next4])) {
$MPEGaudioHeaderDecodeCache[$next4] = self::MPEGaudioHeaderDecode($next4);
}
if (!isset($MPEGaudioHeaderValidCache[$next4])) {
$MPEGaudioHeaderValidCache[$next4] = self::MPEGaudioHeaderValid($MPEGaudioHeaderDecodeCache[$next4], false, false);
}
if ($MPEGaudioHeaderValidCache[$next4]) {
$this->fseek(-4, SEEK_CUR);
$Distribution['bitrate'][$LongMPEGbitrateLookup[$head4]] = isset($Distribution['bitrate'][$LongMPEGbitrateLookup[$head4]]) ? ++$Distribution['bitrate'][$LongMPEGbitrateLookup[$head4]] : 1;
$Distribution['layer'][$LongMPEGlayerLookup[$head4]] = isset($Distribution['layer'][$LongMPEGlayerLookup[$head4]]) ? ++$Distribution['layer'][$LongMPEGlayerLookup[$head4]] : 1;
$Distribution['version'][$LongMPEGversionLookup[$head4]] = isset($Distribution['version'][$LongMPEGversionLookup[$head4]]) ? ++$Distribution['version'][$LongMPEGversionLookup[$head4]] : 1;
$Distribution['padding'][intval($LongMPEGpaddingLookup[$head4])] = isset($Distribution['padding'][intval($LongMPEGpaddingLookup[$head4])]) ? ++$Distribution['padding'][intval($LongMPEGpaddingLookup[$head4])] : 1;
$Distribution['frequency'][$LongMPEGfrequencyLookup[$head4]] = isset($Distribution['frequency'][$LongMPEGfrequencyLookup[$head4]]) ? ++$Distribution['frequency'][$LongMPEGfrequencyLookup[$head4]] : 1;
if (++$frames_scanned >= $max_frames_scan) {
$pct_data_scanned = ($this->ftell() - $info['avdataoffset']) / ($info['avdataend'] - $info['avdataoffset']);
$this->warning('too many MPEG audio frames to scan, only scanned first '.$max_frames_scan.' frames ('.number_format($pct_data_scanned * 100, 1).'% of file) and extrapolated distribution, playtime and bitrate may be incorrect.');
foreach ($Distribution as $key1 => $value1) {
foreach ($value1 as $key2 => $value2) {
$Distribution[$key1][$key2] = round($value2 / $pct_data_scanned);
}
}
break;
}
continue;
}
}
unset($next4);
$this->fseek($WhereWeWere - 3);
}
}
}
foreach ($Distribution as $key => $value) {
ksort($Distribution[$key], SORT_NUMERIC);
}
ksort($Distribution['version'], SORT_STRING);
$info['mpeg']['audio']['bitrate_distribution'] = $Distribution['bitrate'];
$info['mpeg']['audio']['frequency_distribution'] = $Distribution['frequency'];
$info['mpeg']['audio']['layer_distribution'] = $Distribution['layer'];
$info['mpeg']['audio']['version_distribution'] = $Distribution['version'];
$info['mpeg']['audio']['padding_distribution'] = $Distribution['padding'];
if (count($Distribution['version']) > 1) {
$this->error('Corrupt file - more than one MPEG version detected');
}
if (count($Distribution['layer']) > 1) {
$this->error('Corrupt file - more than one MPEG layer detected');
}
if (count($Distribution['frequency']) > 1) {
$this->error('Corrupt file - more than one MPEG sample rate detected');
}
$bittotal = 0;
foreach ($Distribution['bitrate'] as $bitratevalue => $bitratecount) {
if ($bitratevalue != 'free') {
$bittotal += ($bitratevalue * $bitratecount);
}
}
$info['mpeg']['audio']['frame_count'] = array_sum($Distribution['bitrate']);
if ($info['mpeg']['audio']['frame_count'] == 0) {
$this->error('no MPEG audio frames found');
return false;
}
$info['mpeg']['audio']['bitrate'] = ($bittotal / $info['mpeg']['audio']['frame_count']);
$info['mpeg']['audio']['bitrate_mode'] = ((count($Distribution['bitrate']) > 0) ? 'vbr' : 'cbr');
$info['mpeg']['audio']['sample_rate'] = getid3_lib::array_max($Distribution['frequency'], true);
$info['audio']['bitrate'] = $info['mpeg']['audio']['bitrate'];
$info['audio']['bitrate_mode'] = $info['mpeg']['audio']['bitrate_mode'];
$info['audio']['sample_rate'] = $info['mpeg']['audio']['sample_rate'];
$info['audio']['dataformat'] = 'mp'.getid3_lib::array_max($Distribution['layer'], true);
$info['fileformat'] = $info['audio']['dataformat'];
return true;
}
/**
* @param int $avdataoffset
* @param bool $BitrateHistogram
*
* @return bool
*/
public function getOnlyMPEGaudioInfo($avdataoffset, $BitrateHistogram=false) {
// looks for synch, decodes MPEG audio header
$info = &$this->getid3->info;
static $MPEGaudioVersionLookup;
static $MPEGaudioLayerLookup;
static $MPEGaudioBitrateLookup;
if (empty($MPEGaudioVersionLookup)) {
$MPEGaudioVersionLookup = self::MPEGaudioVersionArray();
$MPEGaudioLayerLookup = self::MPEGaudioLayerArray();
$MPEGaudioBitrateLookup = self::MPEGaudioBitrateArray();
}
$this->fseek($avdataoffset);
$sync_seek_buffer_size = min(128 * 1024, $info['avdataend'] - $avdataoffset);
if ($sync_seek_buffer_size <= 0) {
$this->error('Invalid $sync_seek_buffer_size at offset '.$avdataoffset);
return false;
}
$header = $this->fread($sync_seek_buffer_size);
$sync_seek_buffer_size = strlen($header);
$SynchSeekOffset = 0;
while ($SynchSeekOffset < $sync_seek_buffer_size) {
if ((($avdataoffset + $SynchSeekOffset) < $info['avdataend']) && !feof($this->getid3->fp)) {
if ($SynchSeekOffset > $sync_seek_buffer_size) {
// if a synch's not found within the first 128k bytes, then give up
$this->error('Could not find valid MPEG audio synch within the first '.round($sync_seek_buffer_size / 1024).'kB');
if (isset($info['audio']['bitrate'])) {
unset($info['audio']['bitrate']);
}
if (isset($info['mpeg']['audio'])) {
unset($info['mpeg']['audio']);
}
if (empty($info['mpeg'])) {
unset($info['mpeg']);
}
return false;
} elseif (feof($this->getid3->fp)) {
$this->error('Could not find valid MPEG audio synch before end of file');
if (isset($info['audio']['bitrate'])) {
unset($info['audio']['bitrate']);
}
if (isset($info['mpeg']['audio'])) {
unset($info['mpeg']['audio']);
}
if (isset($info['mpeg']) && (!is_array($info['mpeg']) || (count($info['mpeg']) == 0))) {
unset($info['mpeg']);
}
return false;
}
}
if (($SynchSeekOffset + 1) >= strlen($header)) {
$this->error('Could not find valid MPEG synch before end of file');
return false;
}
if (($header[$SynchSeekOffset] == "\xFF") && ($header[($SynchSeekOffset + 1)] > "\xE0")) { // synch detected
$FirstFrameAVDataOffset = null;
if (!isset($FirstFrameThisfileInfo) && !isset($info['mpeg']['audio'])) {
$FirstFrameThisfileInfo = $info;
$FirstFrameAVDataOffset = $avdataoffset + $SynchSeekOffset;
if (!$this->decodeMPEGaudioHeader($FirstFrameAVDataOffset, $FirstFrameThisfileInfo, false)) {
// if this is the first valid MPEG-audio frame, save it in case it's a VBR header frame and there's
// garbage between this frame and a valid sequence of MPEG-audio frames, to be restored below
unset($FirstFrameThisfileInfo);
}
}
$dummy = $info; // only overwrite real data if valid header found
if ($this->decodeMPEGaudioHeader($avdataoffset + $SynchSeekOffset, $dummy, true)) {
$info = $dummy;
$info['avdataoffset'] = $avdataoffset + $SynchSeekOffset;
switch (isset($info['fileformat']) ? $info['fileformat'] : '') {
case '':
case 'id3':
case 'ape':
case 'mp3':
$info['fileformat'] = 'mp3';
$info['audio']['dataformat'] = 'mp3';
break;
}
if (isset($FirstFrameThisfileInfo) && isset($FirstFrameThisfileInfo['mpeg']['audio']['bitrate_mode']) && ($FirstFrameThisfileInfo['mpeg']['audio']['bitrate_mode'] == 'vbr')) {
if (!(abs($info['audio']['bitrate'] - $FirstFrameThisfileInfo['audio']['bitrate']) <= 1)) {
// If there is garbage data between a valid VBR header frame and a sequence
// of valid MPEG-audio frames the VBR data is no longer discarded.
$info = $FirstFrameThisfileInfo;
$info['avdataoffset'] = $FirstFrameAVDataOffset;
$info['fileformat'] = 'mp3';
$info['audio']['dataformat'] = 'mp3';
$dummy = $info;
unset($dummy['mpeg']['audio']);
$GarbageOffsetStart = $FirstFrameAVDataOffset + $FirstFrameThisfileInfo['mpeg']['audio']['framelength'];
$GarbageOffsetEnd = $avdataoffset + $SynchSeekOffset;
if ($this->decodeMPEGaudioHeader($GarbageOffsetEnd, $dummy, true, true)) {
$info = $dummy;
$info['avdataoffset'] = $GarbageOffsetEnd;
$this->warning('apparently-valid VBR header not used because could not find '.GETID3_MP3_VALID_CHECK_FRAMES.' consecutive MPEG-audio frames immediately after VBR header (garbage data for '.($GarbageOffsetEnd - $GarbageOffsetStart).' bytes between '.$GarbageOffsetStart.' and '.$GarbageOffsetEnd.'), but did find valid CBR stream starting at '.$GarbageOffsetEnd);
} else {
$this->warning('using data from VBR header even though could not find '.GETID3_MP3_VALID_CHECK_FRAMES.' consecutive MPEG-audio frames immediately after VBR header (garbage data for '.($GarbageOffsetEnd - $GarbageOffsetStart).' bytes between '.$GarbageOffsetStart.' and '.$GarbageOffsetEnd.')');
}
}
}
if (isset($info['mpeg']['audio']['bitrate_mode']) && ($info['mpeg']['audio']['bitrate_mode'] == 'vbr') && !isset($info['mpeg']['audio']['VBR_method'])) {
// VBR file with no VBR header
$BitrateHistogram = true;
}
if ($BitrateHistogram) {
$info['mpeg']['audio']['stereo_distribution'] = array('stereo'=>0, 'joint stereo'=>0, 'dual channel'=>0, 'mono'=>0);
$info['mpeg']['audio']['version_distribution'] = array('1'=>0, '2'=>0, '2.5'=>0);
if ($info['mpeg']['audio']['version'] == '1') {
if ($info['mpeg']['audio']['layer'] == 3) {
$info['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 32000=>0, 40000=>0, 48000=>0, 56000=>0, 64000=>0, 80000=>0, 96000=>0, 112000=>0, 128000=>0, 160000=>0, 192000=>0, 224000=>0, 256000=>0, 320000=>0);
} elseif ($info['mpeg']['audio']['layer'] == 2) {
$info['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 32000=>0, 48000=>0, 56000=>0, 64000=>0, 80000=>0, 96000=>0, 112000=>0, 128000=>0, 160000=>0, 192000=>0, 224000=>0, 256000=>0, 320000=>0, 384000=>0);
} elseif ($info['mpeg']['audio']['layer'] == 1) {
$info['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 32000=>0, 64000=>0, 96000=>0, 128000=>0, 160000=>0, 192000=>0, 224000=>0, 256000=>0, 288000=>0, 320000=>0, 352000=>0, 384000=>0, 416000=>0, 448000=>0);
}
} elseif ($info['mpeg']['audio']['layer'] == 1) {
$info['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 32000=>0, 48000=>0, 56000=>0, 64000=>0, 80000=>0, 96000=>0, 112000=>0, 128000=>0, 144000=>0, 160000=>0, 176000=>0, 192000=>0, 224000=>0, 256000=>0);
} else {
$info['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 8000=>0, 16000=>0, 24000=>0, 32000=>0, 40000=>0, 48000=>0, 56000=>0, 64000=>0, 80000=>0, 96000=>0, 112000=>0, 128000=>0, 144000=>0, 160000=>0);
}
$dummy = array('error'=>$info['error'], 'warning'=>$info['warning'], 'avdataend'=>$info['avdataend'], 'avdataoffset'=>$info['avdataoffset']);
$synchstartoffset = $info['avdataoffset'];
$this->fseek($info['avdataoffset']);
// you can play with these numbers:
$max_frames_scan = 50000;
$max_scan_segments = 10;
// don't play with these numbers:
$FastMode = false;
$SynchErrorsFound = 0;
$frames_scanned = 0;
$this_scan_segment = 0;
$frames_scan_per_segment = ceil($max_frames_scan / $max_scan_segments);
$pct_data_scanned = 0;
for ($current_segment = 0; $current_segment < $max_scan_segments; $current_segment++) {
$frames_scanned_this_segment = 0;
if ($this->ftell() >= $info['avdataend']) {
break;
}
$scan_start_offset[$current_segment] = max($this->ftell(), $info['avdataoffset'] + round($current_segment * (($info['avdataend'] - $info['avdataoffset']) / $max_scan_segments)));
if ($current_segment > 0) {
$this->fseek($scan_start_offset[$current_segment]);
$buffer_4k = $this->fread(4096);
for ($j = 0; $j < (strlen($buffer_4k) - 4); $j++) {
if (($buffer_4k[$j] == "\xFF") && ($buffer_4k[($j + 1)] > "\xE0")) { // synch detected
if ($this->decodeMPEGaudioHeader($scan_start_offset[$current_segment] + $j, $dummy, false, false, $FastMode)) {
$calculated_next_offset = $scan_start_offset[$current_segment] + $j + $dummy['mpeg']['audio']['framelength'];
if ($this->decodeMPEGaudioHeader($calculated_next_offset, $dummy, false, false, $FastMode)) {
$scan_start_offset[$current_segment] += $j;
break;
}
}
}
}
}
$synchstartoffset = $scan_start_offset[$current_segment];
while (($synchstartoffset < $info['avdataend']) && $this->decodeMPEGaudioHeader($synchstartoffset, $dummy, false, false, $FastMode)) {
$FastMode = true;
$thisframebitrate = $MPEGaudioBitrateLookup[$MPEGaudioVersionLookup[$dummy['mpeg']['audio']['raw']['version']]][$MPEGaudioLayerLookup[$dummy['mpeg']['audio']['raw']['layer']]][$dummy['mpeg']['audio']['raw']['bitrate']];
if (empty($dummy['mpeg']['audio']['framelength'])) {
$SynchErrorsFound++;
$synchstartoffset++;
} else {
getid3_lib::safe_inc($info['mpeg']['audio']['bitrate_distribution'][$thisframebitrate]);
getid3_lib::safe_inc($info['mpeg']['audio']['stereo_distribution'][$dummy['mpeg']['audio']['channelmode']]);
getid3_lib::safe_inc($info['mpeg']['audio']['version_distribution'][$dummy['mpeg']['audio']['version']]);
$synchstartoffset += $dummy['mpeg']['audio']['framelength'];
}
$frames_scanned++;
if ($frames_scan_per_segment && (++$frames_scanned_this_segment >= $frames_scan_per_segment)) {
$this_pct_scanned = ($this->ftell() - $scan_start_offset[$current_segment]) / ($info['avdataend'] - $info['avdataoffset']);
if (($current_segment == 0) && (($this_pct_scanned * $max_scan_segments) >= 1)) {
// file likely contains < $max_frames_scan, just scan as one segment
$max_scan_segments = 1;
$frames_scan_per_segment = $max_frames_scan;
} else {
$pct_data_scanned += $this_pct_scanned;
break;
}
}
}
}
if ($pct_data_scanned > 0) {
$this->warning('too many MPEG audio frames to scan, only scanned '.$frames_scanned.' frames in '.$max_scan_segments.' segments ('.number_format($pct_data_scanned * 100, 1).'% of file) and extrapolated distribution, playtime and bitrate may be incorrect.');
foreach ($info['mpeg']['audio'] as $key1 => $value1) {
if (!preg_match('#_distribution$#i', $key1)) {
continue;
}
foreach ($value1 as $key2 => $value2) {
$info['mpeg']['audio'][$key1][$key2] = round($value2 / $pct_data_scanned);
}
}
}
if ($SynchErrorsFound > 0) {
$this->warning('Found '.$SynchErrorsFound.' synch errors in histogram analysis');
//return false;
}
$bittotal = 0;
$framecounter = 0;
foreach ($info['mpeg']['audio']['bitrate_distribution'] as $bitratevalue => $bitratecount) {
$framecounter += $bitratecount;
if ($bitratevalue != 'free') {
$bittotal += ($bitratevalue * $bitratecount);
}
}
if ($framecounter == 0) {
$this->error('Corrupt MP3 file: framecounter == zero');
return false;
}
$info['mpeg']['audio']['frame_count'] = getid3_lib::CastAsInt($framecounter);
$info['mpeg']['audio']['bitrate'] = ($bittotal / $framecounter);
$info['audio']['bitrate'] = $info['mpeg']['audio']['bitrate'];
// Definitively set VBR vs CBR, even if the Xing/LAME/VBRI header says differently
$distinct_bitrates = 0;
foreach ($info['mpeg']['audio']['bitrate_distribution'] as $bitrate_value => $bitrate_count) {
if ($bitrate_count > 0) {
$distinct_bitrates++;
}
}
if ($distinct_bitrates > 1) {
$info['mpeg']['audio']['bitrate_mode'] = 'vbr';
} else {
$info['mpeg']['audio']['bitrate_mode'] = 'cbr';
}
$info['audio']['bitrate_mode'] = $info['mpeg']['audio']['bitrate_mode'];
}
break; // exit while()
}
}
$SynchSeekOffset++;
if (($avdataoffset + $SynchSeekOffset) >= $info['avdataend']) {
// end of file/data
if (empty($info['mpeg']['audio'])) {
$this->error('could not find valid MPEG synch before end of file');
if (isset($info['audio']['bitrate'])) {
unset($info['audio']['bitrate']);
}
if (isset($info['mpeg']['audio'])) {
unset($info['mpeg']['audio']);
}
if (isset($info['mpeg']) && (!is_array($info['mpeg']) || empty($info['mpeg']))) {
unset($info['mpeg']);
}
return false;
}
break;
}
}
$info['audio']['channels'] = $info['mpeg']['audio']['channels'];
$info['audio']['channelmode'] = $info['mpeg']['audio']['channelmode'];
$info['audio']['sample_rate'] = $info['mpeg']['audio']['sample_rate'];
return true;
}
/**
* @return array
*/
public static function MPEGaudioVersionArray() {
static $MPEGaudioVersion = array('2.5', false, '2', '1');
return $MPEGaudioVersion;
}
/**
* @return array
*/
public static function MPEGaudioLayerArray() {
static $MPEGaudioLayer = array(false, 3, 2, 1);
return $MPEGaudioLayer;
}
/**
* @return array
*/
public static function MPEGaudioBitrateArray() {
static $MPEGaudioBitrate;
if (empty($MPEGaudioBitrate)) {
$MPEGaudioBitrate = array (
'1' => array (1 => array('free', 32000, 64000, 96000, 128000, 160000, 192000, 224000, 256000, 288000, 320000, 352000, 384000, 416000, 448000),
2 => array('free', 32000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 160000, 192000, 224000, 256000, 320000, 384000),
3 => array('free', 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 160000, 192000, 224000, 256000, 320000)
),
'2' => array (1 => array('free', 32000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, 160000, 176000, 192000, 224000, 256000),
2 => array('free', 8000, 16000, 24000, 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, 160000),
)
);
$MPEGaudioBitrate['2'][3] = $MPEGaudioBitrate['2'][2];
$MPEGaudioBitrate['2.5'] = $MPEGaudioBitrate['2'];
}
return $MPEGaudioBitrate;
}
/**
* @return array
*/
public static function MPEGaudioFrequencyArray() {
static $MPEGaudioFrequency;
if (empty($MPEGaudioFrequency)) {
$MPEGaudioFrequency = array (
'1' => array(44100, 48000, 32000),
'2' => array(22050, 24000, 16000),
'2.5' => array(11025, 12000, 8000)
);
}
return $MPEGaudioFrequency;
}
/**
* @return array
*/
public static function MPEGaudioChannelModeArray() {
static $MPEGaudioChannelMode = array('stereo', 'joint stereo', 'dual channel', 'mono');
return $MPEGaudioChannelMode;
}
/**
* @return array
*/
public static function MPEGaudioModeExtensionArray() {
static $MPEGaudioModeExtension;
if (empty($MPEGaudioModeExtension)) {
$MPEGaudioModeExtension = array (
1 => array('4-31', '8-31', '12-31', '16-31'),
2 => array('4-31', '8-31', '12-31', '16-31'),
3 => array('', 'IS', 'MS', 'IS+MS')
);
}
return $MPEGaudioModeExtension;
}
/**
* @return array
*/
public static function MPEGaudioEmphasisArray() {
static $MPEGaudioEmphasis = array('none', '50/15ms', false, 'CCIT J.17');
return $MPEGaudioEmphasis;
}
/**
* @param string $head4
* @param bool $allowBitrate15
*
* @return bool
*/
public static function MPEGaudioHeaderBytesValid($head4, $allowBitrate15=false) {
return self::MPEGaudioHeaderValid(self::MPEGaudioHeaderDecode($head4), false, $allowBitrate15);
}
/**
* @param array $rawarray
* @param bool $echoerrors
* @param bool $allowBitrate15
*
* @return bool
*/
public static function MPEGaudioHeaderValid($rawarray, $echoerrors=false, $allowBitrate15=false) {
if (!isset($rawarray['synch']) || ($rawarray['synch'] & 0x0FFE) != 0x0FFE) {
return false;
}
static $MPEGaudioVersionLookup;
static $MPEGaudioLayerLookup;
static $MPEGaudioBitrateLookup;
static $MPEGaudioFrequencyLookup;
static $MPEGaudioChannelModeLookup;
static $MPEGaudioModeExtensionLookup;
static $MPEGaudioEmphasisLookup;
if (empty($MPEGaudioVersionLookup)) {
$MPEGaudioVersionLookup = self::MPEGaudioVersionArray();
$MPEGaudioLayerLookup = self::MPEGaudioLayerArray();
$MPEGaudioBitrateLookup = self::MPEGaudioBitrateArray();
$MPEGaudioFrequencyLookup = self::MPEGaudioFrequencyArray();
$MPEGaudioChannelModeLookup = self::MPEGaudioChannelModeArray();
$MPEGaudioModeExtensionLookup = self::MPEGaudioModeExtensionArray();
$MPEGaudioEmphasisLookup = self::MPEGaudioEmphasisArray();
}
if (isset($MPEGaudioVersionLookup[$rawarray['version']])) {
$decodedVersion = $MPEGaudioVersionLookup[$rawarray['version']];
} else {
echo ($echoerrors ? "\n".'invalid Version ('.$rawarray['version'].')' : '');
return false;
}
if (isset($MPEGaudioLayerLookup[$rawarray['layer']])) {
$decodedLayer = $MPEGaudioLayerLookup[$rawarray['layer']];
} else {
echo ($echoerrors ? "\n".'invalid Layer ('.$rawarray['layer'].')' : '');
return false;
}
if (!isset($MPEGaudioBitrateLookup[$decodedVersion][$decodedLayer][$rawarray['bitrate']])) {
echo ($echoerrors ? "\n".'invalid Bitrate ('.$rawarray['bitrate'].')' : '');
if ($rawarray['bitrate'] == 15) {
// known issue in LAME 3.90 - 3.93.1 where free-format has bitrate ID of 15 instead of 0
// let it go through here otherwise file will not be identified
if (!$allowBitrate15) {
return false;
}
} else {
return false;
}
}
if (!isset($MPEGaudioFrequencyLookup[$decodedVersion][$rawarray['sample_rate']])) {
echo ($echoerrors ? "\n".'invalid Frequency ('.$rawarray['sample_rate'].')' : '');
return false;
}
if (!isset($MPEGaudioChannelModeLookup[$rawarray['channelmode']])) {
echo ($echoerrors ? "\n".'invalid ChannelMode ('.$rawarray['channelmode'].')' : '');
return false;
}
if (!isset($MPEGaudioModeExtensionLookup[$decodedLayer][$rawarray['modeextension']])) {
echo ($echoerrors ? "\n".'invalid Mode Extension ('.$rawarray['modeextension'].')' : '');
return false;
}
if (!isset($MPEGaudioEmphasisLookup[$rawarray['emphasis']])) {
echo ($echoerrors ? "\n".'invalid Emphasis ('.$rawarray['emphasis'].')' : '');
return false;
}
// These are just either set or not set, you can't mess that up :)
// $rawarray['protection'];
// $rawarray['padding'];
// $rawarray['private'];
// $rawarray['copyright'];
// $rawarray['original'];
return true;
}
/**
* @param string $Header4Bytes
*
* @return array|false
*/
public static function MPEGaudioHeaderDecode($Header4Bytes) {
// AAAA AAAA AAAB BCCD EEEE FFGH IIJJ KLMM
// A - Frame sync (all bits set)
// B - MPEG Audio version ID
// C - Layer description
// D - Protection bit
// E - Bitrate index
// F - Sampling rate frequency index
// G - Padding bit
// H - Private bit
// I - Channel Mode
// J - Mode extension (Only if Joint stereo)
// K - Copyright
// L - Original
// M - Emphasis
if (strlen($Header4Bytes) != 4) {
return false;
}
$MPEGrawHeader['synch'] = (getid3_lib::BigEndian2Int(substr($Header4Bytes, 0, 2)) & 0xFFE0) >> 4;
$MPEGrawHeader['version'] = (ord($Header4Bytes[1]) & 0x18) >> 3; // BB
$MPEGrawHeader['layer'] = (ord($Header4Bytes[1]) & 0x06) >> 1; // CC
$MPEGrawHeader['protection'] = (ord($Header4Bytes[1]) & 0x01); // D
$MPEGrawHeader['bitrate'] = (ord($Header4Bytes[2]) & 0xF0) >> 4; // EEEE
$MPEGrawHeader['sample_rate'] = (ord($Header4Bytes[2]) & 0x0C) >> 2; // FF
$MPEGrawHeader['padding'] = (ord($Header4Bytes[2]) & 0x02) >> 1; // G
$MPEGrawHeader['private'] = (ord($Header4Bytes[2]) & 0x01); // H
$MPEGrawHeader['channelmode'] = (ord($Header4Bytes[3]) & 0xC0) >> 6; // II
$MPEGrawHeader['modeextension'] = (ord($Header4Bytes[3]) & 0x30) >> 4; // JJ
$MPEGrawHeader['copyright'] = (ord($Header4Bytes[3]) & 0x08) >> 3; // K
$MPEGrawHeader['original'] = (ord($Header4Bytes[3]) & 0x04) >> 2; // L
$MPEGrawHeader['emphasis'] = (ord($Header4Bytes[3]) & 0x03); // MM
return $MPEGrawHeader;
}
/**
* @param int|string $bitrate
* @param string $version
* @param string $layer
* @param bool $padding
* @param int $samplerate
*
* @return int|false
*/
public static function MPEGaudioFrameLength(&$bitrate, &$version, &$layer, $padding, &$samplerate) {
static $AudioFrameLengthCache = array();
if (!isset($AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate])) {
$AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate] = false;
if ($bitrate != 'free') {
if ($version == '1') {
if ($layer == '1') {
// For Layer I slot is 32 bits long
$FrameLengthCoefficient = 48;
$SlotLength = 4;
} else { // Layer 2 / 3
// for Layer 2 and Layer 3 slot is 8 bits long.
$FrameLengthCoefficient = 144;
$SlotLength = 1;
}
} else { // MPEG-2 / MPEG-2.5
if ($layer == '1') {
// For Layer I slot is 32 bits long
$FrameLengthCoefficient = 24;
$SlotLength = 4;
} elseif ($layer == '2') {
// for Layer 2 and Layer 3 slot is 8 bits long.
$FrameLengthCoefficient = 144;
$SlotLength = 1;
} else { // layer 3
// for Layer 2 and Layer 3 slot is 8 bits long.
$FrameLengthCoefficient = 72;
$SlotLength = 1;
}
}
// FrameLengthInBytes = ((Coefficient * BitRate) / SampleRate) + Padding
if ($samplerate > 0) {
$NewFramelength = ($FrameLengthCoefficient * $bitrate) / $samplerate;
$NewFramelength = floor($NewFramelength / $SlotLength) * $SlotLength; // round to next-lower multiple of SlotLength (1 byte for Layer 2/3, 4 bytes for Layer I)
if ($padding) {
$NewFramelength += $SlotLength;
}
$AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate] = (int) $NewFramelength;
}
}
}
return $AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate];
}
/**
* @param float|int $bit_rate
*
* @return int|float|string
*/
public static function ClosestStandardMP3Bitrate($bit_rate) {
static $standard_bit_rates = array (320000, 256000, 224000, 192000, 160000, 128000, 112000, 96000, 80000, 64000, 56000, 48000, 40000, 32000, 24000, 16000, 8000);
static $bit_rate_table = array (0=>'-');
$round_bit_rate = intval(round($bit_rate, -3));
if (!isset($bit_rate_table[$round_bit_rate])) {
if ($round_bit_rate > max($standard_bit_rates)) {
$bit_rate_table[$round_bit_rate] = round($bit_rate, 2 - strlen($bit_rate));
} else {
$bit_rate_table[$round_bit_rate] = max($standard_bit_rates);
foreach ($standard_bit_rates as $standard_bit_rate) {
if ($round_bit_rate >= $standard_bit_rate + (($bit_rate_table[$round_bit_rate] - $standard_bit_rate) / 2)) {
break;
}
$bit_rate_table[$round_bit_rate] = $standard_bit_rate;
}
}
}
return $bit_rate_table[$round_bit_rate];
}
/**
* @param string $version
* @param string $channelmode
*
* @return int
*/
public static function XingVBRidOffset($version, $channelmode) {
static $XingVBRidOffsetCache = array();
if (empty($XingVBRidOffsetCache)) {
$XingVBRidOffsetCache = array (
'1' => array ('mono' => 0x15, // 4 + 17 = 21
'stereo' => 0x24, // 4 + 32 = 36
'joint stereo' => 0x24,
'dual channel' => 0x24
),
'2' => array ('mono' => 0x0D, // 4 + 9 = 13
'stereo' => 0x15, // 4 + 17 = 21
'joint stereo' => 0x15,
'dual channel' => 0x15
),
'2.5' => array ('mono' => 0x15,
'stereo' => 0x15,
'joint stereo' => 0x15,
'dual channel' => 0x15
)
);
}
return $XingVBRidOffsetCache[$version][$channelmode];
}
/**
* @param int $VBRmethodID
*
* @return string
*/
public static function LAMEvbrMethodLookup($VBRmethodID) {
static $LAMEvbrMethodLookup = array(
0x00 => 'unknown',
0x01 => 'cbr',
0x02 => 'abr',
0x03 => 'vbr-old / vbr-rh',
0x04 => 'vbr-new / vbr-mtrh',
0x05 => 'vbr-mt',
0x06 => 'vbr (full vbr method 4)',
0x08 => 'cbr (constant bitrate 2 pass)',
0x09 => 'abr (2 pass)',
0x0F => 'reserved'
);
return (isset($LAMEvbrMethodLookup[$VBRmethodID]) ? $LAMEvbrMethodLookup[$VBRmethodID] : '');
}
/**
* @param int $StereoModeID
*
* @return string
*/
public static function LAMEmiscStereoModeLookup($StereoModeID) {
static $LAMEmiscStereoModeLookup = array(
0 => 'mono',
1 => 'stereo',
2 => 'dual mono',
3 => 'joint stereo',
4 => 'forced stereo',
5 => 'auto',
6 => 'intensity stereo',
7 => 'other'
);
return (isset($LAMEmiscStereoModeLookup[$StereoModeID]) ? $LAMEmiscStereoModeLookup[$StereoModeID] : '');
}
/**
* @param int $SourceSampleFrequencyID
*
* @return string
*/
public static function LAMEmiscSourceSampleFrequencyLookup($SourceSampleFrequencyID) {
static $LAMEmiscSourceSampleFrequencyLookup = array(
0 => '<= 32 kHz',
1 => '44.1 kHz',
2 => '48 kHz',
3 => '> 48kHz'
);
return (isset($LAMEmiscSourceSampleFrequencyLookup[$SourceSampleFrequencyID]) ? $LAMEmiscSourceSampleFrequencyLookup[$SourceSampleFrequencyID] : '');
}
/**
* @param int $SurroundInfoID
*
* @return string
*/
public static function LAMEsurroundInfoLookup($SurroundInfoID) {
static $LAMEsurroundInfoLookup = array(
0 => 'no surround info',
1 => 'DPL encoding',
2 => 'DPL2 encoding',
3 => 'Ambisonic encoding'
);
return (isset($LAMEsurroundInfoLookup[$SurroundInfoID]) ? $LAMEsurroundInfoLookup[$SurroundInfoID] : 'reserved');
}
/**
* @param array $LAMEtag
*
* @return string
*/
public static function LAMEpresetUsedLookup($LAMEtag) {
if ($LAMEtag['preset_used_id'] == 0) {
// no preset used (LAME >=3.93)
// no preset recorded (LAME <3.93)
return '';
}
$LAMEpresetUsedLookup = array();
///// THIS PART CANNOT BE STATIC .
for ($i = 8; $i <= 320; $i++) {
switch ($LAMEtag['vbr_method']) {
case 'cbr':
$LAMEpresetUsedLookup[$i] = '--alt-preset '.$LAMEtag['vbr_method'].' '.$i;
break;
case 'abr':
default: // other VBR modes shouldn't be here(?)
$LAMEpresetUsedLookup[$i] = '--alt-preset '.$i;
break;
}
}
// named old-style presets (studio, phone, voice, etc) are handled in GuessEncoderOptions()
// named alt-presets
$LAMEpresetUsedLookup[1000] = '--r3mix';
$LAMEpresetUsedLookup[1001] = '--alt-preset standard';
$LAMEpresetUsedLookup[1002] = '--alt-preset extreme';
$LAMEpresetUsedLookup[1003] = '--alt-preset insane';
$LAMEpresetUsedLookup[1004] = '--alt-preset fast standard';
$LAMEpresetUsedLookup[1005] = '--alt-preset fast extreme';
$LAMEpresetUsedLookup[1006] = '--alt-preset medium';
$LAMEpresetUsedLookup[1007] = '--alt-preset fast medium';
// LAME 3.94 additions/changes
$LAMEpresetUsedLookup[1010] = '--preset portable'; // 3.94a15 Oct 21 2003
$LAMEpresetUsedLookup[1015] = '--preset radio'; // 3.94a15 Oct 21 2003
$LAMEpresetUsedLookup[320] = '--preset insane'; // 3.94a15 Nov 12 2003
$LAMEpresetUsedLookup[410] = '-V9';
$LAMEpresetUsedLookup[420] = '-V8';
$LAMEpresetUsedLookup[440] = '-V6';
$LAMEpresetUsedLookup[430] = '--preset radio'; // 3.94a15 Nov 12 2003
$LAMEpresetUsedLookup[450] = '--preset '.(($LAMEtag['raw']['vbr_method'] == 4) ? 'fast ' : '').'portable'; // 3.94a15 Nov 12 2003
$LAMEpresetUsedLookup[460] = '--preset '.(($LAMEtag['raw']['vbr_method'] == 4) ? 'fast ' : '').'medium'; // 3.94a15 Nov 12 2003
$LAMEpresetUsedLookup[470] = '--r3mix'; // 3.94b1 Dec 18 2003
$LAMEpresetUsedLookup[480] = '--preset '.(($LAMEtag['raw']['vbr_method'] == 4) ? 'fast ' : '').'standard'; // 3.94a15 Nov 12 2003
$LAMEpresetUsedLookup[490] = '-V1';
$LAMEpresetUsedLookup[500] = '--preset '.(($LAMEtag['raw']['vbr_method'] == 4) ? 'fast ' : '').'extreme'; // 3.94a15 Nov 12 2003
return (isset($LAMEpresetUsedLookup[$LAMEtag['preset_used_id']]) ? $LAMEpresetUsedLookup[$LAMEtag['preset_used_id']] : 'new/unknown preset: '.$LAMEtag['preset_used_id'].' - report to info@getid3.org');
}
}
<?php
/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org> //
// available at https://github.com/JamesHeinrich/getID3 //
// or https://www.getid3.org //
// or http://getid3.sourceforge.net //
// see readme.txt for more details //
/////////////////////////////////////////////////////////////////
// //
// module.audio.mpc.php //
// module for analyzing Musepack/MPEG+ Audio files //
// dependencies: NONE //
// ///
/////////////////////////////////////////////////////////////////
if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
exit;
}
class getid3_mpc extends getid3_handler
{
/**
* @return bool
*/
public function Analyze() {
$info = &$this->getid3->info;
$info['mpc']['header'] = array();
$thisfile_mpc_header = &$info['mpc']['header'];
$info['fileformat'] = 'mpc';
$info['audio']['dataformat'] = 'mpc';
$info['audio']['bitrate_mode'] = 'vbr';
$info['audio']['channels'] = 2; // up to SV7 the format appears to have been hardcoded for stereo only
$info['audio']['lossless'] = false;
$this->fseek($info['avdataoffset']);
$MPCheaderData = $this->fread(4);
$info['mpc']['header']['preamble'] = substr($MPCheaderData, 0, 4); // should be 'MPCK' (SV8) or 'MP+' (SV7), otherwise possible stream data (SV4-SV6)
if (preg_match('#^MPCK#', $info['mpc']['header']['preamble'])) {
// this is SV8
return $this->ParseMPCsv8();
} elseif (preg_match('#^MP\+#', $info['mpc']['header']['preamble'])) {
// this is SV7
return $this->ParseMPCsv7();
} elseif (preg_match('/^[\x00\x01\x10\x11\x40\x41\x50\x51\x80\x81\x90\x91\xC0\xC1\xD0\xD1][\x20-37][\x00\x20\x40\x60\x80\xA0\xC0\xE0]/s', $MPCheaderData)) {
// this is SV4 - SV6, handle seperately
return $this->ParseMPCsv6();
} else {
$this->error('Expecting "MP+" or "MPCK" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($MPCheaderData, 0, 4)).'"');
unset($info['fileformat']);
unset($info['mpc']);
return false;
}
}
/**
* @return bool
*/
public function ParseMPCsv8() {
// this is SV8
// http://trac.musepack.net/trac/wiki/SV8Specification
$info = &$this->getid3->info;
$thisfile_mpc_header = &$info['mpc']['header'];
$keyNameSize = 2;
$maxHandledPacketLength = 9; // specs say: "n*8; 0 < n < 10"
$offset = $this->ftell();
while ($offset < $info['avdataend']) {
$thisPacket = array();
$thisPacket['offset'] = $offset;
$packet_offset = 0;
// Size is a variable-size field, could be 1-4 bytes (possibly more?)
// read enough data in and figure out the exact size later
$MPCheaderData = $this->fread($keyNameSize + $maxHandledPacketLength);
$packet_offset += $keyNameSize;
$thisPacket['key'] = substr($MPCheaderData, 0, $keyNameSize);
$thisPacket['key_name'] = $this->MPCsv8PacketName($thisPacket['key']);
if ($thisPacket['key'] == $thisPacket['key_name']) {
$this->error('Found unexpected key value "'.$thisPacket['key'].'" at offset '.$thisPacket['offset']);
return false;
}
$packetLength = 0;
$thisPacket['packet_size'] = $this->SV8variableLengthInteger(substr($MPCheaderData, $keyNameSize), $packetLength); // includes keyname and packet_size field
if ($thisPacket['packet_size'] === false) {
$this->error('Did not find expected packet length within '.$maxHandledPacketLength.' bytes at offset '.($thisPacket['offset'] + $keyNameSize));
return false;
}
$packet_offset += $packetLength;
$offset += $thisPacket['packet_size'];
switch ($thisPacket['key']) {
case 'SH': // Stream Header
$moreBytesToRead = $thisPacket['packet_size'] - $keyNameSize - $maxHandledPacketLength;
if ($moreBytesToRead > 0) {
$MPCheaderData .= $this->fread($moreBytesToRead);
}
$thisPacket['crc'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 4));
$packet_offset += 4;
$thisPacket['stream_version'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 1));
$packet_offset += 1;
$packetLength = 0;
$thisPacket['sample_count'] = $this->SV8variableLengthInteger(substr($MPCheaderData, $packet_offset, $maxHandledPacketLength), $packetLength);
$packet_offset += $packetLength;
$packetLength = 0;
$thisPacket['beginning_silence'] = $this->SV8variableLengthInteger(substr($MPCheaderData, $packet_offset, $maxHandledPacketLength), $packetLength);
$packet_offset += $packetLength;
$otherUsefulData = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 2));
$packet_offset += 2;
$thisPacket['sample_frequency_raw'] = (($otherUsefulData & 0xE000) >> 13);
$thisPacket['max_bands_used'] = (($otherUsefulData & 0x1F00) >> 8);
$thisPacket['channels'] = (($otherUsefulData & 0x00F0) >> 4) + 1;
$thisPacket['ms_used'] = (bool) (($otherUsefulData & 0x0008) >> 3);
$thisPacket['audio_block_frames'] = (($otherUsefulData & 0x0007) >> 0);
$thisPacket['sample_frequency'] = $this->MPCfrequencyLookup($thisPacket['sample_frequency_raw']);
$thisfile_mpc_header['mid_side_stereo'] = $thisPacket['ms_used'];
$thisfile_mpc_header['sample_rate'] = $thisPacket['sample_frequency'];
$thisfile_mpc_header['samples'] = $thisPacket['sample_count'];
$thisfile_mpc_header['stream_version_major'] = $thisPacket['stream_version'];
$info['audio']['channels'] = $thisPacket['channels'];
$info['audio']['sample_rate'] = $thisPacket['sample_frequency'];
$info['playtime_seconds'] = $thisPacket['sample_count'] / $thisPacket['sample_frequency'];
$info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
break;
case 'RG': // Replay Gain
$moreBytesToRead = $thisPacket['packet_size'] - $keyNameSize - $maxHandledPacketLength;
if ($moreBytesToRead > 0) {
$MPCheaderData .= $this->fread($moreBytesToRead);
}
$thisPacket['replaygain_version'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 1));
$packet_offset += 1;
$thisPacket['replaygain_title_gain'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 2));
$packet_offset += 2;
$thisPacket['replaygain_title_peak'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 2));
$packet_offset += 2;
$thisPacket['replaygain_album_gain'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 2));
$packet_offset += 2;
$thisPacket['replaygain_album_peak'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 2));
$packet_offset += 2;
if ($thisPacket['replaygain_title_gain']) { $info['replay_gain']['title']['gain'] = $thisPacket['replaygain_title_gain']; }
if ($thisPacket['replaygain_title_peak']) { $info['replay_gain']['title']['peak'] = $thisPacket['replaygain_title_peak']; }
if ($thisPacket['replaygain_album_gain']) { $info['replay_gain']['album']['gain'] = $thisPacket['replaygain_album_gain']; }
if ($thisPacket['replaygain_album_peak']) { $info['replay_gain']['album']['peak'] = $thisPacket['replaygain_album_peak']; }
break;
case 'EI': // Encoder Info
$moreBytesToRead = $thisPacket['packet_size'] - $keyNameSize - $maxHandledPacketLength;
if ($moreBytesToRead > 0) {
$MPCheaderData .= $this->fread($moreBytesToRead);
}
$profile_pns = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 1));
$packet_offset += 1;
$quality_int = (($profile_pns & 0xF0) >> 4);
$quality_dec = (($profile_pns & 0x0E) >> 3);
$thisPacket['quality'] = (float) $quality_int + ($quality_dec / 8);
$thisPacket['pns_tool'] = (bool) (($profile_pns & 0x01) >> 0);
$thisPacket['version_major'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 1));
$packet_offset += 1;
$thisPacket['version_minor'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 1));
$packet_offset += 1;
$thisPacket['version_build'] = getid3_lib::BigEndian2Int(substr($MPCheaderData, $packet_offset, 1));
$packet_offset += 1;
$thisPacket['version'] = $thisPacket['version_major'].'.'.$thisPacket['version_minor'].'.'.$thisPacket['version_build'];
$info['audio']['encoder'] = 'MPC v'.$thisPacket['version'].' ('.(($thisPacket['version_minor'] % 2) ? 'unstable' : 'stable').')';
$thisfile_mpc_header['encoder_version'] = $info['audio']['encoder'];
//$thisfile_mpc_header['quality'] = (float) ($thisPacket['quality'] / 1.5875); // values can range from 0.000 to 15.875, mapped to qualities of 0.0 to 10.0
$thisfile_mpc_header['quality'] = (float) ($thisPacket['quality'] - 5); // values can range from 0.000 to 15.875, of which 0..4 are "reserved/experimental", and 5..15 are mapped to qualities of 0.0 to 10.0
break;
case 'SO': // Seek Table Offset
$packetLength = 0;
$thisPacket['seek_table_offset'] = $thisPacket['offset'] + $this->SV8variableLengthInteger(substr($MPCheaderData, $packet_offset, $maxHandledPacketLength), $packetLength);
$packet_offset += $packetLength;
break;
case 'ST': // Seek Table
case 'SE': // Stream End
case 'AP': // Audio Data
// nothing useful here, just skip this packet
$thisPacket = array();
break;
default:
$this->error('Found unhandled key type "'.$thisPacket['key'].'" at offset '.$thisPacket['offset']);
return false;
}
if (!empty($thisPacket)) {
$info['mpc']['packets'][] = $thisPacket;
}
$this->fseek($offset);
}
$thisfile_mpc_header['size'] = $offset;
return true;
}
/**
* @return bool
*/
public function ParseMPCsv7() {
// this is SV7
// http://www.uni-jena.de/~pfk/mpp/sv8/header.html
$info = &$this->getid3->info;
$thisfile_mpc_header = &$info['mpc']['header'];
$offset = 0;
$thisfile_mpc_header['size'] = 28;
$MPCheaderData = $info['mpc']['header']['preamble'];
$MPCheaderData .= $this->fread($thisfile_mpc_header['size'] - strlen($info['mpc']['header']['preamble']));
$offset = strlen('MP+');
$StreamVersionByte = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 1));
$offset += 1;
$thisfile_mpc_header['stream_version_major'] = ($StreamVersionByte & 0x0F) >> 0;
$thisfile_mpc_header['stream_version_minor'] = ($StreamVersionByte & 0xF0) >> 4; // should always be 0, subversions no longer exist in SV8
$thisfile_mpc_header['frame_count'] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 4));
$offset += 4;
if ($thisfile_mpc_header['stream_version_major'] != 7) {
$this->error('Only Musepack SV7 supported (this file claims to be v'.$thisfile_mpc_header['stream_version_major'].')');
return false;
}
$FlagsDWORD1 = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 4));
$offset += 4;
$thisfile_mpc_header['intensity_stereo'] = (bool) (($FlagsDWORD1 & 0x80000000) >> 31);
$thisfile_mpc_header['mid_side_stereo'] = (bool) (($FlagsDWORD1 & 0x40000000) >> 30);
$thisfile_mpc_header['max_subband'] = ($FlagsDWORD1 & 0x3F000000) >> 24;
$thisfile_mpc_header['raw']['profile'] = ($FlagsDWORD1 & 0x00F00000) >> 20;
$thisfile_mpc_header['begin_loud'] = (bool) (($FlagsDWORD1 & 0x00080000) >> 19);
$thisfile_mpc_header['end_loud'] = (bool) (($FlagsDWORD1 & 0x00040000) >> 18);
$thisfile_mpc_header['raw']['sample_rate'] = ($FlagsDWORD1 & 0x00030000) >> 16;
$thisfile_mpc_header['max_level'] = ($FlagsDWORD1 & 0x0000FFFF);
$thisfile_mpc_header['raw']['title_peak'] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 2));
$offset += 2;
$thisfile_mpc_header['raw']['title_gain'] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 2), true);
$offset += 2;
$thisfile_mpc_header['raw']['album_peak'] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 2));
$offset += 2;
$thisfile_mpc_header['raw']['album_gain'] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 2), true);
$offset += 2;
$FlagsDWORD2 = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 4));
$offset += 4;
$thisfile_mpc_header['true_gapless'] = (bool) (($FlagsDWORD2 & 0x80000000) >> 31);
$thisfile_mpc_header['last_frame_length'] = ($FlagsDWORD2 & 0x7FF00000) >> 20;
$thisfile_mpc_header['raw']['not_sure_what'] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 3));
$offset += 3;
$thisfile_mpc_header['raw']['encoder_version'] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, $offset, 1));
$offset += 1;
$thisfile_mpc_header['profile'] = $this->MPCprofileNameLookup($thisfile_mpc_header['raw']['profile']);
$thisfile_mpc_header['sample_rate'] = $this->MPCfrequencyLookup($thisfile_mpc_header['raw']['sample_rate']);
if ($thisfile_mpc_header['sample_rate'] == 0) {
$this->error('Corrupt MPC file: frequency == zero');
return false;
}
$info['audio']['sample_rate'] = $thisfile_mpc_header['sample_rate'];
$thisfile_mpc_header['samples'] = ((($thisfile_mpc_header['frame_count'] - 1) * 1152) + $thisfile_mpc_header['last_frame_length']) * $info['audio']['channels'];
$info['playtime_seconds'] = ($thisfile_mpc_header['samples'] / $info['audio']['channels']) / $info['audio']['sample_rate'];
if ($info['playtime_seconds'] == 0) {
$this->error('Corrupt MPC file: playtime_seconds == zero');
return false;
}
// add size of file header to avdataoffset - calc bitrate correctly + MD5 data
$info['avdataoffset'] += $thisfile_mpc_header['size'];
$info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
$thisfile_mpc_header['title_peak'] = $thisfile_mpc_header['raw']['title_peak'];
$thisfile_mpc_header['title_peak_db'] = $this->MPCpeakDBLookup($thisfile_mpc_header['title_peak']);
if ($thisfile_mpc_header['raw']['title_gain'] < 0) {
$thisfile_mpc_header['title_gain_db'] = (float) (32768 + $thisfile_mpc_header['raw']['title_gain']) / -100;
} else {
$thisfile_mpc_header['title_gain_db'] = (float) $thisfile_mpc_header['raw']['title_gain'] / 100;
}
$thisfile_mpc_header['album_peak'] = $thisfile_mpc_header['raw']['album_peak'];
$thisfile_mpc_header['album_peak_db'] = $this->MPCpeakDBLookup($thisfile_mpc_header['album_peak']);
if ($thisfile_mpc_header['raw']['album_gain'] < 0) {
$thisfile_mpc_header['album_gain_db'] = (float) (32768 + $thisfile_mpc_header['raw']['album_gain']) / -100;
} else {
$thisfile_mpc_header['album_gain_db'] = (float) $thisfile_mpc_header['raw']['album_gain'] / 100;;
}
$thisfile_mpc_header['encoder_version'] = $this->MPCencoderVersionLookup($thisfile_mpc_header['raw']['encoder_version']);
$info['replay_gain']['track']['adjustment'] = $thisfile_mpc_header['title_gain_db'];
$info['replay_gain']['album']['adjustment'] = $thisfile_mpc_header['album_gain_db'];
if ($thisfile_mpc_header['title_peak'] > 0) {
$info['replay_gain']['track']['peak'] = $thisfile_mpc_header['title_peak'];
} elseif (round($thisfile_mpc_header['max_level'] * 1.18) > 0) {
$info['replay_gain']['track']['peak'] = getid3_lib::CastAsInt(round($thisfile_mpc_header['max_level'] * 1.18)); // why? I don't know - see mppdec.c
}
if ($thisfile_mpc_header['album_peak'] > 0) {
$info['replay_gain']['album']['peak'] = $thisfile_mpc_header['album_peak'];
}
//$info['audio']['encoder'] = 'SV'.$thisfile_mpc_header['stream_version_major'].'.'.$thisfile_mpc_header['stream_version_minor'].', '.$thisfile_mpc_header['encoder_version'];
$info['audio']['encoder'] = $thisfile_mpc_header['encoder_version'];
$info['audio']['encoder_options'] = $thisfile_mpc_header['profile'];
$thisfile_mpc_header['quality'] = (float) ($thisfile_mpc_header['raw']['profile'] - 5); // values can range from 0 to 15, of which 0..4 are "reserved/experimental", and 5..15 are mapped to qualities of 0.0 to 10.0
return true;
}
/**
* @return bool
*/
public function ParseMPCsv6() {
// this is SV4 - SV6
$info = &$this->getid3->info;
$thisfile_mpc_header = &$info['mpc']['header'];
$offset = 0;
$thisfile_mpc_header['size'] = 8;
$this->fseek($info['avdataoffset']);
$MPCheaderData = $this->fread($thisfile_mpc_header['size']);
// add size of file header to avdataoffset - calc bitrate correctly + MD5 data
$info['avdataoffset'] += $thisfile_mpc_header['size'];
// Most of this code adapted from Jurgen Faul's MPEGplus source code - thanks Jurgen! :)
$HeaderDWORD[0] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, 0, 4));
$HeaderDWORD[1] = getid3_lib::LittleEndian2Int(substr($MPCheaderData, 4, 4));
// DDDD DDDD CCCC CCCC BBBB BBBB AAAA AAAA
// aaaa aaaa abcd dddd dddd deee eeff ffff
//
// a = bitrate = anything
// b = IS = anything
// c = MS = anything
// d = streamversion = 0000000004 or 0000000005 or 0000000006
// e = maxband = anything
// f = blocksize = 000001 for SV5+, anything(?) for SV4
$thisfile_mpc_header['target_bitrate'] = (($HeaderDWORD[0] & 0xFF800000) >> 23);
$thisfile_mpc_header['intensity_stereo'] = (bool) (($HeaderDWORD[0] & 0x00400000) >> 22);
$thisfile_mpc_header['mid_side_stereo'] = (bool) (($HeaderDWORD[0] & 0x00200000) >> 21);
$thisfile_mpc_header['stream_version_major'] = ($HeaderDWORD[0] & 0x001FF800) >> 11;
$thisfile_mpc_header['stream_version_minor'] = 0; // no sub-version numbers before SV7
$thisfile_mpc_header['max_band'] = ($HeaderDWORD[0] & 0x000007C0) >> 6; // related to lowpass frequency, not sure how it translates exactly
$thisfile_mpc_header['block_size'] = ($HeaderDWORD[0] & 0x0000003F);
switch ($thisfile_mpc_header['stream_version_major']) {
case 4:
$thisfile_mpc_header['frame_count'] = ($HeaderDWORD[1] >> 16);
break;
case 5:
case 6:
$thisfile_mpc_header['frame_count'] = $HeaderDWORD[1];
break;
default:
$info['error'] = 'Expecting 4, 5 or 6 in version field, found '.$thisfile_mpc_header['stream_version_major'].' instead';
unset($info['mpc']);
return false;
}
if (($thisfile_mpc_header['stream_version_major'] > 4) && ($thisfile_mpc_header['block_size'] != 1)) {
$this->warning('Block size expected to be 1, actual value found: '.$thisfile_mpc_header['block_size']);
}
$thisfile_mpc_header['sample_rate'] = 44100; // AB: used by all files up to SV7
$info['audio']['sample_rate'] = $thisfile_mpc_header['sample_rate'];
$thisfile_mpc_header['samples'] = $thisfile_mpc_header['frame_count'] * 1152 * $info['audio']['channels'];
if ($thisfile_mpc_header['target_bitrate'] == 0) {
$info['audio']['bitrate_mode'] = 'vbr';
} else {
$info['audio']['bitrate_mode'] = 'cbr';
}
$info['mpc']['bitrate'] = ($info['avdataend'] - $info['avdataoffset']) * 8 * 44100 / $thisfile_mpc_header['frame_count'] / 1152;
$info['audio']['bitrate'] = $info['mpc']['bitrate'];
$info['audio']['encoder'] = 'SV'.$thisfile_mpc_header['stream_version_major'];
return true;
}
/**
* @param int $profileid
*
* @return string
*/
public function MPCprofileNameLookup($profileid) {
static $MPCprofileNameLookup = array(
0 => 'no profile',
1 => 'Experimental',
2 => 'unused',
3 => 'unused',
4 => 'unused',
5 => 'below Telephone (q = 0.0)',
6 => 'below Telephone (q = 1.0)',
7 => 'Telephone (q = 2.0)',
8 => 'Thumb (q = 3.0)',
9 => 'Radio (q = 4.0)',
10 => 'Standard (q = 5.0)',
11 => 'Extreme (q = 6.0)',
12 => 'Insane (q = 7.0)',
13 => 'BrainDead (q = 8.0)',
14 => 'above BrainDead (q = 9.0)',
15 => 'above BrainDead (q = 10.0)'
);
return (isset($MPCprofileNameLookup[$profileid]) ? $MPCprofileNameLookup[$profileid] : 'invalid');
}
/**
* @param int $frequencyid
*
* @return int|string
*/
public function MPCfrequencyLookup($frequencyid) {
static $MPCfrequencyLookup = array(
0 => 44100,
1 => 48000,
2 => 37800,
3 => 32000
);
return (isset($MPCfrequencyLookup[$frequencyid]) ? $MPCfrequencyLookup[$frequencyid] : 'invalid');
}
/**
* @param int $intvalue
*
* @return float|false
*/
public function MPCpeakDBLookup($intvalue) {
if ($intvalue > 0) {
return ((log10($intvalue) / log10(2)) - 15) * 6;
}
return false;
}
/**
* @param int $encoderversion
*
* @return string
*/
public function MPCencoderVersionLookup($encoderversion) {
//Encoder version * 100 (106 = 1.06)
//EncoderVersion % 10 == 0 Release (1.0)
//EncoderVersion % 2 == 0 Beta (1.06)
//EncoderVersion % 2 == 1 Alpha (1.05a...z)
if ($encoderversion == 0) {
// very old version, not known exactly which
return 'Buschmann v1.7.0-v1.7.9 or Klemm v0.90-v1.05';
}
if (($encoderversion % 10) == 0) {
// release version
return number_format($encoderversion / 100, 2);
} elseif (($encoderversion % 2) == 0) {
// beta version
return number_format($encoderversion / 100, 2).' beta';
}
// alpha version
return number_format($encoderversion / 100, 2).' alpha';
}
/**
* @param string $data
* @param int $packetLength
* @param int $maxHandledPacketLength
*
* @return int|false
*/
public function SV8variableLengthInteger($data, &$packetLength, $maxHandledPacketLength=9) {
$packet_size = 0;
for ($packetLength = 1; $packetLength <= $maxHandledPacketLength; $packetLength++) {
// variable-length size field:
// bits, big-endian
// 0xxx xxxx - value 0 to 2^7-1
// 1xxx xxxx 0xxx xxxx - value 0 to 2^14-1
// 1xxx xxxx 1xxx xxxx 0xxx xxxx - value 0 to 2^21-1
// 1xxx xxxx 1xxx xxxx 1xxx xxxx 0xxx xxxx - value 0 to 2^28-1
// ...
$thisbyte = ord(substr($data, ($packetLength - 1), 1));
// look through bytes until find a byte with MSB==0
$packet_size = ($packet_size << 7);
$packet_size = ($packet_size | ($thisbyte & 0x7F));
if (($thisbyte & 0x80) === 0) {
break;
}
if ($packetLength >= $maxHandledPacketLength) {
return false;
}
}
return $packet_size;
}
/**
* @param string $packetKey
*
* @return string
*/
public function MPCsv8PacketName($packetKey) {
static $MPCsv8PacketName = array();
if (empty($MPCsv8PacketName)) {
$MPCsv8PacketName = array(
'AP' => 'Audio Packet',
'CT' => 'Chapter Tag',
'EI' => 'Encoder Info',
'RG' => 'Replay Gain',
'SE' => 'Stream End',
'SH' => 'Stream Header',
'SO' => 'Seek Table Offset',
'ST' => 'Seek Table',
);
}
return (isset($MPCsv8PacketName[$packetKey]) ? $MPCsv8PacketName[$packetKey] : $packetKey);
}
}
<?php
/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org> //
// available at https://github.com/JamesHeinrich/getID3 //
// or https://www.getid3.org //
// or http://getid3.sourceforge.net //
// see readme.txt for more details //
/////////////////////////////////////////////////////////////////
// //
// module.audio.ogg.php //
// module for analyzing Ogg Vorbis, OggFLAC and Speex files //
// dependencies: module.audio.flac.php //
// ///
/////////////////////////////////////////////////////////////////
if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
exit;
}
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.flac.php', __FILE__, true);
class getid3_ogg extends getid3_handler
{
/**
* @link http://xiph.org/vorbis/doc/Vorbis_I_spec.html
*
* @return bool
*/
public function Analyze() {
$info = &$this->getid3->info;
$info['fileformat'] = 'ogg';
// Warn about illegal tags - only vorbiscomments are allowed
if (isset($info['id3v2'])) {
$this->warning('Illegal ID3v2 tag present.');
}
if (isset($info['id3v1'])) {
$this->warning('Illegal ID3v1 tag present.');
}
if (isset($info['ape'])) {
$this->warning('Illegal APE tag present.');
}
// 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()) {
$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?)');
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);
} elseif (substr($filedata, 0, 8) == 'OpusHead') {
if ($this->ParseOpusPageHeader($filedata, $filedataoffset, $oggpageinfo) === false) {
return false;
}
} 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';
}
} 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'];
}
$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');
} 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'];
$info['ogg']['skeleton']['fishead']['presentationtime'] = $info['ogg']['skeleton']['fishead']['raw']['presentationtime_numerator'] / $info['ogg']['skeleton']['fishead']['raw']['presentationtime_denominator'];
$info['ogg']['skeleton']['fishead']['basetime'] = $info['ogg']['skeleton']['fishead']['raw']['basetime_numerator'] / $info['ogg']['skeleton']['fishead']['raw']['basetime_denominator'];
$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') {
$info['video']['dataformat'] = 'theora1';
$this->error('Ogg Theora (v1) not correctly handled in this version of getID3 ['.$this->getid3->version().']');
//break;
} elseif (substr($filedata, 1, 6) == 'vorbis') {
$this->ParseVorbisPageHeader($filedata, $filedataoffset, $oggpageinfo);
} else {
$this->error('unexpected');
//break;
}
//} while ($oggpageinfo['page_seqno'] == 0);
} while (($oggpageinfo['page_seqno'] == 0) && (substr($filedata, 0, 8) != "fisbone\x00"));
$this->fseek($oggpageinfo['page_start_offset']);
$this->error('Ogg Skeleton not correctly handled in this version of getID3 ['.$this->getid3->version().']');
//return false;
} 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'];
$info['playtime_seconds'] = $info['flac']['STREAMINFO']['samples_stream'] / $info['flac']['STREAMINFO']['sample_rate'];
}
} else {
$this->error('Expecting one of "vorbis", "Speex", "OpusHead", "vorbis", "fishhead", "theora", "fLaC" identifier strings, found "'.substr($filedata, 0, 8).'"');
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()) {
$this->error('Failed to parse FLAC headers');
return false;
}
unset($flac);
break;
case 'speex':
$this->fseek($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length'], SEEK_CUR);
$this->ParseVorbisComments();
break;
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') {
$this->error('Expected "OpusTags" as header but got "'.substr($filedata, 0, 8).'"');
return false;
}
$this->ParseVorbisComments();
break;
}
// Last Page - Number of Samples
if (!getid3_lib::intValueSupported($info['avdataend'])) {
$this->warning('Unable to parse Ogg end chunk file (PHP does not support file operations beyond '.round(PHP_INT_MAX / 1073741824).'GB)');
} 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) {
$this->error('Corrupt Ogg file: eos.number of samples == zero');
return false;
}
if (!empty($info['audio']['sample_rate'])) {
$info['ogg']['bitrate_average'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / ($info['ogg']['samples'] / $info['audio']['sample_rate']);
}
}
}
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) {
$this->error('Corrupt Ogg file: bitrate_audio == zero');
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;
}
/**
* @param string $filedata
* @param int $filedataoffset
* @param array $oggpageinfo
*
* @return bool
*/
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) {
$this->error('Corrupt Ogg file: sample rate == zero');
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;
}
/**
* @link http://tools.ietf.org/html/draft-ietf-codec-oggopus-03
*
* @param string $filedata
* @param int $filedataoffset
* @param array $oggpageinfo
*
* @return bool
*/
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) {
$this->error('Unknown opus version number (only accepting 1-15)');
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) {
$this->error('Invalid channel count in opus header (must not be zero)');
return false;
}
$info['ogg']['pageheader']['opus']['pre_skip'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 2));
$filedataoffset += 2;
$info['ogg']['pageheader']['opus']['input_sample_rate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4));
$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;
$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'];
$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
return true;
}
/**
* @return array|false
*/
public function ParseOggPageHeader() {
// http://xiph.org/ogg/vorbis/doc/framing.html
$oggheader['page_start_offset'] = $this->ftell(); // where we started from in the file
$filedata = $this->fread($this->getid3->fread_buffer_size());
$filedataoffset = 0;
while ((substr($filedata, $filedataoffset++, 4) != 'OggS')) {
if (($this->ftell() - $oggheader['page_start_offset']) >= $this->getid3->fread_buffer_size()) {
// should be found before here
return false;
}
if ((($filedataoffset + 28) > strlen($filedata)) || (strlen($filedata) < 28)) {
if ($this->feof() || (($filedata .= $this->fread($this->getid3->fread_buffer_size())) === '')) {
// 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;
}
/**
* @link http://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-810005
*
* @return bool
*/
public function ParseVorbisComments() {
$info = &$this->getid3->info;
$OriginalOffset = $this->ftell();
$commentdata = null;
$commentdataoffset = 0;
$VorbisCommentPage = 1;
$CommentStartOffset = 0;
switch ($info['audio']['dataformat']) {
case 'vorbis':
case 'speex':
case 'opus':
$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);
}
else if ($info['audio']['dataformat'] == 'opus') {
$commentdataoffset += strlen('OpusTags');
}
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++) {
if ($i >= 10000) {
// https://github.com/owncloud/music/issues/212#issuecomment-43082336
$this->warning('Unexpectedly large number ('.$CommentsCount.') of Ogg comments - breaking after reading '.$i.' comments');
break;
}
$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)) {
$this->warning('Invalid Ogg comment size (comment #'.$i.', claims to be '.number_format($ThisFileInfo_ogg_comments_raw[$i]['size']).' bytes) - aborting reading comments');
break 2;
}
$VorbisCommentPage++;
$oggpageinfo = $this->ParseOggPageHeader();
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo;
// 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']);
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);
//$filebaseoffset += $oggpageinfo['header_end_offset'] - $oggpageinfo['page_start_offset'];
}
$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?
$this->warning('Blank Ogg comment ['.$i.']');
} 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(
'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']),
);
unset($ogg);
} else {
$info['ogg']['comments'][strtolower($ThisFileInfo_ogg_comments_raw[$i]['key'])][] = $ThisFileInfo_ogg_comments_raw[$i]['value'];
}
} else {
$this->warning('[known problem with CDex >= v1.40, < v1.50b7] Invalid Ogg comment name/value pair ['.$i.']: '.$commentstring);
}
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;
}
/**
* @param int $mode
*
* @return string|null
*/
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);
}
/**
* @param array $OggInfoArray
* @param int $SegmentNumber
*
* @return int
*/
public static function OggPageSegmentLength($OggInfoArray, $SegmentNumber=1) {
$segmentlength = 0;
for ($i = 0; $i < $SegmentNumber; $i++) {
$segmentlength = 0;
foreach ($OggInfoArray['segment_table'] as $key => $value) {
$segmentlength += $value;
if ($value < 255) {
break;
}
}
}
return $segmentlength;
}
/**
* @param int $nominal_bitrate
*
* @return float
*/
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
}
/**
* @param int $colorspace_id
*
* @return string|null
*/
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);
}
/**
* @param int $pixelformat_id
*
* @return string|null
*/
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);
}
}
<?php
/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org> //
// available at https://github.com/JamesHeinrich/getID3 //
// or https://www.getid3.org //
// or http://getid3.sourceforge.net //
// see readme.txt for more details //
/////////////////////////////////////////////////////////////////
// //
// module.audio.optimfrog.php //
// module for analyzing OptimFROG audio files //
// dependencies: module.audio.riff.php //
// ///
/////////////////////////////////////////////////////////////////
if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
exit;
}
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, true);
class getid3_optimfrog extends getid3_handler
{
/**
* @return bool
*/
public function Analyze() {
$info = &$this->getid3->info;
$info['fileformat'] = 'ofr';
$info['audio']['dataformat'] = 'ofr';
$info['audio']['bitrate_mode'] = 'vbr';
$info['audio']['lossless'] = true;
$this->fseek($info['avdataoffset']);
$OFRheader = $this->fread(8);
if (substr($OFRheader, 0, 5) == '*RIFF') {
return $this->ParseOptimFROGheader42();
} elseif (substr($OFRheader, 0, 3) == 'OFR') {
return $this->ParseOptimFROGheader45();
}
$this->error('Expecting "*RIFF" or "OFR " at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($OFRheader).'"');
unset($info['fileformat']);
return false;
}
/**
* @return bool
*/
public function ParseOptimFROGheader42() {
// for fileformat of v4.21 and older
$info = &$this->getid3->info;
$this->fseek($info['avdataoffset']);
$OptimFROGheaderData = $this->fread(45);
$info['avdataoffset'] = 45;
$OptimFROGencoderVersion_raw = getid3_lib::LittleEndian2Int(substr($OptimFROGheaderData, 0, 1));
$OptimFROGencoderVersion_major = floor($OptimFROGencoderVersion_raw / 10);
$OptimFROGencoderVersion_minor = $OptimFROGencoderVersion_raw - ($OptimFROGencoderVersion_major * 10);
$RIFFdata = substr($OptimFROGheaderData, 1, 44);
$OrignalRIFFheaderSize = getid3_lib::LittleEndian2Int(substr($RIFFdata, 4, 4)) + 8;
$OrignalRIFFdataSize = getid3_lib::LittleEndian2Int(substr($RIFFdata, 40, 4)) + 44;
if ($OrignalRIFFheaderSize > $OrignalRIFFdataSize) {
$info['avdataend'] -= ($OrignalRIFFheaderSize - $OrignalRIFFdataSize);
$this->fseek($info['avdataend']);
$RIFFdata .= $this->fread($OrignalRIFFheaderSize - $OrignalRIFFdataSize);
}
// move the data chunk after all other chunks (if any)
// so that the RIFF parser doesn't see EOF when trying
// to skip over the data chunk
$RIFFdata = substr($RIFFdata, 0, 36).substr($RIFFdata, 44).substr($RIFFdata, 36, 8);
$getid3_temp = new getID3();
$getid3_temp->openfile($this->getid3->filename, $this->getid3->info['filesize'], $this->getid3->fp);
$getid3_temp->info['avdataoffset'] = $info['avdataoffset'];
$getid3_temp->info['avdataend'] = $info['avdataend'];
$getid3_riff = new getid3_riff($getid3_temp);
$getid3_riff->ParseRIFFdata($RIFFdata);
$info['riff'] = $getid3_temp->info['riff'];
$info['audio']['encoder'] = 'OptimFROG '.$OptimFROGencoderVersion_major.'.'.$OptimFROGencoderVersion_minor;
$info['audio']['channels'] = $info['riff']['audio'][0]['channels'];
$info['audio']['sample_rate'] = $info['riff']['audio'][0]['sample_rate'];
$info['audio']['bits_per_sample'] = $info['riff']['audio'][0]['bits_per_sample'];
$info['playtime_seconds'] = $OrignalRIFFdataSize / ($info['audio']['channels'] * $info['audio']['sample_rate'] * ($info['audio']['bits_per_sample'] / 8));
$info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
unset($getid3_riff, $getid3_temp, $RIFFdata);
return true;
}
/**
* @return bool
*/
public function ParseOptimFROGheader45() {
// for fileformat of v4.50a and higher
$info = &$this->getid3->info;
$RIFFdata = '';
$this->fseek($info['avdataoffset']);
while (!feof($this->getid3->fp) && ($this->ftell() < $info['avdataend'])) {
$BlockOffset = $this->ftell();
$BlockData = $this->fread(8);
$offset = 8;
$BlockName = substr($BlockData, 0, 4);
$BlockSize = getid3_lib::LittleEndian2Int(substr($BlockData, 4, 4));
if ($BlockName == 'OFRX') {
$BlockName = 'OFR ';
}
if (!isset($info['ofr'][$BlockName])) {
$info['ofr'][$BlockName] = array();
}
$thisfile_ofr_thisblock = &$info['ofr'][$BlockName];
switch ($BlockName) {
case 'OFR ':
// shortcut
$thisfile_ofr_thisblock['offset'] = $BlockOffset;
$thisfile_ofr_thisblock['size'] = $BlockSize;
$info['audio']['encoder'] = 'OptimFROG 4.50 alpha';
switch ($BlockSize) {
case 12:
case 15:
// good
break;
default:
$this->warning('"'.$BlockName.'" contains more data than expected (expected 12 or 15 bytes, found '.$BlockSize.' bytes)');
break;
}
$BlockData .= $this->fread($BlockSize);
$thisfile_ofr_thisblock['total_samples'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 6));
$offset += 6;
$thisfile_ofr_thisblock['raw']['sample_type'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 1));
$thisfile_ofr_thisblock['sample_type'] = $this->OptimFROGsampleTypeLookup($thisfile_ofr_thisblock['raw']['sample_type']);
$offset += 1;
$thisfile_ofr_thisblock['channel_config'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 1));
$thisfile_ofr_thisblock['channels'] = $thisfile_ofr_thisblock['channel_config'];
$offset += 1;
$thisfile_ofr_thisblock['sample_rate'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 4));
$offset += 4;
if ($BlockSize > 12) {
// OFR 4.504b or higher
$thisfile_ofr_thisblock['channels'] = $this->OptimFROGchannelConfigNumChannelsLookup($thisfile_ofr_thisblock['channel_config']);
$thisfile_ofr_thisblock['raw']['encoder_id'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 2));
$thisfile_ofr_thisblock['encoder'] = $this->OptimFROGencoderNameLookup($thisfile_ofr_thisblock['raw']['encoder_id']);
$offset += 2;
$thisfile_ofr_thisblock['raw']['compression'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 1));
$thisfile_ofr_thisblock['compression'] = $this->OptimFROGcompressionLookup($thisfile_ofr_thisblock['raw']['compression']);
$thisfile_ofr_thisblock['speedup'] = $this->OptimFROGspeedupLookup($thisfile_ofr_thisblock['raw']['compression']);
$offset += 1;
$info['audio']['encoder'] = 'OptimFROG '.$thisfile_ofr_thisblock['encoder'];
$info['audio']['encoder_options'] = '--mode '.$thisfile_ofr_thisblock['compression'];
if ((($thisfile_ofr_thisblock['raw']['encoder_id'] & 0xF0) >> 4) == 7) { // v4.507
if (strtolower(getid3_lib::fileextension($info['filename'])) == 'ofs') {
// OptimFROG DualStream format is lossy, but as of v4.507 there is no way to tell the difference
// between lossless and lossy other than the file extension.
$info['audio']['dataformat'] = 'ofs';
$info['audio']['lossless'] = true;
}
}
}
$info['audio']['channels'] = $thisfile_ofr_thisblock['channels'];
$info['audio']['sample_rate'] = $thisfile_ofr_thisblock['sample_rate'];
$info['audio']['bits_per_sample'] = $this->OptimFROGbitsPerSampleTypeLookup($thisfile_ofr_thisblock['raw']['sample_type']);
break;
case 'COMP':
// unlike other block types, there CAN be multiple COMP blocks
$COMPdata['offset'] = $BlockOffset;
$COMPdata['size'] = $BlockSize;
if ($info['avdataoffset'] == 0) {
$info['avdataoffset'] = $BlockOffset;
}
// Only interested in first 14 bytes (only first 12 needed for v4.50 alpha), not actual audio data
$BlockData .= $this->fread(14);
$this->fseek($BlockSize - 14, SEEK_CUR);
$COMPdata['crc_32'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 4));
$offset += 4;
$COMPdata['sample_count'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 4));
$offset += 4;
$COMPdata['raw']['sample_type'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 1));
$COMPdata['sample_type'] = $this->OptimFROGsampleTypeLookup($COMPdata['raw']['sample_type']);
$offset += 1;
$COMPdata['raw']['channel_configuration'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 1));
$COMPdata['channel_configuration'] = $this->OptimFROGchannelConfigurationLookup($COMPdata['raw']['channel_configuration']);
$offset += 1;
$COMPdata['raw']['algorithm_id'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 2));
//$COMPdata['algorithm'] = OptimFROGalgorithmNameLookup($COMPdata['raw']['algorithm_id']);
$offset += 2;
if ($info['ofr']['OFR ']['size'] > 12) {
// OFR 4.504b or higher
$COMPdata['raw']['encoder_id'] = getid3_lib::LittleEndian2Int(substr($BlockData, $offset, 2));
$COMPdata['encoder'] = $this->OptimFROGencoderNameLookup($COMPdata['raw']['encoder_id']);
$offset += 2;
}
if ($COMPdata['crc_32'] == 0x454E4F4E) {
// ASCII value of 'NONE' - placeholder value in v4.50a
$COMPdata['crc_32'] = false;
}
$thisfile_ofr_thisblock[] = $COMPdata;
break;
case 'HEAD':
$thisfile_ofr_thisblock['offset'] = $BlockOffset;
$thisfile_ofr_thisblock['size'] = $BlockSize;
$RIFFdata .= $this->fread($BlockSize);
break;
case 'TAIL':
$thisfile_ofr_thisblock['offset'] = $BlockOffset;
$thisfile_ofr_thisblock['size'] = $BlockSize;
if ($BlockSize > 0) {
$RIFFdata .= $this->fread($BlockSize);
}
break;
case 'RECV':
// block contains no useful meta data - simply note and skip
$thisfile_ofr_thisblock['offset'] = $BlockOffset;
$thisfile_ofr_thisblock['size'] = $BlockSize;
$this->fseek($BlockSize, SEEK_CUR);
break;
case 'APET':
// APEtag v2
$thisfile_ofr_thisblock['offset'] = $BlockOffset;
$thisfile_ofr_thisblock['size'] = $BlockSize;
$this->warning('APEtag processing inside OptimFROG not supported in this version ('.$this->getid3->version().') of getID3()');
$this->fseek($BlockSize, SEEK_CUR);
break;
case 'MD5 ':
// APEtag v2
$thisfile_ofr_thisblock['offset'] = $BlockOffset;
$thisfile_ofr_thisblock['size'] = $BlockSize;
if ($BlockSize == 16) {
$thisfile_ofr_thisblock['md5_binary'] = $this->fread($BlockSize);
$thisfile_ofr_thisblock['md5_string'] = getid3_lib::PrintHexBytes($thisfile_ofr_thisblock['md5_binary'], true, false, false);
$info['md5_data_source'] = $thisfile_ofr_thisblock['md5_string'];
} else {
$this->warning('Expecting block size of 16 in "MD5 " chunk, found '.$BlockSize.' instead');
$this->fseek($BlockSize, SEEK_CUR);
}
break;
default:
$thisfile_ofr_thisblock['offset'] = $BlockOffset;
$thisfile_ofr_thisblock['size'] = $BlockSize;
$this->warning('Unhandled OptimFROG block type "'.$BlockName.'" at offset '.$thisfile_ofr_thisblock['offset']);
$this->fseek($BlockSize, SEEK_CUR);
break;
}
}
if (isset($info['ofr']['TAIL']['offset'])) {
$info['avdataend'] = $info['ofr']['TAIL']['offset'];
}
$info['playtime_seconds'] = (float) $info['ofr']['OFR ']['total_samples'] / ($info['audio']['channels'] * $info['audio']['sample_rate']);
$info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
// move the data chunk after all other chunks (if any)
// so that the RIFF parser doesn't see EOF when trying
// to skip over the data chunk
$RIFFdata = substr($RIFFdata, 0, 36).substr($RIFFdata, 44).substr($RIFFdata, 36, 8);
$getid3_temp = new getID3();
$getid3_temp->openfile($this->getid3->filename, $this->getid3->info['filesize'], $this->getid3->fp);
$getid3_temp->info['avdataoffset'] = $info['avdataoffset'];
$getid3_temp->info['avdataend'] = $info['avdataend'];
$getid3_riff = new getid3_riff($getid3_temp);
$getid3_riff->ParseRIFFdata($RIFFdata);
$info['riff'] = $getid3_temp->info['riff'];
unset($getid3_riff, $getid3_temp, $RIFFdata);
return true;
}
/**
* @param int $SampleType
*
* @return string|false
*/
public static function OptimFROGsampleTypeLookup($SampleType) {
static $OptimFROGsampleTypeLookup = array(
0 => 'unsigned int (8-bit)',
1 => 'signed int (8-bit)',
2 => 'unsigned int (16-bit)',
3 => 'signed int (16-bit)',
4 => 'unsigned int (24-bit)',
5 => 'signed int (24-bit)',
6 => 'unsigned int (32-bit)',
7 => 'signed int (32-bit)',
8 => 'float 0.24 (32-bit)',
9 => 'float 16.8 (32-bit)',
10 => 'float 24.0 (32-bit)'
);
return (isset($OptimFROGsampleTypeLookup[$SampleType]) ? $OptimFROGsampleTypeLookup[$SampleType] : false);
}
/**
* @param int $SampleType
*
* @return int|false
*/
public static function OptimFROGbitsPerSampleTypeLookup($SampleType) {
static $OptimFROGbitsPerSampleTypeLookup = array(
0 => 8,
1 => 8,
2 => 16,
3 => 16,
4 => 24,
5 => 24,
6 => 32,
7 => 32,
8 => 32,
9 => 32,
10 => 32
);
return (isset($OptimFROGbitsPerSampleTypeLookup[$SampleType]) ? $OptimFROGbitsPerSampleTypeLookup[$SampleType] : false);
}
/**
* @param int $ChannelConfiguration
*
* @return string|false
*/
public static function OptimFROGchannelConfigurationLookup($ChannelConfiguration) {
static $OptimFROGchannelConfigurationLookup = array(
0 => 'mono',
1 => 'stereo'
);
return (isset($OptimFROGchannelConfigurationLookup[$ChannelConfiguration]) ? $OptimFROGchannelConfigurationLookup[$ChannelConfiguration] : false);
}
/**
* @param int $ChannelConfiguration
*
* @return int|false
*/
public static function OptimFROGchannelConfigNumChannelsLookup($ChannelConfiguration) {
static $OptimFROGchannelConfigNumChannelsLookup = array(
0 => 1,
1 => 2
);
return (isset($OptimFROGchannelConfigNumChannelsLookup[$ChannelConfiguration]) ? $OptimFROGchannelConfigNumChannelsLookup[$ChannelConfiguration] : false);
}
// static function OptimFROGalgorithmNameLookup($AlgorithID) {
// static $OptimFROGalgorithmNameLookup = array();
// return (isset($OptimFROGalgorithmNameLookup[$AlgorithID]) ? $OptimFROGalgorithmNameLookup[$AlgorithID] : false);
// }
/**
* @param int $EncoderID
*
* @return string
*/
public static function OptimFROGencoderNameLookup($EncoderID) {
// version = (encoderID >> 4) + 4500
// system = encoderID & 0xF
$EncoderVersion = number_format(((($EncoderID & 0xF0) >> 4) + 4500) / 1000, 3);
$EncoderSystemID = ($EncoderID & 0x0F);
static $OptimFROGencoderSystemLookup = array(
0x00 => 'Windows console',
0x01 => 'Linux console',
0x0F => 'unknown'
);
return $EncoderVersion.' ('.(isset($OptimFROGencoderSystemLookup[$EncoderSystemID]) ? $OptimFROGencoderSystemLookup[$EncoderSystemID] : 'undefined encoder type (0x'.dechex($EncoderSystemID).')').')';
}
/**
* @param int $CompressionID
*
* @return string
*/
public static function OptimFROGcompressionLookup($CompressionID) {
// mode = compression >> 3
// speedup = compression & 0x07
$CompressionModeID = ($CompressionID & 0xF8) >> 3;
//$CompressionSpeedupID = ($CompressionID & 0x07);
static $OptimFROGencoderModeLookup = array(
0x00 => 'fast',
0x01 => 'normal',
0x02 => 'high',
0x03 => 'extra', // extranew (some versions)
0x04 => 'best', // bestnew (some versions)
0x05 => 'ultra',
0x06 => 'insane',
0x07 => 'highnew',
0x08 => 'extranew',
0x09 => 'bestnew'
);
return (isset($OptimFROGencoderModeLookup[$CompressionModeID]) ? $OptimFROGencoderModeLookup[$CompressionModeID] : 'undefined mode (0x'.str_pad(dechex($CompressionModeID), 2, '0', STR_PAD_LEFT).')');
}
/**
* @param int $CompressionID
*
* @return string
*/
public static function OptimFROGspeedupLookup($CompressionID) {
// mode = compression >> 3
// speedup = compression & 0x07
//$CompressionModeID = ($CompressionID & 0xF8) >> 3;
$CompressionSpeedupID = ($CompressionID & 0x07);
static $OptimFROGencoderSpeedupLookup = array(
0x00 => '1x',
0x01 => '2x',
0x02 => '4x'
);
return (isset($OptimFROGencoderSpeedupLookup[$CompressionSpeedupID]) ? $OptimFROGencoderSpeedupLookup[$CompressionSpeedupID] : 'undefined mode (0x'.dechex($CompressionSpeedupID));
}
}
<?php
/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org> //
// available at https://github.com/JamesHeinrich/getID3 //
// or https://www.getid3.org //
// or http://getid3.sourceforge.net //
// see readme.txt for more details //
/////////////////////////////////////////////////////////////////
// //
// module.audio.shorten.php //
// module for analyzing Shorten Audio files //
// dependencies: NONE //
// ///
/////////////////////////////////////////////////////////////////
if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
exit;
}
class getid3_rkau extends getid3_handler
{
/**
* @return bool
*/
public function Analyze() {
$info = &$this->getid3->info;
$this->fseek($info['avdataoffset']);
$RKAUHeader = $this->fread(20);
$magic = 'RKA';
if (substr($RKAUHeader, 0, 3) != $magic) {
$this->error('Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($RKAUHeader, 0, 3)).'"');
return false;
}
$info['fileformat'] = 'rkau';
$info['audio']['dataformat'] = 'rkau';
$info['audio']['bitrate_mode'] = 'vbr';
$info['rkau']['raw']['version'] = getid3_lib::LittleEndian2Int(substr($RKAUHeader, 3, 1));
$info['rkau']['version'] = '1.'.str_pad($info['rkau']['raw']['version'] & 0x0F, 2, '0', STR_PAD_LEFT);
if (($info['rkau']['version'] > 1.07) || ($info['rkau']['version'] < 1.06)) {
$this->error('This version of getID3() ['.$this->getid3->version().'] can only parse RKAU files v1.06 and 1.07 (this file is v'.$info['rkau']['version'].')');
unset($info['rkau']);
return false;
}
$info['rkau']['source_bytes'] = getid3_lib::LittleEndian2Int(substr($RKAUHeader, 4, 4));
$info['rkau']['sample_rate'] = getid3_lib::LittleEndian2Int(substr($RKAUHeader, 8, 4));
$info['rkau']['channels'] = getid3_lib::LittleEndian2Int(substr($RKAUHeader, 12, 1));
$info['rkau']['bits_per_sample'] = getid3_lib::LittleEndian2Int(substr($RKAUHeader, 13, 1));
$info['rkau']['raw']['quality'] = getid3_lib::LittleEndian2Int(substr($RKAUHeader, 14, 1));
$this->RKAUqualityLookup($info['rkau']);
$info['rkau']['raw']['flags'] = getid3_lib::LittleEndian2Int(substr($RKAUHeader, 15, 1));
$info['rkau']['flags']['joint_stereo'] = !($info['rkau']['raw']['flags'] & 0x01);
$info['rkau']['flags']['streaming'] = (bool) ($info['rkau']['raw']['flags'] & 0x02);
$info['rkau']['flags']['vrq_lossy_mode'] = (bool) ($info['rkau']['raw']['flags'] & 0x04);
if ($info['rkau']['flags']['streaming']) {
$info['avdataoffset'] += 20;
$info['rkau']['compressed_bytes'] = getid3_lib::LittleEndian2Int(substr($RKAUHeader, 16, 4));
} else {
$info['avdataoffset'] += 16;
$info['rkau']['compressed_bytes'] = $info['avdataend'] - $info['avdataoffset'] - 1;
}
// Note: compressed_bytes does not always equal what appears to be the actual number of compressed bytes,
// sometimes it's more, sometimes less. No idea why(?)
$info['audio']['lossless'] = $info['rkau']['lossless'];
$info['audio']['channels'] = $info['rkau']['channels'];
$info['audio']['bits_per_sample'] = $info['rkau']['bits_per_sample'];
$info['audio']['sample_rate'] = $info['rkau']['sample_rate'];
$info['playtime_seconds'] = $info['rkau']['source_bytes'] / ($info['rkau']['sample_rate'] * $info['rkau']['channels'] * ($info['rkau']['bits_per_sample'] / 8));
$info['audio']['bitrate'] = ($info['rkau']['compressed_bytes'] * 8) / $info['playtime_seconds'];
return true;
}
/**
* @param array $RKAUdata
*
* @return bool
*/
public function RKAUqualityLookup(&$RKAUdata) {
$level = ($RKAUdata['raw']['quality'] & 0xF0) >> 4;
$quality = $RKAUdata['raw']['quality'] & 0x0F;
$RKAUdata['lossless'] = (($quality == 0) ? true : false);
$RKAUdata['compression_level'] = $level + 1;
if (!$RKAUdata['lossless']) {
$RKAUdata['quality_setting'] = $quality;
}
return true;
}
}
<?php
/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org> //
// available at https://github.com/JamesHeinrich/getID3 //
// or https://www.getid3.org //
// or http://getid3.sourceforge.net //
// see readme.txt for more details //
/////////////////////////////////////////////////////////////////
// //
// module.audio.shorten.php //
// module for analyzing Shorten Audio files //
// dependencies: NONE //
// ///
/////////////////////////////////////////////////////////////////
if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
exit;
}
class getid3_shorten extends getid3_handler
{
/**
* @return bool
*/
public function Analyze() {
$info = &$this->getid3->info;
$this->fseek($info['avdataoffset']);
$ShortenHeader = $this->fread(8);
$magic = 'ajkg';
if (substr($ShortenHeader, 0, 4) != $magic) {
$this->error('Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($ShortenHeader, 0, 4)).'"');
return false;
}
$info['fileformat'] = 'shn';
$info['audio']['dataformat'] = 'shn';
$info['audio']['lossless'] = true;
$info['audio']['bitrate_mode'] = 'vbr';
$info['shn']['version'] = getid3_lib::LittleEndian2Int(substr($ShortenHeader, 4, 1));
$this->fseek($info['avdataend'] - 12);
$SeekTableSignatureTest = $this->fread(12);
$info['shn']['seektable']['present'] = substr($SeekTableSignatureTest, 4, 8) == 'SHNAMPSK';
if ($info['shn']['seektable']['present']) {
$info['shn']['seektable']['length'] = getid3_lib::LittleEndian2Int(substr($SeekTableSignatureTest, 0, 4));
$info['shn']['seektable']['offset'] = $info['avdataend'] - $info['shn']['seektable']['length'];
$this->fseek($info['shn']['seektable']['offset']);
$SeekTableMagic = $this->fread(4);
$magic = 'SEEK';
if ($SeekTableMagic != $magic) {
$this->error('Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['shn']['seektable']['offset'].', found "'.getid3_lib::PrintHexBytes($SeekTableMagic).'"');
return false;
} else {
// typedef struct tag_TSeekEntry
// {
// unsigned long SampleNumber;
// unsigned long SHNFileByteOffset;
// unsigned long SHNLastBufferReadPosition;
// unsigned short SHNByteGet;
// unsigned short SHNBufferOffset;
// unsigned short SHNFileBitOffset;
// unsigned long SHNGBuffer;
// unsigned short SHNBitShift;
// long CBuf0[3];
// long CBuf1[3];
// long Offset0[4];
// long Offset1[4];
// }TSeekEntry;
$SeekTableData = $this->fread($info['shn']['seektable']['length'] - 16);
$info['shn']['seektable']['entry_count'] = floor(strlen($SeekTableData) / 80);
//$info['shn']['seektable']['entries'] = array();
//$SeekTableOffset = 0;
//for ($i = 0; $i < $info['shn']['seektable']['entry_count']; $i++) {
// $SeekTableEntry['sample_number'] = getid3_lib::LittleEndian2Int(substr($SeekTableData, $SeekTableOffset, 4));
// $SeekTableOffset += 4;
// $SeekTableEntry['shn_file_byte_offset'] = getid3_lib::LittleEndian2Int(substr($SeekTableData, $SeekTableOffset, 4));
// $SeekTableOffset += 4;
// $SeekTableEntry['shn_last_buffer_read_position'] = getid3_lib::LittleEndian2Int(substr($SeekTableData, $SeekTableOffset, 4));
// $SeekTableOffset += 4;
// $SeekTableEntry['shn_byte_get'] = getid3_lib::LittleEndian2Int(substr($SeekTableData, $SeekTableOffset, 2));
// $SeekTableOffset += 2;
// $SeekTableEntry['shn_buffer_offset'] = getid3_lib::LittleEndian2Int(substr($SeekTableData, $SeekTableOffset, 2));
// $SeekTableOffset += 2;
// $SeekTableEntry['shn_file_bit_offset'] = getid3_lib::LittleEndian2Int(substr($SeekTableData, $SeekTableOffset, 2));
// $SeekTableOffset += 2;
// $SeekTableEntry['shn_gbuffer'] = getid3_lib::LittleEndian2Int(substr($SeekTableData, $SeekTableOffset, 4));
// $SeekTableOffset += 4;
// $SeekTableEntry['shn_bit_shift'] = getid3_lib::LittleEndian2Int(substr($SeekTableData, $SeekTableOffset, 2));
// $SeekTableOffset += 2;
// for ($j = 0; $j < 3; $j++) {
// $SeekTableEntry['cbuf0'][$j] = getid3_lib::LittleEndian2Int(substr($SeekTableData, $SeekTableOffset, 4));
// $SeekTableOffset += 4;
// }
// for ($j = 0; $j < 3; $j++) {
// $SeekTableEntry['cbuf1'][$j] = getid3_lib::LittleEndian2Int(substr($SeekTableData, $SeekTableOffset, 4));
// $SeekTableOffset += 4;
// }
// for ($j = 0; $j < 4; $j++) {
// $SeekTableEntry['offset0'][$j] = getid3_lib::LittleEndian2Int(substr($SeekTableData, $SeekTableOffset, 4));
// $SeekTableOffset += 4;
// }
// for ($j = 0; $j < 4; $j++) {
// $SeekTableEntry['offset1'][$j] = getid3_lib::LittleEndian2Int(substr($SeekTableData, $SeekTableOffset, 4));
// $SeekTableOffset += 4;
// }
//
// $info['shn']['seektable']['entries'][] = $SeekTableEntry;
//}
}
}
if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) {
$this->error('PHP running in Safe Mode - backtick operator not available, cannot run shntool to analyze Shorten files');
return false;
}
if (GETID3_OS_ISWINDOWS) {
$RequiredFiles = array('shorten.exe', 'cygwin1.dll', 'head.exe');
foreach ($RequiredFiles as $required_file) {
if (!is_readable(GETID3_HELPERAPPSDIR.$required_file)) {
$this->error(GETID3_HELPERAPPSDIR.$required_file.' does not exist');
return false;
}
}
$commandline = GETID3_HELPERAPPSDIR.'shorten.exe -x "'.$info['filenamepath'].'" - | '.GETID3_HELPERAPPSDIR.'head.exe -c 64';
$commandline = str_replace('/', '\\', $commandline);
} else {
static $shorten_present;
if (!isset($shorten_present)) {
$shorten_present = file_exists('/usr/local/bin/shorten') || `which shorten`;
}
if (!$shorten_present) {
$this->error('shorten binary was not found in path or /usr/local/bin');
return false;
}
$commandline = (file_exists('/usr/local/bin/shorten') ? '/usr/local/bin/' : '' ) . 'shorten -x '.escapeshellarg($info['filenamepath']).' - | head -c 64';
}
$output = `$commandline`;
if (!empty($output) && (substr($output, 12, 4) == 'fmt ')) {
if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
exit;
}
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, true);
$fmt_size = getid3_lib::LittleEndian2Int(substr($output, 16, 4));
$DecodedWAVFORMATEX = getid3_riff::parseWAVEFORMATex(substr($output, 20, $fmt_size));
$info['audio']['channels'] = $DecodedWAVFORMATEX['channels'];
$info['audio']['bits_per_sample'] = $DecodedWAVFORMATEX['bits_per_sample'];
$info['audio']['sample_rate'] = $DecodedWAVFORMATEX['sample_rate'];
if (substr($output, 20 + $fmt_size, 4) == 'data') {
$info['playtime_seconds'] = getid3_lib::LittleEndian2Int(substr($output, 20 + 4 + $fmt_size, 4)) / $DecodedWAVFORMATEX['raw']['nAvgBytesPerSec'];
} else {
$this->error('shorten failed to decode DATA chunk to expected location, cannot determine playtime');
return false;
}
$info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) / $info['playtime_seconds']) * 8;
} else {
$this->error('shorten failed to decode file to WAV for parsing');
return false;
}
return true;
}
}
<?php
/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org> //
// available at http://getid3.sourceforge.net //
// or https://www.getid3.org //
// also https://github.com/JamesHeinrich/getID3 //
/////////////////////////////////////////////////////////////////
// See readme.txt for more details //
/////////////////////////////////////////////////////////////////
// //
// module.audio.tak.php //
// module for analyzing Tom's lossless Audio Kompressor //
// dependencies: NONE //
// ///
/////////////////////////////////////////////////////////////////
if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
exit;
}
class getid3_tak extends getid3_handler
{
public function Analyze() {
$info = &$this->getid3->info;
$info['fileformat'] = 'tak';
$info['audio']['dataformat'] = 'tak';
$info['audio']['bitrate_mode'] = 'vbr';
$info['audio']['lossless'] = true;
$info['tak_audio']['raw'] = array();
$thisfile_takaudio = &$info['tak_audio'];
$thisfile_takaudio_raw = &$thisfile_takaudio['raw'];
$this->fseek($info['avdataoffset']);
$TAKMetaData = $this->fread(4);
$thisfile_takaudio_raw['magic'] = $TAKMetaData;
$magic = 'tBaK';
if ($thisfile_takaudio_raw['magic'] != $magic) {
$this->error('Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($thisfile_takaudio_raw['magic']).'"');
unset($info['fileformat']);
return false;
}
$offset = 4; //skip magic
$this->fseek($offset);
$TAKMetaData = $this->fread(4); //read Metadata Block Header
$objtype = getid3_lib::BigEndian2Int(substr($TAKMetaData, 0, 1)); //Metadata Block Object Type
$objlength = getid3_lib::LittleEndian2Int(substr($TAKMetaData, 1, 3)); //Metadata Block Object Lenght excluding header
if ($objtype == 1) { //The First Metadata Block Object must be of Type 1 (STREAMINFO)
$offset += 4; //skip to Metadata Block contents
$this->fseek($offset);
$TAKMetaData = $this->fread($objlength); // Get the raw Metadata Block Data
$thisfile_takaudio_raw['STREAMINFO'] = getid3_lib::LittleEndian2Bin(substr($TAKMetaData, 0, $objlength - 3));
$offset += $objlength; // Move to the next Metadata Block Object
$thisfile_takaudio['channels'] = getid3_lib::Bin2Dec(substr($thisfile_takaudio_raw['STREAMINFO'], 1, 4)) + 1;
$thisfile_takaudio['bits_per_sample'] = getid3_lib::Bin2Dec(substr($thisfile_takaudio_raw['STREAMINFO'], 5, 5)) + 8;
$thisfile_takaudio['sample_rate'] = getid3_lib::Bin2Dec(substr($thisfile_takaudio_raw['STREAMINFO'], 10, 18)) + 6000;
$thisfile_takaudio['samples'] = getid3_lib::Bin2Dec(substr($thisfile_takaudio_raw['STREAMINFO'], 31, 35));
$thisfile_takaudio['framesize'] = self::TAKFramesizeLookup(getid3_lib::Bin2Dec(substr($thisfile_takaudio_raw['STREAMINFO'], 66, 4)));
$thisfile_takaudio['codectype'] = self::TAKCodecTypeLookup(getid3_lib::Bin2Dec(substr($thisfile_takaudio_raw['STREAMINFO'], 74, 6)));
} else {
$this->error('Expecting Type 1 (STREAMINFO) Metadata Object header, but found Type "'.$objtype.'" Object instead');
unset($info['fileformat']);
return false;
}
$this->fseek($offset);
$TAKMetaData = $this->fread(4);
$objtype = getid3_lib::BigEndian2Int(substr($TAKMetaData, 0, 1));
$objlength = getid3_lib::LittleEndian2Int(substr($TAKMetaData, 1, 3));
while ($objtype != 0) {
switch ($objtype) {
case 4 :
// ENCODERINFO Metadata Block
$offset += 4;
$this->fseek($offset);
$TAKMetaData = $this->fread($objlength);
$ver = getid3_lib::LittleEndian2Int(substr($TAKMetaData, 0, 3));
$major = ($ver & 0xff0000) >> 16;
$minor = ($ver & 0x00ff00) >> 8;
$revision= $ver & 0x0000ff;
$thisfile_takaudio['version'] = 'TAK V '.$major.'.'.$minor.'.'.$revision;
$thisfile_takaudio['profile'] = self::TAKProfileLookup(getid3_lib::BigEndian2Int(substr($TAKMetaData, 3, 1)));
$offset += $objlength;
break;
case 6 :
// MD5 Checksum Metadata Block
$offset += 4;
$this->fseek($offset);
$TAKMetaData = $this->fread($objlength);
$thisfile_takaudio_raw['MD5Data'] = substr($TAKMetaData, 0, 16);
$offset += $objlength;
break;
case 7 :
// LASTFRAME Metadata Block
$offset += 4;
$this->fseek($offset);
$TAKMetaData = $this->fread($objlength);
$thisfile_takaudio['lastframe_pos'] = getid3_lib::LittleEndian2Int(substr($TAKMetaData, 0, 5));
$thisfile_takaudio['last_frame_size'] = getid3_lib::LittleEndian2Int(substr($TAKMetaData, 5, 3));
$offset += $objlength;
break;
case 3 :
// ORIGINALFILEDATA Metadata Block
$offset += 4;
$this->fseek($offset);
$TAKMetaData = $this->fread($objlength);
$headersize = getid3_lib::LittleEndian2Int(substr($TAKMetaData, 0, 3));
$footersize = getid3_lib::LittleEndian2Int(substr($TAKMetaData, 3, 3));
if ($headersize) $thisfile_takaudio_raw['header_data'] = substr($TAKMetaData, 6, $headersize);
if ($footersize) $thisfile_takaudio_raw['footer_data'] = substr($TAKMetaData, $headersize, $footersize);
$offset += $objlength;
break;
default :
// PADDING or SEEKTABLE Metadata Block. Just skip it
$offset += 4;
$this->fseek($offset);
$TAKMetaData = $this->fread($objlength);
$offset += $objlength;
break;
}
$this->fseek($offset);
$TAKMetaData = $this->fread(4);
$objtype = getid3_lib::BigEndian2Int(substr($TAKMetaData, 0, 1));
$objlength = getid3_lib::LittleEndian2Int(substr($TAKMetaData, 1, 3));
}
// Finished all Metadata Blocks. So update $info['avdataoffset'] because next block is the first Audio data block
$info['avdataoffset'] = $offset;
$info['audio']['channels'] = $thisfile_takaudio['channels'];
if ($thisfile_takaudio['sample_rate'] == 0) {
$this->error('Corrupt TAK file: samplerate == zero');
return false;
}
$info['audio']['sample_rate'] = $thisfile_takaudio['sample_rate'];
$thisfile_takaudio['playtime'] = $thisfile_takaudio['samples'] / $thisfile_takaudio['sample_rate'];
if ($thisfile_takaudio['playtime'] == 0) {
$this->error('Corrupt TAK file: playtime == zero');
return false;
}
$info['playtime_seconds'] = $thisfile_takaudio['playtime'];
$thisfile_takaudio['compressed_size'] = $info['avdataend'] - $info['avdataoffset'];
$thisfile_takaudio['uncompressed_size'] = $thisfile_takaudio['samples'] * $thisfile_takaudio['channels'] * ($thisfile_takaudio['bits_per_sample'] / 8);
if ($thisfile_takaudio['uncompressed_size'] == 0) {
$this->error('Corrupt TAK file: uncompressed_size == zero');
return false;
}
$thisfile_takaudio['compression_ratio'] = $thisfile_takaudio['compressed_size'] / ($thisfile_takaudio['uncompressed_size'] + $offset);
$thisfile_takaudio['bitrate'] = (($thisfile_takaudio['samples'] * $thisfile_takaudio['channels'] * $thisfile_takaudio['bits_per_sample']) / $thisfile_takaudio['playtime']) * $thisfile_takaudio['compression_ratio'];
$info['audio']['bitrate'] = $thisfile_takaudio['bitrate'];
if (empty($thisfile_takaudio_raw['MD5Data'])) {
//$this->warning('MD5Data is not set');
} elseif ($thisfile_takaudio_raw['MD5Data'] === str_repeat("\x00", 16)) {
//$this->warning('MD5Data is null');
} else {
$info['md5_data_source'] = '';
$md5 = $thisfile_takaudio_raw['MD5Data'];
for ($i = 0; $i < strlen($md5); $i++) {
$info['md5_data_source'] .= str_pad(dechex(ord($md5[$i])), 2, '00', STR_PAD_LEFT);
}
if (!preg_match('/^[0-9a-f]{32}$/', $info['md5_data_source'])) {
unset($info['md5_data_source']);
}
}
foreach (array('bits_per_sample', 'version', 'profile') as $key) {
if (!empty($thisfile_takaudio[$key])) {
$info['audio'][$key] = $thisfile_takaudio[$key];
}
}
return true;
}
public function TAKFramesizeLookup($framesize) {
static $TAKFramesizeLookup = array(
0 => '94 ms',
1 => '125 ms',
2 => '188 ms',
3 => '250 ms',
4 => '4096 samples',
5 => '8192 samples',
6 => '16384 samples',
7 => '512 samples',
8 => '1024 samples',
9 => '2048 samples'
);
return (isset($TAKFramesizeLookup[$framesize]) ? $TAKFramesizeLookup[$framesize] : 'invalid');
}
public function TAKCodecTypeLookup($code) {
static $TAKCodecTypeLookup = array(
0 => 'Integer 24 bit (TAK 1.0)',
1 => 'Experimental!',
2 => 'Integer 24 bit (TAK 2.0)',
3 => 'LossyWav (TAK 2.1)',
4 => 'Integer 24 bit MC (TAK 2.2)'
);
return (isset($TAKCodecTypeLookup[$code]) ? $TAKCodecTypeLookup[$code] : 'invalid');
}
public function TAKProfileLookup($code) {
$out ='-p';
$evaluation = ($code & 0xf0) >> 4;
$compresion = $code & 0x0f;
static $TAKEvaluationLookup = array(
0 => '',
1 => 'e',
2 => 'm'
);
return (isset($TAKEvaluationLookup[$evaluation]) ? $out .= $compresion . $TAKEvaluationLookup[$evaluation] : 'invalid');
}
}
<?php
/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org> //
// available at https://github.com/JamesHeinrich/getID3 //
// or https://www.getid3.org //
// or http://getid3.sourceforge.net //
// see readme.txt for more details //
/////////////////////////////////////////////////////////////////
// //
// module.audio.tta.php //
// module for analyzing TTA Audio files //
// dependencies: NONE //
// ///
/////////////////////////////////////////////////////////////////
if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
exit;
}
class getid3_tta extends getid3_handler
{
/**
* @return bool
*/
public function Analyze() {
$info = &$this->getid3->info;
$info['fileformat'] = 'tta';
$info['audio']['dataformat'] = 'tta';
$info['audio']['lossless'] = true;
$info['audio']['bitrate_mode'] = 'vbr';
$this->fseek($info['avdataoffset']);
$ttaheader = $this->fread(26);
$info['tta']['magic'] = substr($ttaheader, 0, 3);
$magic = 'TTA';
if ($info['tta']['magic'] != $magic) {
$this->error('Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($info['tta']['magic']).'"');
unset($info['fileformat']);
unset($info['audio']);
unset($info['tta']);
return false;
}
switch ($ttaheader[3]) {
case "\x01": // TTA v1.x
case "\x02": // TTA v1.x
case "\x03": // TTA v1.x
// "It was the demo-version of the TTA encoder. There is no released format with such header. TTA encoder v1 is not supported about a year."
$info['tta']['major_version'] = 1;
$info['avdataoffset'] += 16;
$info['tta']['compression_level'] = ord($ttaheader[3]);
$info['tta']['channels'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 4, 2));
$info['tta']['bits_per_sample'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 6, 2));
$info['tta']['sample_rate'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 8, 4));
$info['tta']['samples_per_channel'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 12, 4));
$info['audio']['encoder_options'] = '-e'.$info['tta']['compression_level'];
$info['playtime_seconds'] = $info['tta']['samples_per_channel'] / $info['tta']['sample_rate'];
break;
case '2': // TTA v2.x
// "I have hurried to release the TTA 2.0 encoder. Format documentation is removed from our site. This format still in development. Please wait the TTA2 format, encoder v4."
$info['tta']['major_version'] = 2;
$info['avdataoffset'] += 20;
$info['tta']['compression_level'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 4, 2));
$info['tta']['audio_format'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 6, 2));
$info['tta']['channels'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 8, 2));
$info['tta']['bits_per_sample'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 10, 2));
$info['tta']['sample_rate'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 12, 4));
$info['tta']['data_length'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 16, 4));
$info['audio']['encoder_options'] = '-e'.$info['tta']['compression_level'];
$info['playtime_seconds'] = $info['tta']['data_length'] / $info['tta']['sample_rate'];
break;
case '1': // TTA v3.x
// "This is a first stable release of the TTA format. It will be supported by the encoders v3 or higher."
$info['tta']['major_version'] = 3;
$info['avdataoffset'] += 26;
$info['tta']['audio_format'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 4, 2)); // getid3_riff::wFormatTagLookup()
$info['tta']['channels'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 6, 2));
$info['tta']['bits_per_sample'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 8, 2));
$info['tta']['sample_rate'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 10, 4));
$info['tta']['data_length'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 14, 4));
$info['tta']['crc32_footer'] = substr($ttaheader, 18, 4);
$info['tta']['seek_point'] = getid3_lib::LittleEndian2Int(substr($ttaheader, 22, 4));
$info['playtime_seconds'] = $info['tta']['data_length'] / $info['tta']['sample_rate'];
break;
default:
$this->error('This version of getID3() ['.$this->getid3->version().'] only knows how to handle TTA v1 and v2 - it may not work correctly with this file which appears to be TTA v'.$ttaheader[3]);
return false;
}
$info['audio']['encoder'] = 'TTA v'.$info['tta']['major_version'];
$info['audio']['bits_per_sample'] = $info['tta']['bits_per_sample'];
$info['audio']['sample_rate'] = $info['tta']['sample_rate'];
$info['audio']['channels'] = $info['tta']['channels'];
$info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
return true;
}
}
<?php
/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org> //
// available at https://github.com/JamesHeinrich/getID3 //
// or https://www.getid3.org //
// or http://getid3.sourceforge.net //
// see readme.txt for more details //
/////////////////////////////////////////////////////////////////
// //
// module.audio.voc.php //
// module for analyzing Creative VOC Audio files //
// dependencies: NONE //
// ///
/////////////////////////////////////////////////////////////////
if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
exit;
}
class getid3_voc extends getid3_handler
{
/**
* @return bool
*/
public function Analyze() {
$info = &$this->getid3->info;
$OriginalAVdataOffset = $info['avdataoffset'];
$this->fseek($info['avdataoffset']);
$VOCheader = $this->fread(26);
$magic = 'Creative Voice File';
if (substr($VOCheader, 0, 19) != $magic) {
$this->error('Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($VOCheader, 0, 19)).'"');
return false;
}
// shortcuts
$thisfile_audio = &$info['audio'];
$info['voc'] = array();
$thisfile_voc = &$info['voc'];
$info['fileformat'] = 'voc';
$thisfile_audio['dataformat'] = 'voc';
$thisfile_audio['bitrate_mode'] = 'cbr';
$thisfile_audio['lossless'] = true;
$thisfile_audio['channels'] = 1; // might be overriden below
$thisfile_audio['bits_per_sample'] = 8; // might be overriden below
// byte # Description
// ------ ------------------------------------------
// 00-12 'Creative Voice File'
// 13 1A (eof to abort printing of file)
// 14-15 Offset of first datablock in .voc file (std 1A 00 in Intel Notation)
// 16-17 Version number (minor,major) (VOC-HDR puts 0A 01)
// 18-19 2's Comp of Ver. # + 1234h (VOC-HDR puts 29 11)
$thisfile_voc['header']['datablock_offset'] = getid3_lib::LittleEndian2Int(substr($VOCheader, 20, 2));
$thisfile_voc['header']['minor_version'] = getid3_lib::LittleEndian2Int(substr($VOCheader, 22, 1));
$thisfile_voc['header']['major_version'] = getid3_lib::LittleEndian2Int(substr($VOCheader, 23, 1));
do {
$BlockOffset = $this->ftell();
$BlockData = $this->fread(4);
$BlockType = ord($BlockData[0]);
$BlockSize = getid3_lib::LittleEndian2Int(substr($BlockData, 1, 3));
$ThisBlock = array();
getid3_lib::safe_inc($thisfile_voc['blocktypes'][$BlockType], 1);
switch ($BlockType) {
case 0: // Terminator
// do nothing, we'll break out of the loop down below
break;
case 1: // Sound data
$BlockData .= $this->fread(2);
if ($info['avdataoffset'] <= $OriginalAVdataOffset) {
$info['avdataoffset'] = $this->ftell();
}
$this->fseek($BlockSize - 2, SEEK_CUR);
$ThisBlock['sample_rate_id'] = getid3_lib::LittleEndian2Int(substr($BlockData, 4, 1));
$ThisBlock['compression_type'] = getid3_lib::LittleEndian2Int(substr($BlockData, 5, 1));
$ThisBlock['compression_name'] = $this->VOCcompressionTypeLookup($ThisBlock['compression_type']);
if ($ThisBlock['compression_type'] <= 3) {
$thisfile_voc['compressed_bits_per_sample'] = getid3_lib::CastAsInt(str_replace('-bit', '', $ThisBlock['compression_name']));
}
// Less accurate sample_rate calculation than the Extended block (#8) data (but better than nothing if Extended Block is not available)
if (empty($thisfile_audio['sample_rate'])) {
// SR byte = 256 - (1000000 / sample_rate)
$thisfile_audio['sample_rate'] = getid3_lib::trunc((1000000 / (256 - $ThisBlock['sample_rate_id'])) / $thisfile_audio['channels']);
}
break;
case 2: // Sound continue
case 3: // Silence
case 4: // Marker
case 6: // Repeat
case 7: // End repeat
// nothing useful, just skip
$this->fseek($BlockSize, SEEK_CUR);
break;
case 8: // Extended
$BlockData .= $this->fread(4);
//00-01 Time Constant:
// Mono: 65536 - (256000000 / sample_rate)
// Stereo: 65536 - (256000000 / (sample_rate * 2))
$ThisBlock['time_constant'] = getid3_lib::LittleEndian2Int(substr($BlockData, 4, 2));
$ThisBlock['pack_method'] = getid3_lib::LittleEndian2Int(substr($BlockData, 6, 1));
$ThisBlock['stereo'] = (bool) getid3_lib::LittleEndian2Int(substr($BlockData, 7, 1));
$thisfile_audio['channels'] = ($ThisBlock['stereo'] ? 2 : 1);
$thisfile_audio['sample_rate'] = getid3_lib::trunc((256000000 / (65536 - $ThisBlock['time_constant'])) / $thisfile_audio['channels']);
break;
case 9: // data block that supersedes blocks 1 and 8. Used for stereo, 16 bit
$BlockData .= $this->fread(12);
if ($info['avdataoffset'] <= $OriginalAVdataOffset) {
$info['avdataoffset'] = $this->ftell();
}
$this->fseek($BlockSize - 12, SEEK_CUR);
$ThisBlock['sample_rate'] = getid3_lib::LittleEndian2Int(substr($BlockData, 4, 4));
$ThisBlock['bits_per_sample'] = getid3_lib::LittleEndian2Int(substr($BlockData, 8, 1));
$ThisBlock['channels'] = getid3_lib::LittleEndian2Int(substr($BlockData, 9, 1));
$ThisBlock['wFormat'] = getid3_lib::LittleEndian2Int(substr($BlockData, 10, 2));
$ThisBlock['compression_name'] = $this->VOCwFormatLookup($ThisBlock['wFormat']);
if ($this->VOCwFormatActualBitsPerSampleLookup($ThisBlock['wFormat'])) {
$thisfile_voc['compressed_bits_per_sample'] = $this->VOCwFormatActualBitsPerSampleLookup($ThisBlock['wFormat']);
}
$thisfile_audio['sample_rate'] = $ThisBlock['sample_rate'];
$thisfile_audio['bits_per_sample'] = $ThisBlock['bits_per_sample'];
$thisfile_audio['channels'] = $ThisBlock['channels'];
break;
default:
$this->warning('Unhandled block type "'.$BlockType.'" at offset '.$BlockOffset);
$this->fseek($BlockSize, SEEK_CUR);
break;
}
if (!empty($ThisBlock)) {
$ThisBlock['block_offset'] = $BlockOffset;
$ThisBlock['block_size'] = $BlockSize;
$ThisBlock['block_type_id'] = $BlockType;
$thisfile_voc['blocks'][] = $ThisBlock;
}
} while (!feof($this->getid3->fp) && ($BlockType != 0));
// Terminator block doesn't have size field, so seek back 3 spaces
$this->fseek(-3, SEEK_CUR);
ksort($thisfile_voc['blocktypes']);
if (!empty($thisfile_voc['compressed_bits_per_sample'])) {
$info['playtime_seconds'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / ($thisfile_voc['compressed_bits_per_sample'] * $thisfile_audio['channels'] * $thisfile_audio['sample_rate']);
$thisfile_audio['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
}
return true;
}
/**
* @param int $index
*
* @return string
*/
public function VOCcompressionTypeLookup($index) {
static $VOCcompressionTypeLookup = array(
0 => '8-bit',
1 => '4-bit',
2 => '2.6-bit',
3 => '2-bit'
);
return (isset($VOCcompressionTypeLookup[$index]) ? $VOCcompressionTypeLookup[$index] : 'Multi DAC ('.($index - 3).') channels');
}
/**
* @param int $index
*
* @return string|false
*/
public function VOCwFormatLookup($index) {
static $VOCwFormatLookup = array(
0x0000 => '8-bit unsigned PCM',
0x0001 => 'Creative 8-bit to 4-bit ADPCM',
0x0002 => 'Creative 8-bit to 3-bit ADPCM',
0x0003 => 'Creative 8-bit to 2-bit ADPCM',
0x0004 => '16-bit signed PCM',
0x0006 => 'CCITT a-Law',
0x0007 => 'CCITT u-Law',
0x2000 => 'Creative 16-bit to 4-bit ADPCM'
);
return (isset($VOCwFormatLookup[$index]) ? $VOCwFormatLookup[$index] : false);
}
/**
* @param int $index
*
* @return int|false
*/
public function VOCwFormatActualBitsPerSampleLookup($index) {
static $VOCwFormatLookup = array(
0x0000 => 8,
0x0001 => 4,
0x0002 => 3,
0x0003 => 2,
0x0004 => 16,
0x0006 => 8,
0x0007 => 8,
0x2000 => 4
);
return (isset($VOCwFormatLookup[$index]) ? $VOCwFormatLookup[$index] : false);
}
}
<?php
/////////////////////////////////////////////////////////////////
/// getID3() by James Heinrich <info@getid3.org> //
// available at https://github.com/JamesHeinrich/getID3 //
// or https://www.getid3.org //
// or http://getid3.sourceforge.net //
// see readme.txt for more details //
/////////////////////////////////////////////////////////////////
// //
// module.audio.vqf.php //
// module for analyzing VQF audio files //
// dependencies: NONE //
// ///
/////////////////////////////////////////////////////////////////
if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
exit;
}
class getid3_vqf extends getid3_handler
{
/**
* @return bool
*/
public function Analyze() {
$info = &$this->getid3->info;
// based loosely on code from TTwinVQ by Jurgen Faul <jfaulØgmx*de>
// http://jfaul.de/atl or http://j-faul.virtualave.net/atl/atl.html
$info['fileformat'] = 'vqf';
$info['audio']['dataformat'] = 'vqf';
$info['audio']['bitrate_mode'] = 'cbr';
$info['audio']['lossless'] = false;
// shortcut
$info['vqf']['raw'] = array();
$thisfile_vqf = &$info['vqf'];
$thisfile_vqf_raw = &$thisfile_vqf['raw'];
$this->fseek($info['avdataoffset']);
$VQFheaderData = $this->fread(16);
$offset = 0;
$thisfile_vqf_raw['header_tag'] = substr($VQFheaderData, $offset, 4);
$magic = 'TWIN';
if ($thisfile_vqf_raw['header_tag'] != $magic) {
$this->error('Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($thisfile_vqf_raw['header_tag']).'"');
unset($info['vqf']);
unset($info['fileformat']);
return false;
}
$offset += 4;
$thisfile_vqf_raw['version'] = substr($VQFheaderData, $offset, 8);
$offset += 8;
$thisfile_vqf_raw['size'] = getid3_lib::BigEndian2Int(substr($VQFheaderData, $offset, 4));
$offset += 4;
while ($this->ftell() < $info['avdataend']) {
$ChunkBaseOffset = $this->ftell();
$chunkoffset = 0;
$ChunkData = $this->fread(8);
$ChunkName = substr($ChunkData, $chunkoffset, 4);
if ($ChunkName == 'DATA') {
$info['avdataoffset'] = $ChunkBaseOffset;
break;
}
$chunkoffset += 4;
$ChunkSize = getid3_lib::BigEndian2Int(substr($ChunkData, $chunkoffset, 4));
$chunkoffset += 4;
if ($ChunkSize > ($info['avdataend'] - $this->ftell())) {
$this->error('Invalid chunk size ('.$ChunkSize.') for chunk "'.$ChunkName.'" at offset '.$ChunkBaseOffset);
break;
}
if ($ChunkSize > 0) {
$ChunkData .= $this->fread($ChunkSize);
}
switch ($ChunkName) {
case 'COMM':
// shortcut
$thisfile_vqf['COMM'] = array();
$thisfile_vqf_COMM = &$thisfile_vqf['COMM'];
$thisfile_vqf_COMM['channel_mode'] = getid3_lib::BigEndian2Int(substr($ChunkData, $chunkoffset, 4));
$chunkoffset += 4;
$thisfile_vqf_COMM['bitrate'] = getid3_lib::BigEndian2Int(substr($ChunkData, $chunkoffset, 4));
$chunkoffset += 4;
$thisfile_vqf_COMM['sample_rate'] = getid3_lib::BigEndian2Int(substr($ChunkData, $chunkoffset, 4));
$chunkoffset += 4;
$thisfile_vqf_COMM['security_level'] = getid3_lib::BigEndian2Int(substr($ChunkData, $chunkoffset, 4));
$chunkoffset += 4;
$info['audio']['channels'] = $thisfile_vqf_COMM['channel_mode'] + 1;
$info['audio']['sample_rate'] = $this->VQFchannelFrequencyLookup($thisfile_vqf_COMM['sample_rate']);
$info['audio']['bitrate'] = $thisfile_vqf_COMM['bitrate'] * 1000;
$info['audio']['encoder_options'] = 'CBR' . ceil($info['audio']['bitrate']/1000);
if ($info['audio']['bitrate'] == 0) {
$this->error('Corrupt VQF file: bitrate_audio == zero');
return false;
}
break;
case 'NAME':
case 'AUTH':
case '(c) ':
case 'FILE':
case 'COMT':
case 'ALBM':
$thisfile_vqf['comments'][$this->VQFcommentNiceNameLookup($ChunkName)][] = trim(substr($ChunkData, 8));
break;
case 'DSIZ':
$thisfile_vqf['DSIZ'] = getid3_lib::BigEndian2Int(substr($ChunkData, 8, 4));
break;
default:
$this->warning('Unhandled chunk type "'.$ChunkName.'" at offset '.$ChunkBaseOffset);
break;
}
}
$info['playtime_seconds'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['audio']['bitrate'];
if (isset($thisfile_vqf['DSIZ']) && (($thisfile_vqf['DSIZ'] != ($info['avdataend'] - $info['avdataoffset'] - strlen('DATA'))))) {
switch ($thisfile_vqf['DSIZ']) {
case 0:
case 1:
$this->warning('Invalid DSIZ value "'.$thisfile_vqf['DSIZ'].'". This is known to happen with VQF files encoded by Ahead Nero, and seems to be its way of saying this is TwinVQF v'.($thisfile_vqf['DSIZ'] + 1).'.0');
$info['audio']['encoder'] = 'Ahead Nero';
break;
default:
$this->warning('Probable corrupted file - should be '.$thisfile_vqf['DSIZ'].' bytes, actually '.($info['avdataend'] - $info['avdataoffset'] - strlen('DATA')));
break;
}
}
return true;
}
/**
* @param int $frequencyid
*
* @return int
*/
public function VQFchannelFrequencyLookup($frequencyid) {
static $VQFchannelFrequencyLookup = array(
11 => 11025,
22 => 22050,
44 => 44100
);
return (isset($VQFchannelFrequencyLookup[$frequencyid]) ? $VQFchannelFrequencyLookup[$frequencyid] : $frequencyid * 1000);
}
/**
* @param string $shortname
*
* @return string
*/
public function VQFcommentNiceNameLookup($shortname) {
static $VQFcommentNiceNameLookup = array(
'NAME' => 'title',
'AUTH' => 'artist',
'(c) ' => 'copyright',
'FILE' => 'filename',
'COMT' => 'comment',
'ALBM' => 'album'
);
return (isset($VQFcommentNiceNameLookup[$shortname]) ? $VQFcommentNiceNameLookup[$shortname] : $shortname);
}
}