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 10070 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.wavpack.php //
// module for analyzing WavPack v4.0+ Audio files //
// dependencies: NONE //
// ///
/////////////////////////////////////////////////////////////////
if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
exit;
}
class getid3_wavpack extends getid3_handler
{
/**
* @return bool
*/
public function Analyze() {
$info = &$this->getid3->info;
$this->fseek($info['avdataoffset']);
while (true) {
$wavpackheader = $this->fread(32);
if ($this->ftell() >= $info['avdataend']) {
break;
} elseif (feof($this->getid3->fp)) {
break;
} elseif (
isset($info['wavpack']['blockheader']['total_samples']) &&
isset($info['wavpack']['blockheader']['block_samples']) &&
($info['wavpack']['blockheader']['total_samples'] > 0) &&
($info['wavpack']['blockheader']['block_samples'] > 0) &&
(!isset($info['wavpack']['riff_trailer_size']) || ($info['wavpack']['riff_trailer_size'] <= 0)) &&
((isset($info['wavpack']['config_flags']['md5_checksum']) && ($info['wavpack']['config_flags']['md5_checksum'] === false)) || !empty($info['md5_data_source']))) {
break;
}
$blockheader_offset = $this->ftell() - 32;
$blockheader_magic = substr($wavpackheader, 0, 4);
$blockheader_size = getid3_lib::LittleEndian2Int(substr($wavpackheader, 4, 4));
$magic = 'wvpk';
if ($blockheader_magic != $magic) {
$this->error('Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$blockheader_offset.', found "'.getid3_lib::PrintHexBytes($blockheader_magic).'"');
switch (isset($info['audio']['dataformat']) ? $info['audio']['dataformat'] : '') {
case 'wavpack':
case 'wvc':
break;
default:
unset($info['fileformat']);
unset($info['audio']);
unset($info['wavpack']);
break;
}
return false;
}
if (empty($info['wavpack']['blockheader']['block_samples']) ||
empty($info['wavpack']['blockheader']['total_samples']) ||
($info['wavpack']['blockheader']['block_samples'] <= 0) ||
($info['wavpack']['blockheader']['total_samples'] <= 0)) {
// Also, it is possible that the first block might not have
// any samples (block_samples == 0) and in this case you should skip blocks
// until you find one with samples because the other information (like
// total_samples) are not guaranteed to be correct until (block_samples > 0)
// Finally, I have defined a format for files in which the length is not known
// (for example when raw files are created using pipes). In these cases
// total_samples will be -1 and you must seek to the final block to determine
// the total number of samples.
$info['audio']['dataformat'] = 'wavpack';
$info['fileformat'] = 'wavpack';
$info['audio']['lossless'] = true;
$info['audio']['bitrate_mode'] = 'vbr';
$info['wavpack']['blockheader']['offset'] = $blockheader_offset;
$info['wavpack']['blockheader']['magic'] = $blockheader_magic;
$info['wavpack']['blockheader']['size'] = $blockheader_size;
if ($info['wavpack']['blockheader']['size'] >= 0x100000) {
$this->error('Expecting WavPack block size less than "0x100000", found "'.$info['wavpack']['blockheader']['size'].'" at offset '.$info['wavpack']['blockheader']['offset']);
switch (isset($info['audio']['dataformat']) ? $info['audio']['dataformat'] : '') {
case 'wavpack':
case 'wvc':
break;
default:
unset($info['fileformat']);
unset($info['audio']);
unset($info['wavpack']);
break;
}
return false;
}
$info['wavpack']['blockheader']['minor_version'] = ord($wavpackheader[8]);
$info['wavpack']['blockheader']['major_version'] = ord($wavpackheader[9]);
if (($info['wavpack']['blockheader']['major_version'] != 4) ||
(($info['wavpack']['blockheader']['minor_version'] < 4) &&
($info['wavpack']['blockheader']['minor_version'] > 16))) {
$this->error('Expecting WavPack version between "4.2" and "4.16", found version "'.$info['wavpack']['blockheader']['major_version'].'.'.$info['wavpack']['blockheader']['minor_version'].'" at offset '.$info['wavpack']['blockheader']['offset']);
switch (isset($info['audio']['dataformat']) ? $info['audio']['dataformat'] : '') {
case 'wavpack':
case 'wvc':
break;
default:
unset($info['fileformat']);
unset($info['audio']);
unset($info['wavpack']);
break;
}
return false;
}
$info['wavpack']['blockheader']['track_number'] = ord($wavpackheader[10]); // unused
$info['wavpack']['blockheader']['index_number'] = ord($wavpackheader[11]); // unused
$info['wavpack']['blockheader']['total_samples'] = getid3_lib::LittleEndian2Int(substr($wavpackheader, 12, 4));
$info['wavpack']['blockheader']['block_index'] = getid3_lib::LittleEndian2Int(substr($wavpackheader, 16, 4));
$info['wavpack']['blockheader']['block_samples'] = getid3_lib::LittleEndian2Int(substr($wavpackheader, 20, 4));
$info['wavpack']['blockheader']['flags_raw'] = getid3_lib::LittleEndian2Int(substr($wavpackheader, 24, 4));
$info['wavpack']['blockheader']['crc'] = getid3_lib::LittleEndian2Int(substr($wavpackheader, 28, 4));
$info['wavpack']['blockheader']['flags']['bytes_per_sample'] = 1 + ($info['wavpack']['blockheader']['flags_raw'] & 0x00000003);
$info['wavpack']['blockheader']['flags']['mono'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x00000004);
$info['wavpack']['blockheader']['flags']['hybrid'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x00000008);
$info['wavpack']['blockheader']['flags']['joint_stereo'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x00000010);
$info['wavpack']['blockheader']['flags']['cross_decorrelation'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x00000020);
$info['wavpack']['blockheader']['flags']['hybrid_noiseshape'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x00000040);
$info['wavpack']['blockheader']['flags']['ieee_32bit_float'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x00000080);
$info['wavpack']['blockheader']['flags']['int_32bit'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x00000100);
$info['wavpack']['blockheader']['flags']['hybrid_bitrate_noise'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x00000200);
$info['wavpack']['blockheader']['flags']['hybrid_balance_noise'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x00000400);
$info['wavpack']['blockheader']['flags']['multichannel_initial'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x00000800);
$info['wavpack']['blockheader']['flags']['multichannel_final'] = (bool) ($info['wavpack']['blockheader']['flags_raw'] & 0x00001000);
$info['audio']['lossless'] = !$info['wavpack']['blockheader']['flags']['hybrid'];
}
while (!feof($this->getid3->fp) && ($this->ftell() < ($blockheader_offset + $blockheader_size + 8))) {
$metablock = array('offset'=>$this->ftell());
$metablockheader = $this->fread(2);
if (feof($this->getid3->fp)) {
break;
}
$metablock['id'] = ord($metablockheader[0]);
$metablock['function_id'] = ($metablock['id'] & 0x3F);
$metablock['function_name'] = $this->WavPackMetablockNameLookup($metablock['function_id']);
// The 0x20 bit in the id of the meta subblocks (which is defined as
// ID_OPTIONAL_DATA) is a permanent part of the id. The idea is that
// if a decoder encounters an id that it does not know about, it uses
// that "ID_OPTIONAL_DATA" flag to determine what to do. If it is set
// then the decoder simply ignores the metadata, but if it is zero
// then the decoder should quit because it means that an understanding
// of the metadata is required to correctly decode the audio.
$metablock['non_decoder'] = (bool) ($metablock['id'] & 0x20);
$metablock['padded_data'] = (bool) ($metablock['id'] & 0x40);
$metablock['large_block'] = (bool) ($metablock['id'] & 0x80);
if ($metablock['large_block']) {
$metablockheader .= $this->fread(2);
}
$metablock['size'] = getid3_lib::LittleEndian2Int(substr($metablockheader, 1)) * 2; // size is stored in words
$metablock['data'] = null;
$metablock['comments'] = array();
if ($metablock['size'] > 0) {
switch ($metablock['function_id']) {
case 0x21: // ID_RIFF_HEADER
case 0x22: // ID_RIFF_TRAILER
case 0x23: // ID_REPLAY_GAIN
case 0x24: // ID_CUESHEET
case 0x25: // ID_CONFIG_BLOCK
case 0x26: // ID_MD5_CHECKSUM
$metablock['data'] = $this->fread($metablock['size']);
if ($metablock['padded_data']) {
// padded to the nearest even byte
$metablock['size']--;
$metablock['data'] = substr($metablock['data'], 0, -1);
}
break;
case 0x00: // ID_DUMMY
case 0x01: // ID_ENCODER_INFO
case 0x02: // ID_DECORR_TERMS
case 0x03: // ID_DECORR_WEIGHTS
case 0x04: // ID_DECORR_SAMPLES
case 0x05: // ID_ENTROPY_VARS
case 0x06: // ID_HYBRID_PROFILE
case 0x07: // ID_SHAPING_WEIGHTS
case 0x08: // ID_FLOAT_INFO
case 0x09: // ID_INT32_INFO
case 0x0A: // ID_WV_BITSTREAM
case 0x0B: // ID_WVC_BITSTREAM
case 0x0C: // ID_WVX_BITSTREAM
case 0x0D: // ID_CHANNEL_INFO
$this->fseek($metablock['offset'] + ($metablock['large_block'] ? 4 : 2) + $metablock['size']);
break;
default:
$this->warning('Unexpected metablock type "0x'.str_pad(dechex($metablock['function_id']), 2, '0', STR_PAD_LEFT).'" at offset '.$metablock['offset']);
$this->fseek($metablock['offset'] + ($metablock['large_block'] ? 4 : 2) + $metablock['size']);
break;
}
switch ($metablock['function_id']) {
case 0x21: // ID_RIFF_HEADER
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, true);
$original_wav_filesize = getid3_lib::LittleEndian2Int(substr($metablock['data'], 4, 4));
$getid3_temp = new getID3();
$getid3_temp->openfile($this->getid3->filename, $this->getid3->info['filesize'], $this->getid3->fp);
$getid3_riff = new getid3_riff($getid3_temp);
$getid3_riff->ParseRIFFdata($metablock['data']);
$metablock['riff'] = $getid3_temp->info['riff'];
$info['audio']['sample_rate'] = $getid3_temp->info['riff']['raw']['fmt ']['nSamplesPerSec'];
unset($getid3_riff, $getid3_temp);
$metablock['riff']['original_filesize'] = $original_wav_filesize;
$info['wavpack']['riff_trailer_size'] = $original_wav_filesize - $metablock['riff']['WAVE']['data'][0]['size'] - $metablock['riff']['header_size'];
$info['playtime_seconds'] = $info['wavpack']['blockheader']['total_samples'] / $info['audio']['sample_rate'];
// Safe RIFF header in case there's a RIFF footer later
$metablockRIFFheader = $metablock['data'];
break;
case 0x22: // ID_RIFF_TRAILER
$metablockRIFFfooter = isset($metablockRIFFheader) ? $metablockRIFFheader : ''.$metablock['data'];
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio-video.riff.php', __FILE__, true);
$startoffset = $metablock['offset'] + ($metablock['large_block'] ? 4 : 2);
$getid3_temp = new getID3();
$getid3_temp->openfile($this->getid3->filename, $this->getid3->info['filesize'], $this->getid3->fp);
$getid3_temp->info['avdataend'] = $info['avdataend'];
//$getid3_temp->info['fileformat'] = 'riff';
$getid3_riff = new getid3_riff($getid3_temp);
$metablock['riff'] = $getid3_riff->ParseRIFF($startoffset, $startoffset + $metablock['size']);
if (!empty($metablock['riff']['INFO'])) {
getid3_riff::parseComments($metablock['riff']['INFO'], $metablock['comments']);
$info['tags']['riff'] = $metablock['comments'];
}
unset($getid3_temp, $getid3_riff);
break;
case 0x23: // ID_REPLAY_GAIN
$this->warning('WavPack "Replay Gain" contents not yet handled by getID3() in metablock at offset '.$metablock['offset']);
break;
case 0x24: // ID_CUESHEET
$this->warning('WavPack "Cuesheet" contents not yet handled by getID3() in metablock at offset '.$metablock['offset']);
break;
case 0x25: // ID_CONFIG_BLOCK
$metablock['flags_raw'] = getid3_lib::LittleEndian2Int(substr($metablock['data'], 0, 3));
$metablock['flags']['adobe_mode'] = (bool) ($metablock['flags_raw'] & 0x000001); // "adobe" mode for 32-bit floats
$metablock['flags']['fast_flag'] = (bool) ($metablock['flags_raw'] & 0x000002); // fast mode
$metablock['flags']['very_fast_flag'] = (bool) ($metablock['flags_raw'] & 0x000004); // double fast
$metablock['flags']['high_flag'] = (bool) ($metablock['flags_raw'] & 0x000008); // high quality mode
$metablock['flags']['very_high_flag'] = (bool) ($metablock['flags_raw'] & 0x000010); // double high (not used yet)
$metablock['flags']['bitrate_kbps'] = (bool) ($metablock['flags_raw'] & 0x000020); // bitrate is kbps, not bits / sample
$metablock['flags']['auto_shaping'] = (bool) ($metablock['flags_raw'] & 0x000040); // automatic noise shaping
$metablock['flags']['shape_override'] = (bool) ($metablock['flags_raw'] & 0x000080); // shaping mode specified
$metablock['flags']['joint_override'] = (bool) ($metablock['flags_raw'] & 0x000100); // joint-stereo mode specified
$metablock['flags']['copy_time'] = (bool) ($metablock['flags_raw'] & 0x000200); // copy file-time from source
$metablock['flags']['create_exe'] = (bool) ($metablock['flags_raw'] & 0x000400); // create executable
$metablock['flags']['create_wvc'] = (bool) ($metablock['flags_raw'] & 0x000800); // create correction file
$metablock['flags']['optimize_wvc'] = (bool) ($metablock['flags_raw'] & 0x001000); // maximize bybrid compression
$metablock['flags']['quality_mode'] = (bool) ($metablock['flags_raw'] & 0x002000); // psychoacoustic quality mode
$metablock['flags']['raw_flag'] = (bool) ($metablock['flags_raw'] & 0x004000); // raw mode (not implemented yet)
$metablock['flags']['calc_noise'] = (bool) ($metablock['flags_raw'] & 0x008000); // calc noise in hybrid mode
$metablock['flags']['lossy_mode'] = (bool) ($metablock['flags_raw'] & 0x010000); // obsolete (for information)
$metablock['flags']['extra_mode'] = (bool) ($metablock['flags_raw'] & 0x020000); // extra processing mode
$metablock['flags']['skip_wvx'] = (bool) ($metablock['flags_raw'] & 0x040000); // no wvx stream w/ floats & big ints
$metablock['flags']['md5_checksum'] = (bool) ($metablock['flags_raw'] & 0x080000); // compute & store MD5 signature
$metablock['flags']['quiet_mode'] = (bool) ($metablock['flags_raw'] & 0x100000); // don't report progress %
$info['wavpack']['config_flags'] = $metablock['flags'];
$info['audio']['encoder_options'] = '';
if ($info['wavpack']['blockheader']['flags']['hybrid']) {
$info['audio']['encoder_options'] .= ' -b???';
}
$info['audio']['encoder_options'] .= ($metablock['flags']['adobe_mode'] ? ' -a' : '');
$info['audio']['encoder_options'] .= ($metablock['flags']['optimize_wvc'] ? ' -cc' : '');
$info['audio']['encoder_options'] .= ($metablock['flags']['create_exe'] ? ' -e' : '');
$info['audio']['encoder_options'] .= ($metablock['flags']['fast_flag'] ? ' -f' : '');
$info['audio']['encoder_options'] .= ($metablock['flags']['joint_override'] ? ' -j?' : '');
$info['audio']['encoder_options'] .= ($metablock['flags']['high_flag'] ? ' -h' : '');
$info['audio']['encoder_options'] .= ($metablock['flags']['md5_checksum'] ? ' -m' : '');
$info['audio']['encoder_options'] .= ($metablock['flags']['calc_noise'] ? ' -n' : '');
$info['audio']['encoder_options'] .= ($metablock['flags']['shape_override'] ? ' -s?' : '');
$info['audio']['encoder_options'] .= ($metablock['flags']['extra_mode'] ? ' -x?' : '');
if (!empty($info['audio']['encoder_options'])) {
$info['audio']['encoder_options'] = trim($info['audio']['encoder_options']);
} elseif (isset($info['audio']['encoder_options'])) {
unset($info['audio']['encoder_options']);
}
break;
case 0x26: // ID_MD5_CHECKSUM
if (strlen($metablock['data']) == 16) {
$info['md5_data_source'] = strtolower(getid3_lib::PrintHexBytes($metablock['data'], true, false, false));
} else {
$this->warning('Expecting 16 bytes of WavPack "MD5 Checksum" in metablock at offset '.$metablock['offset'].', but found '.strlen($metablock['data']).' bytes');
}
break;
case 0x00: // ID_DUMMY
case 0x01: // ID_ENCODER_INFO
case 0x02: // ID_DECORR_TERMS
case 0x03: // ID_DECORR_WEIGHTS
case 0x04: // ID_DECORR_SAMPLES
case 0x05: // ID_ENTROPY_VARS
case 0x06: // ID_HYBRID_PROFILE
case 0x07: // ID_SHAPING_WEIGHTS
case 0x08: // ID_FLOAT_INFO
case 0x09: // ID_INT32_INFO
case 0x0A: // ID_WV_BITSTREAM
case 0x0B: // ID_WVC_BITSTREAM
case 0x0C: // ID_WVX_BITSTREAM
case 0x0D: // ID_CHANNEL_INFO
unset($metablock);
break;
}
}
if (!empty($metablock)) {
$info['wavpack']['metablocks'][] = $metablock;
}
}
}
$info['audio']['encoder'] = 'WavPack v'.$info['wavpack']['blockheader']['major_version'].'.'.str_pad($info['wavpack']['blockheader']['minor_version'], 2, '0', STR_PAD_LEFT);
$info['audio']['bits_per_sample'] = $info['wavpack']['blockheader']['flags']['bytes_per_sample'] * 8;
$info['audio']['channels'] = ($info['wavpack']['blockheader']['flags']['mono'] ? 1 : 2);
if (!empty($info['playtime_seconds'])) {
$info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
} else {
$info['audio']['dataformat'] = 'wvc';
}
return true;
}
/**
* @param int $id
*
* @return string
*/
public function WavPackMetablockNameLookup(&$id) {
static $WavPackMetablockNameLookup = array(
0x00 => 'Dummy',
0x01 => 'Encoder Info',
0x02 => 'Decorrelation Terms',
0x03 => 'Decorrelation Weights',
0x04 => 'Decorrelation Samples',
0x05 => 'Entropy Variables',
0x06 => 'Hybrid Profile',
0x07 => 'Shaping Weights',
0x08 => 'Float Info',
0x09 => 'Int32 Info',
0x0A => 'WV Bitstream',
0x0B => 'WVC Bitstream',
0x0C => 'WVX Bitstream',
0x0D => 'Channel Info',
0x21 => 'RIFF header',
0x22 => 'RIFF trailer',
0x23 => 'Replay Gain',
0x24 => 'Cuesheet',
0x25 => 'Config Block',
0x26 => 'MD5 Checksum',
);
return (isset($WavPackMetablockNameLookup[$id]) ? $WavPackMetablockNameLookup[$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.graphic.bmp.php //
// module for analyzing BMP Image files //
// dependencies: NONE //
// ///
/////////////////////////////////////////////////////////////////
if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
exit;
}
class getid3_bmp extends getid3_handler
{
public $ExtractPalette = false;
public $ExtractData = false;
/**
* @return bool
*/
public function Analyze() {
$info = &$this->getid3->info;
// shortcuts
$info['bmp']['header']['raw'] = array();
$thisfile_bmp = &$info['bmp'];
$thisfile_bmp_header = &$thisfile_bmp['header'];
$thisfile_bmp_header_raw = &$thisfile_bmp_header['raw'];
// BITMAPFILEHEADER [14 bytes] - http://msdn.microsoft.com/library/en-us/gdi/bitmaps_62uq.asp
// all versions
// WORD bfType;
// DWORD bfSize;
// WORD bfReserved1;
// WORD bfReserved2;
// DWORD bfOffBits;
$this->fseek($info['avdataoffset']);
$offset = 0;
$BMPheader = $this->fread(14 + 40);
$thisfile_bmp_header_raw['identifier'] = substr($BMPheader, $offset, 2);
$offset += 2;
$magic = 'BM';
if ($thisfile_bmp_header_raw['identifier'] != $magic) {
$this->error('Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($thisfile_bmp_header_raw['identifier']).'"');
unset($info['fileformat']);
unset($info['bmp']);
return false;
}
$thisfile_bmp_header_raw['filesize'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
$offset += 4;
$thisfile_bmp_header_raw['reserved1'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2));
$offset += 2;
$thisfile_bmp_header_raw['reserved2'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2));
$offset += 2;
$thisfile_bmp_header_raw['data_offset'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
$offset += 4;
$thisfile_bmp_header_raw['header_size'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
$offset += 4;
// check if the hardcoded-to-1 "planes" is at offset 22 or 26
$planes22 = getid3_lib::LittleEndian2Int(substr($BMPheader, 22, 2));
$planes26 = getid3_lib::LittleEndian2Int(substr($BMPheader, 26, 2));
if (($planes22 == 1) && ($planes26 != 1)) {
$thisfile_bmp['type_os'] = 'OS/2';
$thisfile_bmp['type_version'] = 1;
} elseif (($planes26 == 1) && ($planes22 != 1)) {
$thisfile_bmp['type_os'] = 'Windows';
$thisfile_bmp['type_version'] = 1;
} elseif ($thisfile_bmp_header_raw['header_size'] == 12) {
$thisfile_bmp['type_os'] = 'OS/2';
$thisfile_bmp['type_version'] = 1;
} elseif ($thisfile_bmp_header_raw['header_size'] == 40) {
$thisfile_bmp['type_os'] = 'Windows';
$thisfile_bmp['type_version'] = 1;
} elseif ($thisfile_bmp_header_raw['header_size'] == 84) {
$thisfile_bmp['type_os'] = 'Windows';
$thisfile_bmp['type_version'] = 4;
} elseif ($thisfile_bmp_header_raw['header_size'] == 100) {
$thisfile_bmp['type_os'] = 'Windows';
$thisfile_bmp['type_version'] = 5;
} else {
$this->error('Unknown BMP subtype (or not a BMP file)');
unset($info['fileformat']);
unset($info['bmp']);
return false;
}
$info['fileformat'] = 'bmp';
$info['video']['dataformat'] = 'bmp';
$info['video']['lossless'] = true;
$info['video']['pixel_aspect_ratio'] = (float) 1;
if ($thisfile_bmp['type_os'] == 'OS/2') {
// OS/2-format BMP
// http://netghost.narod.ru/gff/graphics/summary/os2bmp.htm
// DWORD Size; /* Size of this structure in bytes */
// DWORD Width; /* Bitmap width in pixels */
// DWORD Height; /* Bitmap height in pixel */
// WORD NumPlanes; /* Number of bit planes (color depth) */
// WORD BitsPerPixel; /* Number of bits per pixel per plane */
$thisfile_bmp_header_raw['width'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2));
$offset += 2;
$thisfile_bmp_header_raw['height'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2));
$offset += 2;
$thisfile_bmp_header_raw['planes'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2));
$offset += 2;
$thisfile_bmp_header_raw['bits_per_pixel'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2));
$offset += 2;
$info['video']['resolution_x'] = $thisfile_bmp_header_raw['width'];
$info['video']['resolution_y'] = $thisfile_bmp_header_raw['height'];
$info['video']['codec'] = 'BI_RGB '.$thisfile_bmp_header_raw['bits_per_pixel'].'-bit';
$info['video']['bits_per_sample'] = $thisfile_bmp_header_raw['bits_per_pixel'];
if ($thisfile_bmp['type_version'] >= 2) {
// DWORD Compression; /* Bitmap compression scheme */
// DWORD ImageDataSize; /* Size of bitmap data in bytes */
// DWORD XResolution; /* X resolution of display device */
// DWORD YResolution; /* Y resolution of display device */
// DWORD ColorsUsed; /* Number of color table indices used */
// DWORD ColorsImportant; /* Number of important color indices */
// WORD Units; /* Type of units used to measure resolution */
// WORD Reserved; /* Pad structure to 4-byte boundary */
// WORD Recording; /* Recording algorithm */
// WORD Rendering; /* Halftoning algorithm used */
// DWORD Size1; /* Reserved for halftoning algorithm use */
// DWORD Size2; /* Reserved for halftoning algorithm use */
// DWORD ColorEncoding; /* Color model used in bitmap */
// DWORD Identifier; /* Reserved for application use */
$thisfile_bmp_header_raw['compression'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
$offset += 4;
$thisfile_bmp_header_raw['bmp_data_size'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
$offset += 4;
$thisfile_bmp_header_raw['resolution_h'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
$offset += 4;
$thisfile_bmp_header_raw['resolution_v'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
$offset += 4;
$thisfile_bmp_header_raw['colors_used'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
$offset += 4;
$thisfile_bmp_header_raw['colors_important'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
$offset += 4;
$thisfile_bmp_header_raw['resolution_units'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2));
$offset += 2;
$thisfile_bmp_header_raw['reserved1'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2));
$offset += 2;
$thisfile_bmp_header_raw['recording'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2));
$offset += 2;
$thisfile_bmp_header_raw['rendering'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2));
$offset += 2;
$thisfile_bmp_header_raw['size1'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
$offset += 4;
$thisfile_bmp_header_raw['size2'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
$offset += 4;
$thisfile_bmp_header_raw['color_encoding'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
$offset += 4;
$thisfile_bmp_header_raw['identifier'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
$offset += 4;
$thisfile_bmp_header['compression'] = $this->BMPcompressionOS2Lookup($thisfile_bmp_header_raw['compression']);
$info['video']['codec'] = $thisfile_bmp_header['compression'].' '.$thisfile_bmp_header_raw['bits_per_pixel'].'-bit';
}
} elseif ($thisfile_bmp['type_os'] == 'Windows') {
// Windows-format BMP
// BITMAPINFOHEADER - [40 bytes] http://msdn.microsoft.com/library/en-us/gdi/bitmaps_1rw2.asp
// all versions
// DWORD biSize;
// LONG biWidth;
// LONG biHeight;
// WORD biPlanes;
// WORD biBitCount;
// DWORD biCompression;
// DWORD biSizeImage;
// LONG biXPelsPerMeter;
// LONG biYPelsPerMeter;
// DWORD biClrUsed;
// DWORD biClrImportant;
// possibly integrate this section and module.audio-video.riff.php::ParseBITMAPINFOHEADER() ?
$thisfile_bmp_header_raw['width'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4), true);
$offset += 4;
$thisfile_bmp_header_raw['height'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4), true);
$offset += 4;
$thisfile_bmp_header_raw['planes'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2));
$offset += 2;
$thisfile_bmp_header_raw['bits_per_pixel'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2));
$offset += 2;
$thisfile_bmp_header_raw['compression'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
$offset += 4;
$thisfile_bmp_header_raw['bmp_data_size'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
$offset += 4;
$thisfile_bmp_header_raw['resolution_h'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4), true);
$offset += 4;
$thisfile_bmp_header_raw['resolution_v'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4), true);
$offset += 4;
$thisfile_bmp_header_raw['colors_used'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
$offset += 4;
$thisfile_bmp_header_raw['colors_important'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
$offset += 4;
$thisfile_bmp_header['compression'] = $this->BMPcompressionWindowsLookup($thisfile_bmp_header_raw['compression']);
$info['video']['resolution_x'] = $thisfile_bmp_header_raw['width'];
$info['video']['resolution_y'] = $thisfile_bmp_header_raw['height'];
$info['video']['codec'] = $thisfile_bmp_header['compression'].' '.$thisfile_bmp_header_raw['bits_per_pixel'].'-bit';
$info['video']['bits_per_sample'] = $thisfile_bmp_header_raw['bits_per_pixel'];
if (($thisfile_bmp['type_version'] >= 4) || ($thisfile_bmp_header_raw['compression'] == 3)) {
// should only be v4+, but BMPs with type_version==1 and BI_BITFIELDS compression have been seen
$BMPheader .= $this->fread(44);
// BITMAPV4HEADER - [44 bytes] - http://msdn.microsoft.com/library/en-us/gdi/bitmaps_2k1e.asp
// Win95+, WinNT4.0+
// DWORD bV4RedMask;
// DWORD bV4GreenMask;
// DWORD bV4BlueMask;
// DWORD bV4AlphaMask;
// DWORD bV4CSType;
// CIEXYZTRIPLE bV4Endpoints;
// DWORD bV4GammaRed;
// DWORD bV4GammaGreen;
// DWORD bV4GammaBlue;
$thisfile_bmp_header_raw['red_mask'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
$offset += 4;
$thisfile_bmp_header_raw['green_mask'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
$offset += 4;
$thisfile_bmp_header_raw['blue_mask'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
$offset += 4;
$thisfile_bmp_header_raw['alpha_mask'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
$offset += 4;
$thisfile_bmp_header_raw['cs_type'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
$offset += 4;
$thisfile_bmp_header_raw['ciexyz_red'] = substr($BMPheader, $offset, 4);
$offset += 4;
$thisfile_bmp_header_raw['ciexyz_green'] = substr($BMPheader, $offset, 4);
$offset += 4;
$thisfile_bmp_header_raw['ciexyz_blue'] = substr($BMPheader, $offset, 4);
$offset += 4;
$thisfile_bmp_header_raw['gamma_red'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
$offset += 4;
$thisfile_bmp_header_raw['gamma_green'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
$offset += 4;
$thisfile_bmp_header_raw['gamma_blue'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
$offset += 4;
$thisfile_bmp_header['ciexyz_red'] = getid3_lib::FixedPoint2_30(strrev($thisfile_bmp_header_raw['ciexyz_red']));
$thisfile_bmp_header['ciexyz_green'] = getid3_lib::FixedPoint2_30(strrev($thisfile_bmp_header_raw['ciexyz_green']));
$thisfile_bmp_header['ciexyz_blue'] = getid3_lib::FixedPoint2_30(strrev($thisfile_bmp_header_raw['ciexyz_blue']));
}
if ($thisfile_bmp['type_version'] >= 5) {
$BMPheader .= $this->fread(16);
// BITMAPV5HEADER - [16 bytes] - http://msdn.microsoft.com/library/en-us/gdi/bitmaps_7c36.asp
// Win98+, Win2000+
// DWORD bV5Intent;
// DWORD bV5ProfileData;
// DWORD bV5ProfileSize;
// DWORD bV5Reserved;
$thisfile_bmp_header_raw['intent'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
$offset += 4;
$thisfile_bmp_header_raw['profile_data_offset'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
$offset += 4;
$thisfile_bmp_header_raw['profile_data_size'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
$offset += 4;
$thisfile_bmp_header_raw['reserved3'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
$offset += 4;
}
} else {
$this->error('Unknown BMP format in header.');
return false;
}
if ($this->ExtractPalette || $this->ExtractData) {
$PaletteEntries = 0;
if ($thisfile_bmp_header_raw['bits_per_pixel'] < 16) {
$PaletteEntries = pow(2, $thisfile_bmp_header_raw['bits_per_pixel']);
} elseif (isset($thisfile_bmp_header_raw['colors_used']) && ($thisfile_bmp_header_raw['colors_used'] > 0) && ($thisfile_bmp_header_raw['colors_used'] <= 256)) {
$PaletteEntries = $thisfile_bmp_header_raw['colors_used'];
}
if ($PaletteEntries > 0) {
$BMPpalette = $this->fread(4 * $PaletteEntries);
$paletteoffset = 0;
for ($i = 0; $i < $PaletteEntries; $i++) {
// RGBQUAD - http://msdn.microsoft.com/library/en-us/gdi/bitmaps_5f8y.asp
// BYTE rgbBlue;
// BYTE rgbGreen;
// BYTE rgbRed;
// BYTE rgbReserved;
$blue = getid3_lib::LittleEndian2Int(substr($BMPpalette, $paletteoffset++, 1));
$green = getid3_lib::LittleEndian2Int(substr($BMPpalette, $paletteoffset++, 1));
$red = getid3_lib::LittleEndian2Int(substr($BMPpalette, $paletteoffset++, 1));
if (($thisfile_bmp['type_os'] == 'OS/2') && ($thisfile_bmp['type_version'] == 1)) {
// no padding byte
} else {
$paletteoffset++; // padding byte
}
$thisfile_bmp['palette'][$i] = (($red << 16) | ($green << 8) | $blue);
}
}
}
if ($this->ExtractData) {
$this->fseek($thisfile_bmp_header_raw['data_offset']);
$RowByteLength = ceil(($thisfile_bmp_header_raw['width'] * ($thisfile_bmp_header_raw['bits_per_pixel'] / 8)) / 4) * 4; // round up to nearest DWORD boundry
$BMPpixelData = $this->fread($thisfile_bmp_header_raw['height'] * $RowByteLength);
$pixeldataoffset = 0;
$thisfile_bmp_header_raw['compression'] = (isset($thisfile_bmp_header_raw['compression']) ? $thisfile_bmp_header_raw['compression'] : '');
switch ($thisfile_bmp_header_raw['compression']) {
case 0: // BI_RGB
switch ($thisfile_bmp_header_raw['bits_per_pixel']) {
case 1:
for ($row = ($thisfile_bmp_header_raw['height'] - 1); $row >= 0; $row--) {
for ($col = 0; $col < $thisfile_bmp_header_raw['width']; $col = $col) {
$paletteindexbyte = ord($BMPpixelData[$pixeldataoffset++]);
for ($i = 7; $i >= 0; $i--) {
$paletteindex = ($paletteindexbyte & (0x01 << $i)) >> $i;
$thisfile_bmp['data'][$row][$col] = $thisfile_bmp['palette'][$paletteindex];
$col++;
}
}
while (($pixeldataoffset % 4) != 0) {
// lines are padded to nearest DWORD
$pixeldataoffset++;
}
}
break;
case 4:
for ($row = ($thisfile_bmp_header_raw['height'] - 1); $row >= 0; $row--) {
for ($col = 0; $col < $thisfile_bmp_header_raw['width']; $col = $col) {
$paletteindexbyte = ord($BMPpixelData[$pixeldataoffset++]);
for ($i = 1; $i >= 0; $i--) {
$paletteindex = ($paletteindexbyte & (0x0F << (4 * $i))) >> (4 * $i);
$thisfile_bmp['data'][$row][$col] = $thisfile_bmp['palette'][$paletteindex];
$col++;
}
}
while (($pixeldataoffset % 4) != 0) {
// lines are padded to nearest DWORD
$pixeldataoffset++;
}
}
break;
case 8:
for ($row = ($thisfile_bmp_header_raw['height'] - 1); $row >= 0; $row--) {
for ($col = 0; $col < $thisfile_bmp_header_raw['width']; $col++) {
$paletteindex = ord($BMPpixelData[$pixeldataoffset++]);
$thisfile_bmp['data'][$row][$col] = $thisfile_bmp['palette'][$paletteindex];
}
while (($pixeldataoffset % 4) != 0) {
// lines are padded to nearest DWORD
$pixeldataoffset++;
}
}
break;
case 24:
for ($row = ($thisfile_bmp_header_raw['height'] - 1); $row >= 0; $row--) {
for ($col = 0; $col < $thisfile_bmp_header_raw['width']; $col++) {
$thisfile_bmp['data'][$row][$col] = (ord($BMPpixelData[$pixeldataoffset+2]) << 16) | (ord($BMPpixelData[$pixeldataoffset+1]) << 8) | ord($BMPpixelData[$pixeldataoffset]);
$pixeldataoffset += 3;
}
while (($pixeldataoffset % 4) != 0) {
// lines are padded to nearest DWORD
$pixeldataoffset++;
}
}
break;
case 32:
for ($row = ($thisfile_bmp_header_raw['height'] - 1); $row >= 0; $row--) {
for ($col = 0; $col < $thisfile_bmp_header_raw['width']; $col++) {
$thisfile_bmp['data'][$row][$col] = (ord($BMPpixelData[$pixeldataoffset+3]) << 24) | (ord($BMPpixelData[$pixeldataoffset+2]) << 16) | (ord($BMPpixelData[$pixeldataoffset+1]) << 8) | ord($BMPpixelData[$pixeldataoffset]);
$pixeldataoffset += 4;
}
while (($pixeldataoffset % 4) != 0) {
// lines are padded to nearest DWORD
$pixeldataoffset++;
}
}
break;
case 16:
// ?
break;
default:
$this->error('Unknown bits-per-pixel value ('.$thisfile_bmp_header_raw['bits_per_pixel'].') - cannot read pixel data');
break;
}
break;
case 1: // BI_RLE8 - http://msdn.microsoft.com/library/en-us/gdi/bitmaps_6x0u.asp
switch ($thisfile_bmp_header_raw['bits_per_pixel']) {
case 8:
$pixelcounter = 0;
while ($pixeldataoffset < strlen($BMPpixelData)) {
$firstbyte = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
$secondbyte = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
if ($firstbyte == 0) {
// escaped/absolute mode - the first byte of the pair can be set to zero to
// indicate an escape character that denotes the end of a line, the end of
// a bitmap, or a delta, depending on the value of the second byte.
switch ($secondbyte) {
case 0:
// end of line
// no need for special processing, just ignore
break;
case 1:
// end of bitmap
$pixeldataoffset = strlen($BMPpixelData); // force to exit loop just in case
break;
case 2:
// delta - The 2 bytes following the escape contain unsigned values
// indicating the horizontal and vertical offsets of the next pixel
// from the current position.
$colincrement = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
$rowincrement = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
$col = ($pixelcounter % $thisfile_bmp_header_raw['width']) + $colincrement;
$row = ($thisfile_bmp_header_raw['height'] - 1 - (($pixelcounter - $col) / $thisfile_bmp_header_raw['width'])) - $rowincrement;
$pixelcounter = ($row * $thisfile_bmp_header_raw['width']) + $col;
break;
default:
// In absolute mode, the first byte is zero and the second byte is a
// value in the range 03H through FFH. The second byte represents the
// number of bytes that follow, each of which contains the color index
// of a single pixel. Each run must be aligned on a word boundary.
for ($i = 0; $i < $secondbyte; $i++) {
$paletteindex = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
$col = $pixelcounter % $thisfile_bmp_header_raw['width'];
$row = $thisfile_bmp_header_raw['height'] - 1 - (($pixelcounter - $col) / $thisfile_bmp_header_raw['width']);
$thisfile_bmp['data'][$row][$col] = $thisfile_bmp['palette'][$paletteindex];
$pixelcounter++;
}
while (($pixeldataoffset % 2) != 0) {
// Each run must be aligned on a word boundary.
$pixeldataoffset++;
}
break;
}
} else {
// encoded mode - the first byte specifies the number of consecutive pixels
// to be drawn using the color index contained in the second byte.
for ($i = 0; $i < $firstbyte; $i++) {
$col = $pixelcounter % $thisfile_bmp_header_raw['width'];
$row = $thisfile_bmp_header_raw['height'] - 1 - (($pixelcounter - $col) / $thisfile_bmp_header_raw['width']);
$thisfile_bmp['data'][$row][$col] = $thisfile_bmp['palette'][$secondbyte];
$pixelcounter++;
}
}
}
break;
default:
$this->error('Unknown bits-per-pixel value ('.$thisfile_bmp_header_raw['bits_per_pixel'].') - cannot read pixel data');
break;
}
break;
case 2: // BI_RLE4 - http://msdn.microsoft.com/library/en-us/gdi/bitmaps_6x0u.asp
switch ($thisfile_bmp_header_raw['bits_per_pixel']) {
case 4:
$pixelcounter = 0;
while ($pixeldataoffset < strlen($BMPpixelData)) {
$firstbyte = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
$secondbyte = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
if ($firstbyte == 0) {
// escaped/absolute mode - the first byte of the pair can be set to zero to
// indicate an escape character that denotes the end of a line, the end of
// a bitmap, or a delta, depending on the value of the second byte.
switch ($secondbyte) {
case 0:
// end of line
// no need for special processing, just ignore
break;
case 1:
// end of bitmap
$pixeldataoffset = strlen($BMPpixelData); // force to exit loop just in case
break;
case 2:
// delta - The 2 bytes following the escape contain unsigned values
// indicating the horizontal and vertical offsets of the next pixel
// from the current position.
$colincrement = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
$rowincrement = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
$col = ($pixelcounter % $thisfile_bmp_header_raw['width']) + $colincrement;
$row = ($thisfile_bmp_header_raw['height'] - 1 - (($pixelcounter - $col) / $thisfile_bmp_header_raw['width'])) - $rowincrement;
$pixelcounter = ($row * $thisfile_bmp_header_raw['width']) + $col;
break;
default:
// In absolute mode, the first byte is zero. The second byte contains the number
// of color indexes that follow. Subsequent bytes contain color indexes in their
// high- and low-order 4 bits, one color index for each pixel. In absolute mode,
// each run must be aligned on a word boundary.
unset($paletteindexes);
$paletteindexes = array();
for ($i = 0; $i < ceil($secondbyte / 2); $i++) {
$paletteindexbyte = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
$paletteindexes[] = ($paletteindexbyte & 0xF0) >> 4;
$paletteindexes[] = ($paletteindexbyte & 0x0F);
}
while (($pixeldataoffset % 2) != 0) {
// Each run must be aligned on a word boundary.
$pixeldataoffset++;
}
foreach ($paletteindexes as $paletteindex) {
$col = $pixelcounter % $thisfile_bmp_header_raw['width'];
$row = $thisfile_bmp_header_raw['height'] - 1 - (($pixelcounter - $col) / $thisfile_bmp_header_raw['width']);
$thisfile_bmp['data'][$row][$col] = $thisfile_bmp['palette'][$paletteindex];
$pixelcounter++;
}
break;
}
} else {
// encoded mode - the first byte of the pair contains the number of pixels to be
// drawn using the color indexes in the second byte. The second byte contains two
// color indexes, one in its high-order 4 bits and one in its low-order 4 bits.
// The first of the pixels is drawn using the color specified by the high-order
// 4 bits, the second is drawn using the color in the low-order 4 bits, the third
// is drawn using the color in the high-order 4 bits, and so on, until all the
// pixels specified by the first byte have been drawn.
$paletteindexes[0] = ($secondbyte & 0xF0) >> 4;
$paletteindexes[1] = ($secondbyte & 0x0F);
for ($i = 0; $i < $firstbyte; $i++) {
$col = $pixelcounter % $thisfile_bmp_header_raw['width'];
$row = $thisfile_bmp_header_raw['height'] - 1 - (($pixelcounter - $col) / $thisfile_bmp_header_raw['width']);
$thisfile_bmp['data'][$row][$col] = $thisfile_bmp['palette'][$paletteindexes[($i % 2)]];
$pixelcounter++;
}
}
}
break;
default:
$this->error('Unknown bits-per-pixel value ('.$thisfile_bmp_header_raw['bits_per_pixel'].') - cannot read pixel data');
break;
}
break;
case 3: // BI_BITFIELDS
switch ($thisfile_bmp_header_raw['bits_per_pixel']) {
case 16:
case 32:
$redshift = 0;
$greenshift = 0;
$blueshift = 0;
while ((($thisfile_bmp_header_raw['red_mask'] >> $redshift) & 0x01) == 0) {
$redshift++;
}
while ((($thisfile_bmp_header_raw['green_mask'] >> $greenshift) & 0x01) == 0) {
$greenshift++;
}
while ((($thisfile_bmp_header_raw['blue_mask'] >> $blueshift) & 0x01) == 0) {
$blueshift++;
}
for ($row = ($thisfile_bmp_header_raw['height'] - 1); $row >= 0; $row--) {
for ($col = 0; $col < $thisfile_bmp_header_raw['width']; $col++) {
$pixelvalue = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset, $thisfile_bmp_header_raw['bits_per_pixel'] / 8));
$pixeldataoffset += $thisfile_bmp_header_raw['bits_per_pixel'] / 8;
$red = intval(round(((($pixelvalue & $thisfile_bmp_header_raw['red_mask']) >> $redshift) / ($thisfile_bmp_header_raw['red_mask'] >> $redshift)) * 255));
$green = intval(round(((($pixelvalue & $thisfile_bmp_header_raw['green_mask']) >> $greenshift) / ($thisfile_bmp_header_raw['green_mask'] >> $greenshift)) * 255));
$blue = intval(round(((($pixelvalue & $thisfile_bmp_header_raw['blue_mask']) >> $blueshift) / ($thisfile_bmp_header_raw['blue_mask'] >> $blueshift)) * 255));
$thisfile_bmp['data'][$row][$col] = (($red << 16) | ($green << 8) | ($blue));
}
while (($pixeldataoffset % 4) != 0) {
// lines are padded to nearest DWORD
$pixeldataoffset++;
}
}
break;
default:
$this->error('Unknown bits-per-pixel value ('.$thisfile_bmp_header_raw['bits_per_pixel'].') - cannot read pixel data');
break;
}
break;
default: // unhandled compression type
$this->error('Unknown/unhandled compression type value ('.$thisfile_bmp_header_raw['compression'].') - cannot decompress pixel data');
break;
}
}
return true;
}
/**
* @param array $BMPinfo
*
* @return bool
*/
public function PlotBMP(&$BMPinfo) {
$starttime = time();
if (!isset($BMPinfo['bmp']['data']) || !is_array($BMPinfo['bmp']['data'])) {
echo 'ERROR: no pixel data<BR>';
return false;
}
set_time_limit(intval(round($BMPinfo['resolution_x'] * $BMPinfo['resolution_y'] / 10000)));
if ($im = imagecreatetruecolor($BMPinfo['resolution_x'], $BMPinfo['resolution_y'])) {
for ($row = 0; $row < $BMPinfo['resolution_y']; $row++) {
for ($col = 0; $col < $BMPinfo['resolution_x']; $col++) {
if (isset($BMPinfo['bmp']['data'][$row][$col])) {
$red = ($BMPinfo['bmp']['data'][$row][$col] & 0x00FF0000) >> 16;
$green = ($BMPinfo['bmp']['data'][$row][$col] & 0x0000FF00) >> 8;
$blue = ($BMPinfo['bmp']['data'][$row][$col] & 0x000000FF);
$pixelcolor = imagecolorallocate($im, $red, $green, $blue);
imagesetpixel($im, $col, $row, $pixelcolor);
} else {
//echo 'ERROR: no data for pixel '.$row.' x '.$col.'<BR>';
//return false;
}
}
}
if (headers_sent()) {
echo 'plotted '.($BMPinfo['resolution_x'] * $BMPinfo['resolution_y']).' pixels in '.(time() - $starttime).' seconds<BR>';
imagedestroy($im);
exit;
} else {
header('Content-type: image/png');
imagepng($im);
imagedestroy($im);
return true;
}
}
return false;
}
/**
* @param int $compressionid
*
* @return string
*/
public function BMPcompressionWindowsLookup($compressionid) {
static $BMPcompressionWindowsLookup = array(
0 => 'BI_RGB',
1 => 'BI_RLE8',
2 => 'BI_RLE4',
3 => 'BI_BITFIELDS',
4 => 'BI_JPEG',
5 => 'BI_PNG'
);
return (isset($BMPcompressionWindowsLookup[$compressionid]) ? $BMPcompressionWindowsLookup[$compressionid] : 'invalid');
}
/**
* @param int $compressionid
*
* @return string
*/
public function BMPcompressionOS2Lookup($compressionid) {
static $BMPcompressionOS2Lookup = array(
0 => 'BI_RGB',
1 => 'BI_RLE8',
2 => 'BI_RLE4',
3 => 'Huffman 1D',
4 => 'BI_RLE24',
);
return (isset($BMPcompressionOS2Lookup[$compressionid]) ? $BMPcompressionOS2Lookup[$compressionid] : '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.archive.efax.php //
// module for analyzing eFax files //
// dependencies: NONE //
// ///
/////////////////////////////////////////////////////////////////
if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
exit;
}
class getid3_efax extends getid3_handler
{
/**
* @return bool
*/
public function Analyze() {
$info = &$this->getid3->info;
$this->fseek($info['avdataoffset']);
$efaxheader = $this->fread(1024);
$info['efax']['header']['magic'] = substr($efaxheader, 0, 2);
if ($info['efax']['header']['magic'] != "\xDC\xFE") {
$this->error('Invalid eFax byte order identifier (expecting DC FE, found '.getid3_lib::PrintHexBytes($info['efax']['header']['magic']).') at offset '.$info['avdataoffset']);
return false;
}
$info['fileformat'] = 'efax';
$info['efax']['header']['filesize'] = getid3_lib::LittleEndian2Int(substr($efaxheader, 2, 4));
if ($info['efax']['header']['filesize'] != $info['filesize']) {
$this->error('Probable '.(($info['efax']['header']['filesize'] > $info['filesize']) ? 'truncated' : 'corrupt').' file, expecting '.$info['efax']['header']['filesize'].' bytes, found '.$info['filesize'].' bytes');
}
$info['efax']['header']['software1'] = rtrim(substr($efaxheader, 26, 32), "\x00");
$info['efax']['header']['software2'] = rtrim(substr($efaxheader, 58, 32), "\x00");
$info['efax']['header']['software3'] = rtrim(substr($efaxheader, 90, 32), "\x00");
$info['efax']['header']['pages'] = getid3_lib::LittleEndian2Int(substr($efaxheader, 198, 2));
$info['efax']['header']['data_bytes'] = getid3_lib::LittleEndian2Int(substr($efaxheader, 202, 4));
$this->error('eFax 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.graphic.gif.php //
// module for analyzing GIF Image files //
// dependencies: NONE //
// ///
/////////////////////////////////////////////////////////////////
/**
* @link https://www.w3.org/Graphics/GIF/spec-gif89a.txt
* @link http://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp
*/
if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
exit;
}
class getid3_gif extends getid3_handler
{
/**
* @return bool
*/
public function Analyze() {
$info = &$this->getid3->info;
$info['fileformat'] = 'gif';
$info['video']['dataformat'] = 'gif';
$info['video']['lossless'] = true;
$info['video']['pixel_aspect_ratio'] = (float) 1;
$this->fseek($info['avdataoffset']);
$GIFheader = $this->fread(13);
$offset = 0;
$info['gif']['header']['raw']['identifier'] = substr($GIFheader, $offset, 3);
$offset += 3;
$magic = 'GIF';
if ($info['gif']['header']['raw']['identifier'] != $magic) {
$this->error('Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($info['gif']['header']['raw']['identifier']).'"');
unset($info['fileformat']);
unset($info['gif']);
return false;
}
//if (!$this->getid3->option_extra_info) {
// $this->warning('GIF Extensions and Global Color Table not returned due to !getid3->option_extra_info');
//}
$info['gif']['header']['raw']['version'] = substr($GIFheader, $offset, 3);
$offset += 3;
$info['gif']['header']['raw']['width'] = getid3_lib::LittleEndian2Int(substr($GIFheader, $offset, 2));
$offset += 2;
$info['gif']['header']['raw']['height'] = getid3_lib::LittleEndian2Int(substr($GIFheader, $offset, 2));
$offset += 2;
$info['gif']['header']['raw']['flags'] = getid3_lib::LittleEndian2Int(substr($GIFheader, $offset, 1));
$offset += 1;
$info['gif']['header']['raw']['bg_color_index'] = getid3_lib::LittleEndian2Int(substr($GIFheader, $offset, 1));
$offset += 1;
$info['gif']['header']['raw']['aspect_ratio'] = getid3_lib::LittleEndian2Int(substr($GIFheader, $offset, 1));
$offset += 1;
$info['video']['resolution_x'] = $info['gif']['header']['raw']['width'];
$info['video']['resolution_y'] = $info['gif']['header']['raw']['height'];
$info['gif']['version'] = $info['gif']['header']['raw']['version'];
$info['gif']['header']['flags']['global_color_table'] = (bool) ($info['gif']['header']['raw']['flags'] & 0x80);
if ($info['gif']['header']['raw']['flags'] & 0x80) {
// Number of bits per primary color available to the original image, minus 1
$info['gif']['header']['bits_per_pixel'] = 3 * ((($info['gif']['header']['raw']['flags'] & 0x70) >> 4) + 1);
} else {
$info['gif']['header']['bits_per_pixel'] = 0;
}
$info['gif']['header']['flags']['global_color_sorted'] = (bool) ($info['gif']['header']['raw']['flags'] & 0x40);
if ($info['gif']['header']['flags']['global_color_table']) {
// the number of bytes contained in the Global Color Table. To determine that
// actual size of the color table, raise 2 to [the value of the field + 1]
$info['gif']['header']['global_color_size'] = pow(2, ($info['gif']['header']['raw']['flags'] & 0x07) + 1);
$info['video']['bits_per_sample'] = ($info['gif']['header']['raw']['flags'] & 0x07) + 1;
} else {
$info['gif']['header']['global_color_size'] = 0;
}
if ($info['gif']['header']['raw']['aspect_ratio'] != 0) {
// Aspect Ratio = (Pixel Aspect Ratio + 15) / 64
$info['gif']['header']['aspect_ratio'] = ($info['gif']['header']['raw']['aspect_ratio'] + 15) / 64;
}
if ($info['gif']['header']['flags']['global_color_table']) {
$GIFcolorTable = $this->fread(3 * $info['gif']['header']['global_color_size']);
if ($this->getid3->option_extra_info) {
$offset = 0;
for ($i = 0; $i < $info['gif']['header']['global_color_size']; $i++) {
$red = getid3_lib::LittleEndian2Int(substr($GIFcolorTable, $offset++, 1));
$green = getid3_lib::LittleEndian2Int(substr($GIFcolorTable, $offset++, 1));
$blue = getid3_lib::LittleEndian2Int(substr($GIFcolorTable, $offset++, 1));
$info['gif']['global_color_table'][$i] = (($red << 16) | ($green << 8) | ($blue));
$info['gif']['global_color_table_rgb'][$i] = sprintf('%02X%02X%02X', $red, $green, $blue);
}
}
}
// Image Descriptor
$info['gif']['animation']['animated'] = false;
while (!feof($this->getid3->fp)) {
$NextBlockTest = $this->fread(1);
switch ($NextBlockTest) {
/*
case ',': // ',' - Image separator character
$ImageDescriptorData = $NextBlockTest.$this->fread(9);
$ImageDescriptor = array();
$ImageDescriptor['image_left'] = getid3_lib::LittleEndian2Int(substr($ImageDescriptorData, 1, 2));
$ImageDescriptor['image_top'] = getid3_lib::LittleEndian2Int(substr($ImageDescriptorData, 3, 2));
$ImageDescriptor['image_width'] = getid3_lib::LittleEndian2Int(substr($ImageDescriptorData, 5, 2));
$ImageDescriptor['image_height'] = getid3_lib::LittleEndian2Int(substr($ImageDescriptorData, 7, 2));
$ImageDescriptor['flags_raw'] = getid3_lib::LittleEndian2Int(substr($ImageDescriptorData, 9, 1));
$ImageDescriptor['flags']['use_local_color_map'] = (bool) ($ImageDescriptor['flags_raw'] & 0x80);
$ImageDescriptor['flags']['image_interlaced'] = (bool) ($ImageDescriptor['flags_raw'] & 0x40);
$info['gif']['image_descriptor'][] = $ImageDescriptor;
if ($ImageDescriptor['flags']['use_local_color_map']) {
$this->warning('This version of getID3() cannot parse local color maps for GIFs');
return true;
}
$RasterData = array();
$RasterData['code_size'] = getid3_lib::LittleEndian2Int($this->fread(1));
$RasterData['block_byte_count'] = getid3_lib::LittleEndian2Int($this->fread(1));
$info['gif']['raster_data'][count($info['gif']['image_descriptor']) - 1] = $RasterData;
$CurrentCodeSize = $RasterData['code_size'] + 1;
for ($i = 0; $i < pow(2, $RasterData['code_size']); $i++) {
$DefaultDataLookupTable[$i] = chr($i);
}
$DefaultDataLookupTable[pow(2, $RasterData['code_size']) + 0] = ''; // Clear Code
$DefaultDataLookupTable[pow(2, $RasterData['code_size']) + 1] = ''; // End Of Image Code
$NextValue = $this->GetLSBits($CurrentCodeSize);
echo 'Clear Code: '.$NextValue.'<BR>';
$NextValue = $this->GetLSBits($CurrentCodeSize);
echo 'First Color: '.$NextValue.'<BR>';
$Prefix = $NextValue;
$i = 0;
while ($i++ < 20) {
$NextValue = $this->GetLSBits($CurrentCodeSize);
echo $NextValue.'<br>';
}
echo 'escaping<br>';
return true;
break;
*/
case '!':
// GIF Extension Block
$ExtensionBlockData = $NextBlockTest.$this->fread(2);
$ExtensionBlock = array();
$ExtensionBlock['function_code'] = getid3_lib::LittleEndian2Int(substr($ExtensionBlockData, 1, 1));
$ExtensionBlock['byte_length'] = getid3_lib::LittleEndian2Int(substr($ExtensionBlockData, 2, 1));
$ExtensionBlock['data'] = (($ExtensionBlock['byte_length'] > 0) ? $this->fread($ExtensionBlock['byte_length']) : null);
if (substr($ExtensionBlock['data'], 0, 11) == 'NETSCAPE2.0') { // Netscape Application Block (NAB)
$ExtensionBlock['data'] .= $this->fread(4);
if (substr($ExtensionBlock['data'], 11, 2) == "\x03\x01") {
$info['gif']['animation']['animated'] = true;
$info['gif']['animation']['loop_count'] = getid3_lib::LittleEndian2Int(substr($ExtensionBlock['data'], 13, 2));
} else {
$this->warning('Expecting 03 01 at offset '.($this->ftell() - 4).', found "'.getid3_lib::PrintHexBytes(substr($ExtensionBlock['data'], 11, 2)).'"');
}
}
if ($this->getid3->option_extra_info) {
$info['gif']['extension_blocks'][] = $ExtensionBlock;
}
break;
case ';':
$info['gif']['terminator_offset'] = $this->ftell() - 1;
// GIF Terminator
break;
default:
break;
}
}
return true;
}
/**
* @param int $bits
*
* @return float|int
*/
public function GetLSBits($bits) {
static $bitbuffer = '';
while (strlen($bitbuffer) < $bits) {
$bitbuffer = str_pad(decbin(ord($this->fread(1))), 8, '0', STR_PAD_LEFT).$bitbuffer;
}
$value = bindec(substr($bitbuffer, 0 - $bits));
$bitbuffer = substr($bitbuffer, 0, 0 - $bits);
return $value;
}
}
<?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.graphic.jpg.php //
// module for analyzing JPEG Image files //
// dependencies: PHP compiled with --enable-exif (optional) //
// module.tag.xmp.php (optional) //
// ///
/////////////////////////////////////////////////////////////////
if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
exit;
}
class getid3_jpg extends getid3_handler
{
/**
* @return bool
*/
public function Analyze() {
$info = &$this->getid3->info;
$info['fileformat'] = 'jpg';
$info['video']['dataformat'] = 'jpg';
$info['video']['lossless'] = false;
$info['video']['bits_per_sample'] = 24;
$info['video']['pixel_aspect_ratio'] = (float) 1;
$this->fseek($info['avdataoffset']);
$imageinfo = array();
//list($width, $height, $type) = getid3_lib::GetDataImageSize($this->fread($info['filesize']), $imageinfo);
list($width, $height, $type) = getimagesize($info['filenamepath'], $imageinfo); // https://www.getid3.org/phpBB3/viewtopic.php?t=1474
if (isset($imageinfo['APP13'])) {
// http://php.net/iptcparse
// http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/IPTC.html
$iptc_parsed = iptcparse($imageinfo['APP13']);
if (is_array($iptc_parsed)) {
foreach ($iptc_parsed as $iptc_key_raw => $iptc_values) {
list($iptc_record, $iptc_tagkey) = explode('#', $iptc_key_raw);
$iptc_tagkey = intval(ltrim($iptc_tagkey, '0'));
foreach ($iptc_values as $key => $value) {
$IPTCrecordName = $this->IPTCrecordName($iptc_record);
$IPTCrecordTagName = $this->IPTCrecordTagName($iptc_record, $iptc_tagkey);
if (isset($info['iptc']['comments'][$IPTCrecordName][$IPTCrecordTagName])) {
$info['iptc']['comments'][$IPTCrecordName][$IPTCrecordTagName][] = $value;
} else {
$info['iptc']['comments'][$IPTCrecordName][$IPTCrecordTagName] = array($value);
}
}
}
}
}
$returnOK = false;
switch ($type) {
case IMG_JPG:
$info['video']['resolution_x'] = $width;
$info['video']['resolution_y'] = $height;
if (isset($imageinfo['APP1'])) {
if (function_exists('exif_read_data')) {
if (substr($imageinfo['APP1'], 0, 4) == 'Exif') {
//$this->warning('known issue: https://bugs.php.net/bug.php?id=62523');
//return false;
set_error_handler(function($errno, $errstr, $errfile, $errline, array $errcontext) {
if (!(error_reporting() & $errno)) {
// error is not specified in the error_reporting setting, so we ignore it
return false;
}
$errcontext['info']['warning'][] = 'Error parsing EXIF data ('.$errstr.')';
});
$info['jpg']['exif'] = exif_read_data($info['filenamepath'], null, true, false);
restore_error_handler();
} else {
$this->warning('exif_read_data() cannot parse non-EXIF data in APP1 (expected "Exif", found "'.substr($imageinfo['APP1'], 0, 4).'")');
}
} else {
$this->warning('EXIF parsing only available when '.(GETID3_OS_ISWINDOWS ? 'php_exif.dll enabled' : 'compiled with --enable-exif'));
}
}
$returnOK = true;
break;
default:
break;
}
$cast_as_appropriate_keys = array('EXIF', 'IFD0', 'THUMBNAIL');
foreach ($cast_as_appropriate_keys as $exif_key) {
if (isset($info['jpg']['exif'][$exif_key])) {
foreach ($info['jpg']['exif'][$exif_key] as $key => $value) {
$info['jpg']['exif'][$exif_key][$key] = $this->CastAsAppropriate($value);
}
}
}
if (isset($info['jpg']['exif']['GPS'])) {
if (isset($info['jpg']['exif']['GPS']['GPSVersion'])) {
$version_subparts = array();
for ($i = 0; $i < 4; $i++) {
$version_subparts[$i] = ord(substr($info['jpg']['exif']['GPS']['GPSVersion'], $i, 1));
}
$info['jpg']['exif']['GPS']['computed']['version'] = 'v'.implode('.', $version_subparts);
}
if (isset($info['jpg']['exif']['GPS']['GPSDateStamp'])) {
$explodedGPSDateStamp = explode(':', $info['jpg']['exif']['GPS']['GPSDateStamp']);
$computed_time[5] = (isset($explodedGPSDateStamp[0]) ? $explodedGPSDateStamp[0] : '');
$computed_time[3] = (isset($explodedGPSDateStamp[1]) ? $explodedGPSDateStamp[1] : '');
$computed_time[4] = (isset($explodedGPSDateStamp[2]) ? $explodedGPSDateStamp[2] : '');
$computed_time = array(0=>0, 1=>0, 2=>0, 3=>0, 4=>0, 5=>0);
if (isset($info['jpg']['exif']['GPS']['GPSTimeStamp']) && is_array($info['jpg']['exif']['GPS']['GPSTimeStamp'])) {
foreach ($info['jpg']['exif']['GPS']['GPSTimeStamp'] as $key => $value) {
$computed_time[$key] = getid3_lib::DecimalizeFraction($value);
}
}
$info['jpg']['exif']['GPS']['computed']['timestamp'] = gmmktime($computed_time[0], $computed_time[1], $computed_time[2], $computed_time[3], $computed_time[4], $computed_time[5]);
}
if (isset($info['jpg']['exif']['GPS']['GPSLatitude']) && is_array($info['jpg']['exif']['GPS']['GPSLatitude'])) {
$direction_multiplier = ((isset($info['jpg']['exif']['GPS']['GPSLatitudeRef']) && ($info['jpg']['exif']['GPS']['GPSLatitudeRef'] == 'S')) ? -1 : 1);
$computed_latitude = array();
foreach ($info['jpg']['exif']['GPS']['GPSLatitude'] as $key => $value) {
$computed_latitude[$key] = getid3_lib::DecimalizeFraction($value);
}
$info['jpg']['exif']['GPS']['computed']['latitude'] = $direction_multiplier * ($computed_latitude[0] + ($computed_latitude[1] / 60) + ($computed_latitude[2] / 3600));
}
if (isset($info['jpg']['exif']['GPS']['GPSLongitude']) && is_array($info['jpg']['exif']['GPS']['GPSLongitude'])) {
$direction_multiplier = ((isset($info['jpg']['exif']['GPS']['GPSLongitudeRef']) && ($info['jpg']['exif']['GPS']['GPSLongitudeRef'] == 'W')) ? -1 : 1);
$computed_longitude = array();
foreach ($info['jpg']['exif']['GPS']['GPSLongitude'] as $key => $value) {
$computed_longitude[$key] = getid3_lib::DecimalizeFraction($value);
}
$info['jpg']['exif']['GPS']['computed']['longitude'] = $direction_multiplier * ($computed_longitude[0] + ($computed_longitude[1] / 60) + ($computed_longitude[2] / 3600));
}
if (isset($info['jpg']['exif']['GPS']['GPSAltitudeRef'])) {
$info['jpg']['exif']['GPS']['GPSAltitudeRef'] = ord($info['jpg']['exif']['GPS']['GPSAltitudeRef']); // 0 = above sea level; 1 = below sea level
}
if (isset($info['jpg']['exif']['GPS']['GPSAltitude'])) {
$direction_multiplier = (!empty($info['jpg']['exif']['GPS']['GPSAltitudeRef']) ? -1 : 1); // 0 = above sea level; 1 = below sea level
$info['jpg']['exif']['GPS']['computed']['altitude'] = $direction_multiplier * getid3_lib::DecimalizeFraction($info['jpg']['exif']['GPS']['GPSAltitude']);
}
}
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.xmp.php', __FILE__, true);
if (isset($info['filenamepath'])) {
$image_xmp = new Image_XMP($info['filenamepath']);
$xmp_raw = $image_xmp->getAllTags();
foreach ($xmp_raw as $key => $value) {
if (strpos($key, ':')) {
list($subsection, $tagname) = explode(':', $key);
$info['xmp'][$subsection][$tagname] = $this->CastAsAppropriate($value);
} else {
$this->warning('XMP: expecting "<subsection>:<tagname>", found "'.$key.'"');
}
}
}
if (!$returnOK) {
unset($info['fileformat']);
return false;
}
return true;
}
/**
* @param mixed $value
*
* @return mixed
*/
public function CastAsAppropriate($value) {
if (is_array($value)) {
return $value;
} elseif (preg_match('#^[0-9]+/[0-9]+$#', $value)) {
return getid3_lib::DecimalizeFraction($value);
} elseif (preg_match('#^[0-9]+$#', $value)) {
return getid3_lib::CastAsInt($value);
} elseif (preg_match('#^[0-9\.]+$#', $value)) {
return (float) $value;
}
return $value;
}
/**
* @param int $iptc_record
*
* @return string
*/
public function IPTCrecordName($iptc_record) {
// http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/IPTC.html
static $IPTCrecordName = array();
if (empty($IPTCrecordName)) {
$IPTCrecordName = array(
1 => 'IPTCEnvelope',
2 => 'IPTCApplication',
3 => 'IPTCNewsPhoto',
7 => 'IPTCPreObjectData',
8 => 'IPTCObjectData',
9 => 'IPTCPostObjectData',
);
}
return (isset($IPTCrecordName[$iptc_record]) ? $IPTCrecordName[$iptc_record] : '');
}
/**
* @param int $iptc_record
* @param int $iptc_tagkey
*
* @return string
*/
public function IPTCrecordTagName($iptc_record, $iptc_tagkey) {
// http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/IPTC.html
static $IPTCrecordTagName = array();
if (empty($IPTCrecordTagName)) {
$IPTCrecordTagName = array(
1 => array( // IPTC EnvelopeRecord Tags
0 => 'EnvelopeRecordVersion',
5 => 'Destination',
20 => 'FileFormat',
22 => 'FileVersion',
30 => 'ServiceIdentifier',
40 => 'EnvelopeNumber',
50 => 'ProductID',
60 => 'EnvelopePriority',
70 => 'DateSent',
80 => 'TimeSent',
90 => 'CodedCharacterSet',
100 => 'UniqueObjectName',
120 => 'ARMIdentifier',
122 => 'ARMVersion',
),
2 => array( // IPTC ApplicationRecord Tags
0 => 'ApplicationRecordVersion',
3 => 'ObjectTypeReference',
4 => 'ObjectAttributeReference',
5 => 'ObjectName',
7 => 'EditStatus',
8 => 'EditorialUpdate',
10 => 'Urgency',
12 => 'SubjectReference',
15 => 'Category',
20 => 'SupplementalCategories',
22 => 'FixtureIdentifier',
25 => 'Keywords',
26 => 'ContentLocationCode',
27 => 'ContentLocationName',
30 => 'ReleaseDate',
35 => 'ReleaseTime',
37 => 'ExpirationDate',
38 => 'ExpirationTime',
40 => 'SpecialInstructions',
42 => 'ActionAdvised',
45 => 'ReferenceService',
47 => 'ReferenceDate',
50 => 'ReferenceNumber',
55 => 'DateCreated',
60 => 'TimeCreated',
62 => 'DigitalCreationDate',
63 => 'DigitalCreationTime',
65 => 'OriginatingProgram',
70 => 'ProgramVersion',
75 => 'ObjectCycle',
80 => 'By-line',
85 => 'By-lineTitle',
90 => 'City',
92 => 'Sub-location',
95 => 'Province-State',
100 => 'Country-PrimaryLocationCode',
101 => 'Country-PrimaryLocationName',
103 => 'OriginalTransmissionReference',
105 => 'Headline',
110 => 'Credit',
115 => 'Source',
116 => 'CopyrightNotice',
118 => 'Contact',
120 => 'Caption-Abstract',
121 => 'LocalCaption',
122 => 'Writer-Editor',
125 => 'RasterizedCaption',
130 => 'ImageType',
131 => 'ImageOrientation',
135 => 'LanguageIdentifier',
150 => 'AudioType',
151 => 'AudioSamplingRate',
152 => 'AudioSamplingResolution',
153 => 'AudioDuration',
154 => 'AudioOutcue',
184 => 'JobID',
185 => 'MasterDocumentID',
186 => 'ShortDocumentID',
187 => 'UniqueDocumentID',
188 => 'OwnerID',
200 => 'ObjectPreviewFileFormat',
201 => 'ObjectPreviewFileVersion',
202 => 'ObjectPreviewData',
221 => 'Prefs',
225 => 'ClassifyState',
228 => 'SimilarityIndex',
230 => 'DocumentNotes',
231 => 'DocumentHistory',
232 => 'ExifCameraInfo',
),
3 => array( // IPTC NewsPhoto Tags
0 => 'NewsPhotoVersion',
10 => 'IPTCPictureNumber',
20 => 'IPTCImageWidth',
30 => 'IPTCImageHeight',
40 => 'IPTCPixelWidth',
50 => 'IPTCPixelHeight',
55 => 'SupplementalType',
60 => 'ColorRepresentation',
64 => 'InterchangeColorSpace',
65 => 'ColorSequence',
66 => 'ICC_Profile',
70 => 'ColorCalibrationMatrix',
80 => 'LookupTable',
84 => 'NumIndexEntries',
85 => 'ColorPalette',
86 => 'IPTCBitsPerSample',
90 => 'SampleStructure',
100 => 'ScanningDirection',
102 => 'IPTCImageRotation',
110 => 'DataCompressionMethod',
120 => 'QuantizationMethod',
125 => 'EndPoints',
130 => 'ExcursionTolerance',
135 => 'BitsPerComponent',
140 => 'MaximumDensityRange',
145 => 'GammaCompensatedValue',
),
7 => array( // IPTC PreObjectData Tags
10 => 'SizeMode',
20 => 'MaxSubfileSize',
90 => 'ObjectSizeAnnounced',
95 => 'MaximumObjectSize',
),
8 => array( // IPTC ObjectData Tags
10 => 'SubFile',
),
9 => array( // IPTC PostObjectData Tags
10 => 'ConfirmedObjectSize',
),
);
}
return (isset($IPTCrecordTagName[$iptc_record][$iptc_tagkey]) ? $IPTCrecordTagName[$iptc_record][$iptc_tagkey] : $iptc_tagkey);
}
}
<?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.graphic.pcd.php //
// module for analyzing PhotoCD (PCD) Image files //
// dependencies: NONE //
// ///
/////////////////////////////////////////////////////////////////
if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
exit;
}
class getid3_pcd extends getid3_handler
{
public $ExtractData = 0;
/**
* @return bool
*/
public function Analyze() {
$info = &$this->getid3->info;
$info['fileformat'] = 'pcd';
$info['video']['dataformat'] = 'pcd';
$info['video']['lossless'] = false;
$this->fseek($info['avdataoffset'] + 72);
$PCDflags = $this->fread(1);
$PCDisVertical = ((ord($PCDflags) & 0x01) ? true : false);
if ($PCDisVertical) {
$info['video']['resolution_x'] = 3072;
$info['video']['resolution_y'] = 2048;
} else {
$info['video']['resolution_x'] = 2048;
$info['video']['resolution_y'] = 3072;
}
if ($this->ExtractData > 3) {
$this->error('Cannot extract PSD image data for detail levels above BASE (level-3) because encrypted with Kodak-proprietary compression/encryption.');
return false;
} elseif ($this->ExtractData > 0) {
$PCD_levels[1] = array( 192, 128, 0x02000); // BASE/16
$PCD_levels[2] = array( 384, 256, 0x0B800); // BASE/4
$PCD_levels[3] = array( 768, 512, 0x30000); // BASE
//$PCD_levels[4] = array(1536, 1024, ??); // BASE*4 - encrypted with Kodak-proprietary compression/encryption
//$PCD_levels[5] = array(3072, 2048, ??); // BASE*16 - encrypted with Kodak-proprietary compression/encryption
//$PCD_levels[6] = array(6144, 4096, ??); // BASE*64 - encrypted with Kodak-proprietary compression/encryption; PhotoCD-Pro only
list($PCD_width, $PCD_height, $PCD_dataOffset) = $PCD_levels[3];
$this->fseek($info['avdataoffset'] + $PCD_dataOffset);
for ($y = 0; $y < $PCD_height; $y += 2) {
// The image-data of these subtypes start at the respective offsets of 02000h, 0b800h and 30000h.
// To decode the YcbYr to the more usual RGB-code, three lines of data have to be read, each
// consisting of ‘w’ bytes, where ‘w’ is the width of the image-subtype. The first ‘w’ bytes and
// the first half of the third ‘w’ bytes contain data for the first RGB-line, the second ‘w’ bytes
// and the second half of the third ‘w’ bytes contain data for a second RGB-line.
$PCD_data_Y1 = $this->fread($PCD_width);
$PCD_data_Y2 = $this->fread($PCD_width);
$PCD_data_Cb = $this->fread(intval(round($PCD_width / 2)));
$PCD_data_Cr = $this->fread(intval(round($PCD_width / 2)));
for ($x = 0; $x < $PCD_width; $x++) {
if ($PCDisVertical) {
$info['pcd']['data'][$PCD_width - $x][$y] = $this->YCbCr2RGB(ord($PCD_data_Y1[$x]), ord($PCD_data_Cb[(int) floor($x / 2)]), ord($PCD_data_Cr[(int) floor($x / 2)]));
$info['pcd']['data'][$PCD_width - $x][$y + 1] = $this->YCbCr2RGB(ord($PCD_data_Y2[$x]), ord($PCD_data_Cb[(int) floor($x / 2)]), ord($PCD_data_Cr[(int) floor($x / 2)]));
} else {
$info['pcd']['data'][$y][$x] = $this->YCbCr2RGB(ord($PCD_data_Y1[$x]), ord($PCD_data_Cb[(int) floor($x / 2)]), ord($PCD_data_Cr[(int) floor($x / 2)]));
$info['pcd']['data'][$y + 1][$x] = $this->YCbCr2RGB(ord($PCD_data_Y2[$x]), ord($PCD_data_Cb[(int) floor($x / 2)]), ord($PCD_data_Cr[(int) floor($x / 2)]));
}
}
}
// Example for plotting extracted data
//getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.ac3.php', __FILE__, true);
//if ($PCDisVertical) {
// $BMPinfo['resolution_x'] = $PCD_height;
// $BMPinfo['resolution_y'] = $PCD_width;
//} else {
// $BMPinfo['resolution_x'] = $PCD_width;
// $BMPinfo['resolution_y'] = $PCD_height;
//}
//$BMPinfo['bmp']['data'] = $info['pcd']['data'];
//getid3_bmp::PlotBMP($BMPinfo);
//exit;
}
return false;
}
/**
* @param int $Y
* @param int $Cb
* @param int $Cr
*
* @return int
*/
public function YCbCr2RGB($Y, $Cb, $Cr) {
static $YCbCr_constants = array();
if (empty($YCbCr_constants)) {
$YCbCr_constants['red']['Y'] = 0.0054980 * 256;
$YCbCr_constants['red']['Cb'] = 0.0000000 * 256;
$YCbCr_constants['red']['Cr'] = 0.0051681 * 256;
$YCbCr_constants['green']['Y'] = 0.0054980 * 256;
$YCbCr_constants['green']['Cb'] = -0.0015446 * 256;
$YCbCr_constants['green']['Cr'] = -0.0026325 * 256;
$YCbCr_constants['blue']['Y'] = 0.0054980 * 256;
$YCbCr_constants['blue']['Cb'] = 0.0079533 * 256;
$YCbCr_constants['blue']['Cr'] = 0.0000000 * 256;
}
$RGBcolor = array('red'=>0, 'green'=>0, 'blue'=>0);
foreach ($RGBcolor as $rgbname => $dummy) {
$RGBcolor[$rgbname] = max(0,
min(255,
intval(
round(
($YCbCr_constants[$rgbname]['Y'] * $Y) +
($YCbCr_constants[$rgbname]['Cb'] * ($Cb - 156)) +
($YCbCr_constants[$rgbname]['Cr'] * ($Cr - 137))
)
)
)
);
}
return (($RGBcolor['red'] * 65536) + ($RGBcolor['green'] * 256) + $RGBcolor['blue']);
}
}
<?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.graphic.png.php //
// module for analyzing PNG Image files //
// dependencies: NONE //
// ///
/////////////////////////////////////////////////////////////////
if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
exit;
}
class getid3_png extends getid3_handler
{
/**
* If data chunk is larger than this do not read it completely (getID3 only needs the first
* few dozen bytes for parsing).
*
* @var int
*/
public $max_data_bytes = 10000000;
/**
* @return bool
*/
public function Analyze() {
$info = &$this->getid3->info;
// shortcut
$info['png'] = array();
$thisfile_png = &$info['png'];
$info['fileformat'] = 'png';
$info['video']['dataformat'] = 'png';
$info['video']['lossless'] = false;
$this->fseek($info['avdataoffset']);
$PNGfiledata = $this->fread($this->getid3->fread_buffer_size());
$offset = 0;
$PNGidentifier = substr($PNGfiledata, $offset, 8); // $89 $50 $4E $47 $0D $0A $1A $0A
$offset += 8;
if ($PNGidentifier != "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A") {
$this->error('First 8 bytes of file ('.getid3_lib::PrintHexBytes($PNGidentifier).') did not match expected PNG identifier');
unset($info['fileformat']);
return false;
}
while ((($this->ftell() - (strlen($PNGfiledata) - $offset)) < $info['filesize'])) {
$chunk['data_length'] = getid3_lib::BigEndian2Int(substr($PNGfiledata, $offset, 4));
if ($chunk['data_length'] === false) {
$this->error('Failed to read data_length at offset '.$offset);
return false;
}
$offset += 4;
$truncated_data = false;
while (((strlen($PNGfiledata) - $offset) < ($chunk['data_length'] + 4)) && ($this->ftell() < $info['filesize'])) {
if (strlen($PNGfiledata) < $this->max_data_bytes) {
$PNGfiledata .= $this->fread($this->getid3->fread_buffer_size());
} else {
$this->warning('At offset '.$offset.' chunk "'.substr($PNGfiledata, $offset, 4).'" exceeded max_data_bytes value of '.$this->max_data_bytes.', data chunk will be truncated at '.(strlen($PNGfiledata) - 8).' bytes');
break;
}
}
$chunk['type_text'] = substr($PNGfiledata, $offset, 4);
$offset += 4;
$chunk['type_raw'] = getid3_lib::BigEndian2Int($chunk['type_text']);
$chunk['data'] = substr($PNGfiledata, $offset, $chunk['data_length']);
$offset += $chunk['data_length'];
$chunk['crc'] = getid3_lib::BigEndian2Int(substr($PNGfiledata, $offset, 4));
$offset += 4;
$chunk['flags']['ancilliary'] = (bool) ($chunk['type_raw'] & 0x20000000);
$chunk['flags']['private'] = (bool) ($chunk['type_raw'] & 0x00200000);
$chunk['flags']['reserved'] = (bool) ($chunk['type_raw'] & 0x00002000);
$chunk['flags']['safe_to_copy'] = (bool) ($chunk['type_raw'] & 0x00000020);
// shortcut
$thisfile_png[$chunk['type_text']] = array();
$thisfile_png_chunk_type_text = &$thisfile_png[$chunk['type_text']];
switch ($chunk['type_text']) {
case 'IHDR': // Image Header
$thisfile_png_chunk_type_text['header'] = $chunk;
$thisfile_png_chunk_type_text['width'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 0, 4));
$thisfile_png_chunk_type_text['height'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 4, 4));
$thisfile_png_chunk_type_text['raw']['bit_depth'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 8, 1));
$thisfile_png_chunk_type_text['raw']['color_type'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 9, 1));
$thisfile_png_chunk_type_text['raw']['compression_method'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 10, 1));
$thisfile_png_chunk_type_text['raw']['filter_method'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 11, 1));
$thisfile_png_chunk_type_text['raw']['interlace_method'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 12, 1));
$thisfile_png_chunk_type_text['compression_method_text'] = $this->PNGcompressionMethodLookup($thisfile_png_chunk_type_text['raw']['compression_method']);
$thisfile_png_chunk_type_text['color_type']['palette'] = (bool) ($thisfile_png_chunk_type_text['raw']['color_type'] & 0x01);
$thisfile_png_chunk_type_text['color_type']['true_color'] = (bool) ($thisfile_png_chunk_type_text['raw']['color_type'] & 0x02);
$thisfile_png_chunk_type_text['color_type']['alpha'] = (bool) ($thisfile_png_chunk_type_text['raw']['color_type'] & 0x04);
$info['video']['resolution_x'] = $thisfile_png_chunk_type_text['width'];
$info['video']['resolution_y'] = $thisfile_png_chunk_type_text['height'];
$info['video']['bits_per_sample'] = $this->IHDRcalculateBitsPerSample($thisfile_png_chunk_type_text['raw']['color_type'], $thisfile_png_chunk_type_text['raw']['bit_depth']);
break;
case 'PLTE': // Palette
$thisfile_png_chunk_type_text['header'] = $chunk;
$paletteoffset = 0;
for ($i = 0; $i <= 255; $i++) {
//$thisfile_png_chunk_type_text['red'][$i] = getid3_lib::BigEndian2Int(substr($chunk['data'], $paletteoffset++, 1));
//$thisfile_png_chunk_type_text['green'][$i] = getid3_lib::BigEndian2Int(substr($chunk['data'], $paletteoffset++, 1));
//$thisfile_png_chunk_type_text['blue'][$i] = getid3_lib::BigEndian2Int(substr($chunk['data'], $paletteoffset++, 1));
$red = getid3_lib::BigEndian2Int(substr($chunk['data'], $paletteoffset++, 1));
$green = getid3_lib::BigEndian2Int(substr($chunk['data'], $paletteoffset++, 1));
$blue = getid3_lib::BigEndian2Int(substr($chunk['data'], $paletteoffset++, 1));
$thisfile_png_chunk_type_text[$i] = (($red << 16) | ($green << 8) | ($blue));
}
break;
case 'tRNS': // Transparency
$thisfile_png_chunk_type_text['header'] = $chunk;
switch ($thisfile_png['IHDR']['raw']['color_type']) {
case 0:
$thisfile_png_chunk_type_text['transparent_color_gray'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 0, 2));
break;
case 2:
$thisfile_png_chunk_type_text['transparent_color_red'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 0, 2));
$thisfile_png_chunk_type_text['transparent_color_green'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 2, 2));
$thisfile_png_chunk_type_text['transparent_color_blue'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 4, 2));
break;
case 3:
for ($i = 0; $i < strlen($chunk['data']); $i++) {
$thisfile_png_chunk_type_text['palette_opacity'][$i] = getid3_lib::BigEndian2Int(substr($chunk['data'], $i, 1));
}
break;
case 4:
case 6:
$this->error('Invalid color_type in tRNS chunk: '.$thisfile_png['IHDR']['raw']['color_type']);
break;
default:
$this->warning('Unhandled color_type in tRNS chunk: '.$thisfile_png['IHDR']['raw']['color_type']);
break;
}
break;
case 'gAMA': // Image Gamma
$thisfile_png_chunk_type_text['header'] = $chunk;
$thisfile_png_chunk_type_text['gamma'] = getid3_lib::BigEndian2Int($chunk['data']) / 100000;
break;
case 'cHRM': // Primary Chromaticities
$thisfile_png_chunk_type_text['header'] = $chunk;
$thisfile_png_chunk_type_text['white_x'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 0, 4)) / 100000;
$thisfile_png_chunk_type_text['white_y'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 4, 4)) / 100000;
$thisfile_png_chunk_type_text['red_y'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 8, 4)) / 100000;
$thisfile_png_chunk_type_text['red_y'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 12, 4)) / 100000;
$thisfile_png_chunk_type_text['green_y'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 16, 4)) / 100000;
$thisfile_png_chunk_type_text['green_y'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 20, 4)) / 100000;
$thisfile_png_chunk_type_text['blue_y'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 24, 4)) / 100000;
$thisfile_png_chunk_type_text['blue_y'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 28, 4)) / 100000;
break;
case 'sRGB': // Standard RGB Color Space
$thisfile_png_chunk_type_text['header'] = $chunk;
$thisfile_png_chunk_type_text['reindering_intent'] = getid3_lib::BigEndian2Int($chunk['data']);
$thisfile_png_chunk_type_text['reindering_intent_text'] = $this->PNGsRGBintentLookup($thisfile_png_chunk_type_text['reindering_intent']);
break;
case 'iCCP': // Embedded ICC Profile
$thisfile_png_chunk_type_text['header'] = $chunk;
list($profilename, $compressiondata) = explode("\x00", $chunk['data'], 2);
$thisfile_png_chunk_type_text['profile_name'] = $profilename;
$thisfile_png_chunk_type_text['compression_method'] = getid3_lib::BigEndian2Int(substr($compressiondata, 0, 1));
$thisfile_png_chunk_type_text['compression_profile'] = substr($compressiondata, 1);
$thisfile_png_chunk_type_text['compression_method_text'] = $this->PNGcompressionMethodLookup($thisfile_png_chunk_type_text['compression_method']);
break;
case 'tEXt': // Textual Data
$thisfile_png_chunk_type_text['header'] = $chunk;
list($keyword, $text) = explode("\x00", $chunk['data'], 2);
$thisfile_png_chunk_type_text['keyword'] = $keyword;
$thisfile_png_chunk_type_text['text'] = $text;
$thisfile_png['comments'][$thisfile_png_chunk_type_text['keyword']][] = $thisfile_png_chunk_type_text['text'];
break;
case 'zTXt': // Compressed Textual Data
$thisfile_png_chunk_type_text['header'] = $chunk;
list($keyword, $otherdata) = explode("\x00", $chunk['data'], 2);
$thisfile_png_chunk_type_text['keyword'] = $keyword;
$thisfile_png_chunk_type_text['compression_method'] = getid3_lib::BigEndian2Int(substr($otherdata, 0, 1));
$thisfile_png_chunk_type_text['compressed_text'] = substr($otherdata, 1);
$thisfile_png_chunk_type_text['compression_method_text'] = $this->PNGcompressionMethodLookup($thisfile_png_chunk_type_text['compression_method']);
switch ($thisfile_png_chunk_type_text['compression_method']) {
case 0:
$thisfile_png_chunk_type_text['text'] = gzuncompress($thisfile_png_chunk_type_text['compressed_text']);
break;
default:
// unknown compression method
break;
}
if (isset($thisfile_png_chunk_type_text['text'])) {
$thisfile_png['comments'][$thisfile_png_chunk_type_text['keyword']][] = $thisfile_png_chunk_type_text['text'];
}
break;
case 'iTXt': // International Textual Data
$thisfile_png_chunk_type_text['header'] = $chunk;
list($keyword, $otherdata) = explode("\x00", $chunk['data'], 2);
$thisfile_png_chunk_type_text['keyword'] = $keyword;
$thisfile_png_chunk_type_text['compression'] = (bool) getid3_lib::BigEndian2Int(substr($otherdata, 0, 1));
$thisfile_png_chunk_type_text['compression_method'] = getid3_lib::BigEndian2Int(substr($otherdata, 1, 1));
$thisfile_png_chunk_type_text['compression_method_text'] = $this->PNGcompressionMethodLookup($thisfile_png_chunk_type_text['compression_method']);
list($languagetag, $translatedkeyword, $text) = explode("\x00", substr($otherdata, 2), 3);
$thisfile_png_chunk_type_text['language_tag'] = $languagetag;
$thisfile_png_chunk_type_text['translated_keyword'] = $translatedkeyword;
if ($thisfile_png_chunk_type_text['compression']) {
switch ($thisfile_png_chunk_type_text['compression_method']) {
case 0:
$thisfile_png_chunk_type_text['text'] = gzuncompress($text);
break;
default:
// unknown compression method
break;
}
} else {
$thisfile_png_chunk_type_text['text'] = $text;
}
if (isset($thisfile_png_chunk_type_text['text'])) {
$thisfile_png['comments'][$thisfile_png_chunk_type_text['keyword']][] = $thisfile_png_chunk_type_text['text'];
}
break;
case 'bKGD': // Background Color
$thisfile_png_chunk_type_text['header'] = $chunk;
switch ($thisfile_png['IHDR']['raw']['color_type']) {
case 0:
case 4:
$thisfile_png_chunk_type_text['background_gray'] = getid3_lib::BigEndian2Int($chunk['data']);
break;
case 2:
case 6:
$thisfile_png_chunk_type_text['background_red'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 0 * $thisfile_png['IHDR']['raw']['bit_depth'], $thisfile_png['IHDR']['raw']['bit_depth']));
$thisfile_png_chunk_type_text['background_green'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 1 * $thisfile_png['IHDR']['raw']['bit_depth'], $thisfile_png['IHDR']['raw']['bit_depth']));
$thisfile_png_chunk_type_text['background_blue'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 2 * $thisfile_png['IHDR']['raw']['bit_depth'], $thisfile_png['IHDR']['raw']['bit_depth']));
break;
case 3:
$thisfile_png_chunk_type_text['background_index'] = getid3_lib::BigEndian2Int($chunk['data']);
break;
default:
break;
}
break;
case 'pHYs': // Physical Pixel Dimensions
$thisfile_png_chunk_type_text['header'] = $chunk;
$thisfile_png_chunk_type_text['pixels_per_unit_x'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 0, 4));
$thisfile_png_chunk_type_text['pixels_per_unit_y'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 4, 4));
$thisfile_png_chunk_type_text['unit_specifier'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 8, 1));
$thisfile_png_chunk_type_text['unit'] = $this->PNGpHYsUnitLookup($thisfile_png_chunk_type_text['unit_specifier']);
break;
case 'sBIT': // Significant Bits
$thisfile_png_chunk_type_text['header'] = $chunk;
switch ($thisfile_png['IHDR']['raw']['color_type']) {
case 0:
$thisfile_png_chunk_type_text['significant_bits_gray'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 0, 1));
break;
case 2:
case 3:
$thisfile_png_chunk_type_text['significant_bits_red'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 0, 1));
$thisfile_png_chunk_type_text['significant_bits_green'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 1, 1));
$thisfile_png_chunk_type_text['significant_bits_blue'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 2, 1));
break;
case 4:
$thisfile_png_chunk_type_text['significant_bits_gray'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 0, 1));
$thisfile_png_chunk_type_text['significant_bits_alpha'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 1, 1));
break;
case 6:
$thisfile_png_chunk_type_text['significant_bits_red'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 0, 1));
$thisfile_png_chunk_type_text['significant_bits_green'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 1, 1));
$thisfile_png_chunk_type_text['significant_bits_blue'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 2, 1));
$thisfile_png_chunk_type_text['significant_bits_alpha'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 3, 1));
break;
default:
break;
}
break;
case 'sPLT': // Suggested Palette
$thisfile_png_chunk_type_text['header'] = $chunk;
list($palettename, $otherdata) = explode("\x00", $chunk['data'], 2);
$thisfile_png_chunk_type_text['palette_name'] = $palettename;
$sPLToffset = 0;
$thisfile_png_chunk_type_text['sample_depth_bits'] = getid3_lib::BigEndian2Int(substr($otherdata, $sPLToffset, 1));
$sPLToffset += 1;
$thisfile_png_chunk_type_text['sample_depth_bytes'] = $thisfile_png_chunk_type_text['sample_depth_bits'] / 8;
$paletteCounter = 0;
while ($sPLToffset < strlen($otherdata)) {
$thisfile_png_chunk_type_text['red'][$paletteCounter] = getid3_lib::BigEndian2Int(substr($otherdata, $sPLToffset, $thisfile_png_chunk_type_text['sample_depth_bytes']));
$sPLToffset += $thisfile_png_chunk_type_text['sample_depth_bytes'];
$thisfile_png_chunk_type_text['green'][$paletteCounter] = getid3_lib::BigEndian2Int(substr($otherdata, $sPLToffset, $thisfile_png_chunk_type_text['sample_depth_bytes']));
$sPLToffset += $thisfile_png_chunk_type_text['sample_depth_bytes'];
$thisfile_png_chunk_type_text['blue'][$paletteCounter] = getid3_lib::BigEndian2Int(substr($otherdata, $sPLToffset, $thisfile_png_chunk_type_text['sample_depth_bytes']));
$sPLToffset += $thisfile_png_chunk_type_text['sample_depth_bytes'];
$thisfile_png_chunk_type_text['alpha'][$paletteCounter] = getid3_lib::BigEndian2Int(substr($otherdata, $sPLToffset, $thisfile_png_chunk_type_text['sample_depth_bytes']));
$sPLToffset += $thisfile_png_chunk_type_text['sample_depth_bytes'];
$thisfile_png_chunk_type_text['frequency'][$paletteCounter] = getid3_lib::BigEndian2Int(substr($otherdata, $sPLToffset, 2));
$sPLToffset += 2;
$paletteCounter++;
}
break;
case 'hIST': // Palette Histogram
$thisfile_png_chunk_type_text['header'] = $chunk;
$hISTcounter = 0;
while ($hISTcounter < strlen($chunk['data'])) {
$thisfile_png_chunk_type_text[$hISTcounter] = getid3_lib::BigEndian2Int(substr($chunk['data'], $hISTcounter / 2, 2));
$hISTcounter += 2;
}
break;
case 'tIME': // Image Last-Modification Time
$thisfile_png_chunk_type_text['header'] = $chunk;
$thisfile_png_chunk_type_text['year'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 0, 2));
$thisfile_png_chunk_type_text['month'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 2, 1));
$thisfile_png_chunk_type_text['day'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 3, 1));
$thisfile_png_chunk_type_text['hour'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 4, 1));
$thisfile_png_chunk_type_text['minute'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 5, 1));
$thisfile_png_chunk_type_text['second'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 6, 1));
$thisfile_png_chunk_type_text['unix'] = gmmktime($thisfile_png_chunk_type_text['hour'], $thisfile_png_chunk_type_text['minute'], $thisfile_png_chunk_type_text['second'], $thisfile_png_chunk_type_text['month'], $thisfile_png_chunk_type_text['day'], $thisfile_png_chunk_type_text['year']);
break;
case 'oFFs': // Image Offset
$thisfile_png_chunk_type_text['header'] = $chunk;
$thisfile_png_chunk_type_text['position_x'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 0, 4), false, true);
$thisfile_png_chunk_type_text['position_y'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 4, 4), false, true);
$thisfile_png_chunk_type_text['unit_specifier'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 8, 1));
$thisfile_png_chunk_type_text['unit'] = $this->PNGoFFsUnitLookup($thisfile_png_chunk_type_text['unit_specifier']);
break;
case 'pCAL': // Calibration Of Pixel Values
$thisfile_png_chunk_type_text['header'] = $chunk;
list($calibrationname, $otherdata) = explode("\x00", $chunk['data'], 2);
$thisfile_png_chunk_type_text['calibration_name'] = $calibrationname;
$pCALoffset = 0;
$thisfile_png_chunk_type_text['original_zero'] = getid3_lib::BigEndian2Int(substr($chunk['data'], $pCALoffset, 4), false, true);
$pCALoffset += 4;
$thisfile_png_chunk_type_text['original_max'] = getid3_lib::BigEndian2Int(substr($chunk['data'], $pCALoffset, 4), false, true);
$pCALoffset += 4;
$thisfile_png_chunk_type_text['equation_type'] = getid3_lib::BigEndian2Int(substr($chunk['data'], $pCALoffset, 1));
$pCALoffset += 1;
$thisfile_png_chunk_type_text['equation_type_text'] = $this->PNGpCALequationTypeLookup($thisfile_png_chunk_type_text['equation_type']);
$thisfile_png_chunk_type_text['parameter_count'] = getid3_lib::BigEndian2Int(substr($chunk['data'], $pCALoffset, 1));
$pCALoffset += 1;
$thisfile_png_chunk_type_text['parameters'] = explode("\x00", substr($chunk['data'], $pCALoffset));
break;
case 'sCAL': // Physical Scale Of Image Subject
$thisfile_png_chunk_type_text['header'] = $chunk;
$thisfile_png_chunk_type_text['unit_specifier'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 0, 1));
$thisfile_png_chunk_type_text['unit'] = $this->PNGsCALUnitLookup($thisfile_png_chunk_type_text['unit_specifier']);
list($pixelwidth, $pixelheight) = explode("\x00", substr($chunk['data'], 1));
$thisfile_png_chunk_type_text['pixel_width'] = $pixelwidth;
$thisfile_png_chunk_type_text['pixel_height'] = $pixelheight;
break;
case 'gIFg': // GIF Graphic Control Extension
$gIFgCounter = 0;
if (isset($thisfile_png_chunk_type_text) && is_array($thisfile_png_chunk_type_text)) {
$gIFgCounter = count($thisfile_png_chunk_type_text);
}
$thisfile_png_chunk_type_text[$gIFgCounter]['header'] = $chunk;
$thisfile_png_chunk_type_text[$gIFgCounter]['disposal_method'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 0, 1));
$thisfile_png_chunk_type_text[$gIFgCounter]['user_input_flag'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 1, 1));
$thisfile_png_chunk_type_text[$gIFgCounter]['delay_time'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 2, 2));
break;
case 'gIFx': // GIF Application Extension
$gIFxCounter = 0;
if (isset($thisfile_png_chunk_type_text) && is_array($thisfile_png_chunk_type_text)) {
$gIFxCounter = count($thisfile_png_chunk_type_text);
}
$thisfile_png_chunk_type_text[$gIFxCounter]['header'] = $chunk;
$thisfile_png_chunk_type_text[$gIFxCounter]['application_identifier'] = substr($chunk['data'], 0, 8);
$thisfile_png_chunk_type_text[$gIFxCounter]['authentication_code'] = substr($chunk['data'], 8, 3);
$thisfile_png_chunk_type_text[$gIFxCounter]['application_data'] = substr($chunk['data'], 11);
break;
case 'IDAT': // Image Data
$idatinformationfieldindex = 0;
if (isset($thisfile_png['IDAT']) && is_array($thisfile_png['IDAT'])) {
$idatinformationfieldindex = count($thisfile_png['IDAT']);
}
unset($chunk['data']);
$thisfile_png_chunk_type_text[$idatinformationfieldindex]['header'] = $chunk;
break;
case 'IEND': // Image Trailer
$thisfile_png_chunk_type_text['header'] = $chunk;
break;
case 'acTL': // Animation Control chunk
// https://wiki.mozilla.org/APNG_Specification#.60acTL.60:_The_Animation_Control_Chunk
$thisfile_png['animation']['num_frames'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 0, 4)); // Number of frames
$thisfile_png['animation']['num_plays'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 4, 4)); // Number of times to loop this APNG. 0 indicates infinite looping.
unset($chunk['data']);
$thisfile_png_chunk_type_text['header'] = $chunk;
break;
case 'fcTL': // Frame Control chunk
// https://wiki.mozilla.org/APNG_Specification#.60fcTL.60:_The_Frame_Control_Chunk
$fcTL = array();
$fcTL['sequence_number'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 0, 4)); // Sequence number of the animation chunk, starting from 0
$fcTL['width'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 4, 4)); // Width of the following frame
$fcTL['height'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 8, 4)); // Height of the following frame
$fcTL['x_offset'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 12, 4)); // X position at which to render the following frame
$fcTL['y_offset'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 16, 4)); // Y position at which to render the following frame
$fcTL['delay_num'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 20, 2)); // Frame delay fraction numerator
$fcTL['delay_den'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 22, 2)); // Frame delay fraction numerator
$fcTL['dispose_op'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 23, 1)); // Type of frame area disposal to be done after rendering this frame
$fcTL['blend_op'] = getid3_lib::BigEndian2Int(substr($chunk['data'], 23, 1)); // Type of frame area rendering for this frame
if ($fcTL['delay_den']) {
$fcTL['delay'] = $fcTL['delay_num'] / $fcTL['delay_den'];
}
$thisfile_png['animation']['fcTL'][$fcTL['sequence_number']] = $fcTL;
unset($chunk['data']);
$thisfile_png_chunk_type_text['header'] = $chunk;
break;
case 'fdAT': // Frame Data chunk
// https://wiki.mozilla.org/APNG_Specification#.60fcTL.60:_The_Frame_Control_Chunk
// "The `fdAT` chunk has the same purpose as an `IDAT` chunk. It has the same structure as an `IDAT` chunk, except preceded by a sequence number."
unset($chunk['data']);
$thisfile_png_chunk_type_text['header'] = $chunk;
break;
default:
//unset($chunk['data']);
$thisfile_png_chunk_type_text['header'] = $chunk;
$this->warning('Unhandled chunk type: '.$chunk['type_text']);
break;
}
}
if (!empty($thisfile_png['animation']['num_frames']) && !empty($thisfile_png['animation']['fcTL'])) {
$info['video']['dataformat'] = 'apng';
$info['playtime_seconds'] = 0;
foreach ($thisfile_png['animation']['fcTL'] as $seqno => $fcTL) {
$info['playtime_seconds'] += $fcTL['delay'];
}
}
return true;
}
/**
* @param int $sRGB
*
* @return string
*/
public function PNGsRGBintentLookup($sRGB) {
static $PNGsRGBintentLookup = array(
0 => 'Perceptual',
1 => 'Relative colorimetric',
2 => 'Saturation',
3 => 'Absolute colorimetric'
);
return (isset($PNGsRGBintentLookup[$sRGB]) ? $PNGsRGBintentLookup[$sRGB] : 'invalid');
}
/**
* @param int $compressionmethod
*
* @return string
*/
public function PNGcompressionMethodLookup($compressionmethod) {
static $PNGcompressionMethodLookup = array(
0 => 'deflate/inflate'
);
return (isset($PNGcompressionMethodLookup[$compressionmethod]) ? $PNGcompressionMethodLookup[$compressionmethod] : 'invalid');
}
/**
* @param int $unitid
*
* @return string
*/
public function PNGpHYsUnitLookup($unitid) {
static $PNGpHYsUnitLookup = array(
0 => 'unknown',
1 => 'meter'
);
return (isset($PNGpHYsUnitLookup[$unitid]) ? $PNGpHYsUnitLookup[$unitid] : 'invalid');
}
/**
* @param int $unitid
*
* @return string
*/
public function PNGoFFsUnitLookup($unitid) {
static $PNGoFFsUnitLookup = array(
0 => 'pixel',
1 => 'micrometer'
);
return (isset($PNGoFFsUnitLookup[$unitid]) ? $PNGoFFsUnitLookup[$unitid] : 'invalid');
}
/**
* @param int $equationtype
*
* @return string
*/
public function PNGpCALequationTypeLookup($equationtype) {
static $PNGpCALequationTypeLookup = array(
0 => 'Linear mapping',
1 => 'Base-e exponential mapping',
2 => 'Arbitrary-base exponential mapping',
3 => 'Hyperbolic mapping'
);
return (isset($PNGpCALequationTypeLookup[$equationtype]) ? $PNGpCALequationTypeLookup[$equationtype] : 'invalid');
}
/**
* @param int $unitid
*
* @return string
*/
public function PNGsCALUnitLookup($unitid) {
static $PNGsCALUnitLookup = array(
0 => 'meter',
1 => 'radian'
);
return (isset($PNGsCALUnitLookup[$unitid]) ? $PNGsCALUnitLookup[$unitid] : 'invalid');
}
/**
* @param int $color_type
* @param int $bit_depth
*
* @return int|false
*/
public function IHDRcalculateBitsPerSample($color_type, $bit_depth) {
switch ($color_type) {
case 0: // Each pixel is a grayscale sample.
return $bit_depth;
case 2: // Each pixel is an R,G,B triple
return 3 * $bit_depth;
case 3: // Each pixel is a palette index; a PLTE chunk must appear.
return $bit_depth;
case 4: // Each pixel is a grayscale sample, followed by an alpha sample.
return 2 * $bit_depth;
case 6: // Each pixel is an R,G,B triple, followed by an alpha sample.
return 4 * $bit_depth;
}
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.graphic.svg.php //
// module for analyzing SVG Image files //
// dependencies: NONE //
// ///
/////////////////////////////////////////////////////////////////
if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
exit;
}
class getid3_svg extends getid3_handler
{
/**
* @return bool
*/
public function Analyze() {
$info = &$this->getid3->info;
$this->fseek($info['avdataoffset']);
$SVGheader = $this->fread(4096);
if (preg_match('#\<\?xml([^\>]+)\?\>#i', $SVGheader, $matches)) {
$info['svg']['xml']['raw'] = $matches;
}
if (preg_match('#\<\!DOCTYPE([^\>]+)\>#i', $SVGheader, $matches)) {
$info['svg']['doctype']['raw'] = $matches;
}
if (preg_match('#\<svg([^\>]+)\>#i', $SVGheader, $matches)) {
$info['svg']['svg']['raw'] = $matches;
}
if (isset($info['svg']['svg']['raw'])) {
$sections_to_fix = array('xml', 'doctype', 'svg');
foreach ($sections_to_fix as $section_to_fix) {
if (!isset($info['svg'][$section_to_fix])) {
continue;
}
$section_data = array();
while (preg_match('/ "([^"]+)"/', $info['svg'][$section_to_fix]['raw'][1], $matches)) {
$section_data[] = $matches[1];
$info['svg'][$section_to_fix]['raw'][1] = str_replace($matches[0], '', $info['svg'][$section_to_fix]['raw'][1]);
}
while (preg_match('/([^\s]+)="([^"]+)"/', $info['svg'][$section_to_fix]['raw'][1], $matches)) {
$section_data[] = $matches[0];
$info['svg'][$section_to_fix]['raw'][1] = str_replace($matches[0], '', $info['svg'][$section_to_fix]['raw'][1]);
}
$section_data = array_merge($section_data, preg_split('/[\s,]+/', $info['svg'][$section_to_fix]['raw'][1]));
foreach ($section_data as $keyvaluepair) {
$keyvaluepair = trim($keyvaluepair);
if ($keyvaluepair) {
$keyvalueexploded = explode('=', $keyvaluepair);
$key = (isset($keyvalueexploded[0]) ? $keyvalueexploded[0] : '');
$value = (isset($keyvalueexploded[1]) ? $keyvalueexploded[1] : '');
$info['svg'][$section_to_fix]['sections'][$key] = trim($value, '"');
}
}
}
$info['fileformat'] = 'svg';
$info['video']['dataformat'] = 'svg';
$info['video']['lossless'] = true;
//$info['video']['bits_per_sample'] = 24;
$info['video']['pixel_aspect_ratio'] = (float) 1;
if (!empty($info['svg']['svg']['sections']['width'])) {
$info['svg']['width'] = intval($info['svg']['svg']['sections']['width']);
}
if (!empty($info['svg']['svg']['sections']['height'])) {
$info['svg']['height'] = intval($info['svg']['svg']['sections']['height']);
}
if (!empty($info['svg']['svg']['sections']['version'])) {
$info['svg']['version'] = $info['svg']['svg']['sections']['version'];
}
if (!isset($info['svg']['version']) && isset($info['svg']['doctype']['sections'])) {
foreach ($info['svg']['doctype']['sections'] as $key => $value) {
if (preg_match('#//W3C//DTD SVG ([0-9\.]+)//#i', $key, $matches)) {
$info['svg']['version'] = $matches[1];
break;
}
}
}
if (!empty($info['svg']['width'])) {
$info['video']['resolution_x'] = $info['svg']['width'];
}
if (!empty($info['svg']['height'])) {
$info['video']['resolution_y'] = $info['svg']['height'];
}
return true;
}
$this->error('Did not find expected <svg> tag');
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.archive.tiff.php //
// module for analyzing TIFF files //
// dependencies: NONE //
// ///
/////////////////////////////////////////////////////////////////
if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
exit;
}
class getid3_tiff extends getid3_handler
{
/**
* @return bool
*/
public function Analyze() {
$info = &$this->getid3->info;
$this->fseek($info['avdataoffset']);
$TIFFheader = $this->fread(4);
switch (substr($TIFFheader, 0, 2)) {
case 'II':
$info['tiff']['byte_order'] = 'Intel';
break;
case 'MM':
$info['tiff']['byte_order'] = 'Motorola';
break;
default:
$this->error('Invalid TIFF byte order identifier ('.substr($TIFFheader, 0, 2).') at offset '.$info['avdataoffset']);
return false;
}
$info['fileformat'] = 'tiff';
$info['video']['dataformat'] = 'tiff';
$info['video']['lossless'] = true;
$info['tiff']['ifd'] = array();
$CurrentIFD = array();
$FieldTypeByteLength = array(1=>1, 2=>1, 3=>2, 4=>4, 5=>8);
$nextIFDoffset = $this->TIFFendian2Int($this->fread(4), $info['tiff']['byte_order']);
while ($nextIFDoffset > 0) {
$CurrentIFD['offset'] = $nextIFDoffset;
$this->fseek($info['avdataoffset'] + $nextIFDoffset);
$CurrentIFD['fieldcount'] = $this->TIFFendian2Int($this->fread(2), $info['tiff']['byte_order']);
for ($i = 0; $i < $CurrentIFD['fieldcount']; $i++) {
$CurrentIFD['fields'][$i]['raw']['tag'] = $this->TIFFendian2Int($this->fread(2), $info['tiff']['byte_order']);
$CurrentIFD['fields'][$i]['raw']['type'] = $this->TIFFendian2Int($this->fread(2), $info['tiff']['byte_order']);
$CurrentIFD['fields'][$i]['raw']['length'] = $this->TIFFendian2Int($this->fread(4), $info['tiff']['byte_order']);
$CurrentIFD['fields'][$i]['raw']['valoff'] = $this->fread(4); // To save time and space the Value Offset contains the Value instead of pointing to the Value if and only if the Value fits into 4 bytes. If the Value is shorter than 4 bytes, it is left-justified within the 4-byte Value Offset, i.e., stored in the lowernumbered bytes. Whether the Value fits within 4 bytes is determined by the Type and Count of the field.
$CurrentIFD['fields'][$i]['raw']['tag_name'] = $this->TIFFcommentName($CurrentIFD['fields'][$i]['raw']['tag']);
switch ($CurrentIFD['fields'][$i]['raw']['type']) {
case 1: // BYTE An 8-bit unsigned integer.
if ($CurrentIFD['fields'][$i]['raw']['length'] <= 4) {
$CurrentIFD['fields'][$i]['value'] = $this->TIFFendian2Int(substr($CurrentIFD['fields'][$i]['raw']['valoff'], 0, 1), $info['tiff']['byte_order']);
} else {
$CurrentIFD['fields'][$i]['offset'] = $this->TIFFendian2Int($CurrentIFD['fields'][$i]['raw']['valoff'], $info['tiff']['byte_order']);
}
break;
case 2: // ASCII 8-bit bytes that store ASCII codes; the last byte must be null.
if ($CurrentIFD['fields'][$i]['raw']['length'] <= 4) {
$CurrentIFD['fields'][$i]['value'] = substr($CurrentIFD['fields'][$i]['raw']['valoff'], 3);
} else {
$CurrentIFD['fields'][$i]['offset'] = $this->TIFFendian2Int($CurrentIFD['fields'][$i]['raw']['valoff'], $info['tiff']['byte_order']);
}
break;
case 3: // SHORT A 16-bit (2-byte) unsigned integer.
if ($CurrentIFD['fields'][$i]['raw']['length'] <= 2) {
$CurrentIFD['fields'][$i]['value'] = $this->TIFFendian2Int(substr($CurrentIFD['fields'][$i]['raw']['valoff'], 0, 2), $info['tiff']['byte_order']);
} else {
$CurrentIFD['fields'][$i]['offset'] = $this->TIFFendian2Int($CurrentIFD['fields'][$i]['raw']['valoff'], $info['tiff']['byte_order']);
}
break;
case 4: // LONG A 32-bit (4-byte) unsigned integer.
if ($CurrentIFD['fields'][$i]['raw']['length'] <= 4) {
$CurrentIFD['fields'][$i]['value'] = $this->TIFFendian2Int($CurrentIFD['fields'][$i]['raw']['valoff'], $info['tiff']['byte_order']);
} else {
$CurrentIFD['fields'][$i]['offset'] = $this->TIFFendian2Int($CurrentIFD['fields'][$i]['raw']['valoff'], $info['tiff']['byte_order']);
}
break;
case 5: // RATIONAL Two LONG_s: the first represents the numerator of a fraction, the second the denominator.
case 7: // UNDEFINED An 8-bit byte that may contain anything, depending on the definition of the field.
$CurrentIFD['fields'][$i]['offset'] = $this->TIFFendian2Int($CurrentIFD['fields'][$i]['raw']['valoff'], $info['tiff']['byte_order']);
break;
// Warning: It is possible that other TIFF field types will be added in the future. Readers should skip over fields containing an unexpected field type.
// In TIFF 6.0, some new field types have been defined:
// These new field types are also governed by the byte order (II or MM) in the TIFF header.
case 6: // SBYTE An 8-bit signed (twos-complement) integer.
case 8: // SSHORT A 16-bit (2-byte) signed (twos-complement) integer.
case 9: // SLONG A 32-bit (4-byte) signed (twos-complement) integer.
case 10: // SRATIONAL Two SLONGs: the first represents the numerator of a fraction, the second the denominator.
case 11: // FLOAT Single precision (4-byte) IEEE format
case 12: // DOUBLE Double precision (8-byte) IEEE format
default:
$this->warning('unhandled IFD field type '.$CurrentIFD['fields'][$i]['raw']['type'].' for IFD entry '.$i);
break;
}
}
$info['tiff']['ifd'][] = $CurrentIFD;
$CurrentIFD = array();
$nextIFDoffset = $this->TIFFendian2Int($this->fread(4), $info['tiff']['byte_order']);
}
foreach ($info['tiff']['ifd'] as $IFDid => $IFDarray) {
foreach ($IFDarray['fields'] as $key => $fieldarray) {
switch ($fieldarray['raw']['tag']) {
case 256: // ImageWidth
case 257: // ImageLength
case 258: // BitsPerSample
case 259: // Compression
if (!isset($fieldarray['value'])) {
$this->fseek($fieldarray['offset']);
$info['tiff']['ifd'][$IFDid]['fields'][$key]['raw']['data'] = $this->fread($fieldarray['raw']['length'] * $FieldTypeByteLength[$fieldarray['raw']['type']]);
}
break;
case 270: // ImageDescription
case 271: // Make
case 272: // Model
case 305: // Software
case 306: // DateTime
case 315: // Artist
case 316: // HostComputer
if (isset($fieldarray['value'])) {
$info['tiff']['ifd'][$IFDid]['fields'][$key]['raw']['data'] = $fieldarray['value'];
} else {
$this->fseek($fieldarray['offset']);
$info['tiff']['ifd'][$IFDid]['fields'][$key]['raw']['data'] = $this->fread($fieldarray['raw']['length'] * $FieldTypeByteLength[$fieldarray['raw']['type']]);
}
break;
case 700:
$XMPmagic = '<?xpacket';
$this->fseek($fieldarray['offset']);
$xmpkey = (isset($info['tiff']['XMP']) ? count($info['tiff']['XMP']) : 0);
$info['tiff']['XMP'][$xmpkey]['raw'] = $this->fread($fieldarray['raw']['length']);
if (substr($info['tiff']['XMP'][$xmpkey]['raw'], 0, strlen($XMPmagic)) != $XMPmagic) {
$this->warning('did not find expected XMP data at offset '.$fieldarray['offset']);
unset($info['tiff']['XMP'][$xmpkey]['raw']);
}
break;
}
switch ($fieldarray['raw']['tag']) {
case 256: // ImageWidth
$info['video']['resolution_x'] = $fieldarray['value'];
break;
case 257: // ImageLength
$info['video']['resolution_y'] = $fieldarray['value'];
break;
case 258: // BitsPerSample
if (isset($fieldarray['value'])) {
$info['video']['bits_per_sample'] = $fieldarray['value'];
} else {
$info['video']['bits_per_sample'] = 0;
for ($i = 0; $i < $fieldarray['raw']['length']; $i++) {
$info['video']['bits_per_sample'] += $this->TIFFendian2Int(substr($info['tiff']['ifd'][$IFDid]['fields'][$key]['raw']['data'], $i * $FieldTypeByteLength[$fieldarray['raw']['type']], $FieldTypeByteLength[$fieldarray['raw']['type']]), $info['tiff']['byte_order']);
}
}
break;
case 259: // Compression
$info['video']['codec'] = $this->TIFFcompressionMethod($fieldarray['value']);
break;
case 270: // ImageDescription
case 271: // Make
case 272: // Model
case 305: // Software
case 306: // DateTime
case 315: // Artist
case 316: // HostComputer
$TIFFcommentName = strtolower($fieldarray['raw']['tag_name']);
if (isset($info['tiff']['comments'][$TIFFcommentName])) {
$info['tiff']['comments'][$TIFFcommentName][] = $info['tiff']['ifd'][$IFDid]['fields'][$key]['raw']['data'];
} else {
$info['tiff']['comments'][$TIFFcommentName] = array($info['tiff']['ifd'][$IFDid]['fields'][$key]['raw']['data']);
}
break;
default:
break;
}
}
}
return true;
}
/**
* @param string $bytestring
* @param string $byteorder
*
* @return int|float|false
*/
public function TIFFendian2Int($bytestring, $byteorder) {
if ($byteorder == 'Intel') {
return getid3_lib::LittleEndian2Int($bytestring);
} elseif ($byteorder == 'Motorola') {
return getid3_lib::BigEndian2Int($bytestring);
}
return false;
}
/**
* @param int $id
*
* @return string
*/
public function TIFFcompressionMethod($id) {
// https://en.wikipedia.org/wiki/TIFF#TIFF_Compression_Tag
static $TIFFcompressionMethod = array();
if (empty($TIFFcompressionMethod)) {
$TIFFcompressionMethod = array(
0x0001 => 'Uncompressed',
0x0002 => 'Huffman',
0x0003 => 'CCITT T.4',
0x0004 => 'CCITT T.6',
0x0005 => 'LZW',
0x0006 => 'JPEG-old',
0x0007 => 'JPEG',
0x0008 => 'deflate',
0x0009 => 'JBIG ITU-T T.85',
0x000A => 'JBIG ITU-T T.43',
0x7FFE => 'NeXT RLE 2-bit',
0x8005 => 'PackBits',
0x8029 => 'ThunderScan RLE 4-bit',
0x807F => 'RasterPadding',
0x8080 => 'RLE-LW',
0x8081 => 'RLE-CT',
0x8082 => 'RLE-BL',
0x80B2 => 'deflate-PK',
0x80B3 => 'Kodak-DCS',
0x8765 => 'JBIG',
0x8798 => 'JPEG2000',
0x8799 => 'Nikon NEF',
0x879B => 'JBIG2',
);
}
return (isset($TIFFcompressionMethod[$id]) ? $TIFFcompressionMethod[$id] : 'unknown/invalid ('.$id.')');
}
/**
* @param int $id
*
* @return string
*/
public function TIFFcommentName($id) {
// https://www.awaresystems.be/imaging/tiff/tifftags.html
static $TIFFcommentName = array();
if (empty($TIFFcommentName)) {
$TIFFcommentName = array(
254 => 'NewSubfileType',
255 => 'SubfileType',
256 => 'ImageWidth',
257 => 'ImageLength',
258 => 'BitsPerSample',
259 => 'Compression',
262 => 'PhotometricInterpretation',
263 => 'Threshholding',
264 => 'CellWidth',
265 => 'CellLength',
266 => 'FillOrder',
269 => 'DocumentName',
270 => 'ImageDescription',
271 => 'Make',
272 => 'Model',
273 => 'StripOffsets',
274 => 'Orientation',
277 => 'SamplesPerPixel',
278 => 'RowsPerStrip',
279 => 'StripByteCounts',
280 => 'MinSampleValue',
281 => 'MaxSampleValue',
282 => 'XResolution',
283 => 'YResolution',
284 => 'PlanarConfiguration',
285 => 'PageName',
286 => 'XPosition',
287 => 'YPosition',
288 => 'FreeOffsets',
289 => 'FreeByteCounts',
290 => 'GrayResponseUnit',
291 => 'GrayResponseCurve',
292 => 'T4Options',
293 => 'T6Options',
296 => 'ResolutionUnit',
297 => 'PageNumber',
301 => 'TransferFunction',
305 => 'Software',
306 => 'DateTime',
315 => 'Artist',
316 => 'HostComputer',
317 => 'Predictor',
318 => 'WhitePoint',
319 => 'PrimaryChromaticities',
320 => 'ColorMap',
321 => 'HalftoneHints',
322 => 'TileWidth',
323 => 'TileLength',
324 => 'TileOffsets',
325 => 'TileByteCounts',
326 => 'BadFaxLines',
327 => 'CleanFaxData',
328 => 'ConsecutiveBadFaxLines',
330 => 'SubIFDs',
332 => 'InkSet',
333 => 'InkNames',
334 => 'NumberOfInks',
336 => 'DotRange',
337 => 'TargetPrinter',
338 => 'ExtraSamples',
339 => 'SampleFormat',
340 => 'SMinSampleValue',
341 => 'SMaxSampleValue',
342 => 'TransferRange',
343 => 'ClipPath',
344 => 'XClipPathUnits',
345 => 'YClipPathUnits',
346 => 'Indexed',
347 => 'JPEGTables',
351 => 'OPIProxy',
400 => 'GlobalParametersIFD',
401 => 'ProfileType',
402 => 'FaxProfile',
403 => 'CodingMethods',
404 => 'VersionYear',
405 => 'ModeNumber',
433 => 'Decode',
434 => 'DefaultImageColor',
512 => 'JPEGProc',
513 => 'JPEGInterchangeFormat',
514 => 'JPEGInterchangeFormatLngth',
515 => 'JPEGRestartInterval',
517 => 'JPEGLosslessPredictors',
518 => 'JPEGPointTransforms',
519 => 'JPEGQTables',
520 => 'JPEGDCTables',
521 => 'JPEGACTables',
529 => 'YCbCrCoefficients',
530 => 'YCbCrSubSampling',
531 => 'YCbCrPositioning',
532 => 'ReferenceBlackWhite',
559 => 'StripRowCounts',
700 => 'XMP',
32781 => 'ImageID',
33432 => 'Copyright',
34732 => 'ImageLayer',
// Private Tags - https://www.awaresystems.be/imaging/tiff/tifftags/private.html
32932 => 'Wang Annotation', // Annotation data, as used in 'Imaging for Windows'.
33445 => 'MD FileTag', // Specifies the pixel data format encoding in the Molecular Dynamics GEL file format.
33446 => 'MD ScalePixel', // Specifies a scale factor in the Molecular Dynamics GEL file format.
33447 => 'MD ColorTable', // Used to specify the conversion from 16bit to 8bit in the Molecular Dynamics GEL file format.
33448 => 'MD LabName', // Name of the lab that scanned this file, as used in the Molecular Dynamics GEL file format.
33449 => 'MD SampleInfo', // Information about the sample, as used in the Molecular Dynamics GEL file format.
33450 => 'MD PrepDate', // Date the sample was prepared, as used in the Molecular Dynamics GEL file format.
33451 => 'MD PrepTime', // Time the sample was prepared, as used in the Molecular Dynamics GEL file format.
33452 => 'MD FileUnits', // Units for data in this file, as used in the Molecular Dynamics GEL file format.
33550 => 'ModelPixelScaleTag', // Used in interchangeable GeoTIFF files.
33723 => 'IPTC', // IPTC (International Press Telecommunications Council) metadata.
33918 => 'INGR Packet Data Tag', // Intergraph Application specific storage.
33919 => 'INGR Flag Registers', // Intergraph Application specific flags.
33920 => 'IrasB Transformation Matrix', // Originally part of Intergraph's GeoTIFF tags, but likely understood by IrasB only.
33922 => 'ModelTiepointTag', // Originally part of Intergraph's GeoTIFF tags, but now used in interchangeable GeoTIFF files.
34264 => 'ModelTransformationTag', // Used in interchangeable GeoTIFF files.
34377 => 'Photoshop', // Collection of Photoshop 'Image Resource Blocks'.
34665 => 'Exif IFD', // A pointer to the Exif IFD.
34675 => 'ICC Profile', // ICC profile data.
34735 => 'GeoKeyDirectoryTag', // Used in interchangeable GeoTIFF files.
34736 => 'GeoDoubleParamsTag', // Used in interchangeable GeoTIFF files.
34737 => 'GeoAsciiParamsTag', // Used in interchangeable GeoTIFF files.
34853 => 'GPS IFD', // A pointer to the Exif-related GPS Info IFD.
34908 => 'HylaFAX FaxRecvParams', // Used by HylaFAX.
34909 => 'HylaFAX FaxSubAddress', // Used by HylaFAX.
34910 => 'HylaFAX FaxRecvTime', // Used by HylaFAX.
37724 => 'ImageSourceData', // Used by Adobe Photoshop.
40965 => 'Interoperability IFD', // A pointer to the Exif-related Interoperability IFD.
42112 => 'GDAL_METADATA', // Used by the GDAL library, holds an XML list of name=value 'metadata' values about the image as a whole, and about specific samples.
42113 => 'GDAL_NODATA', // Used by the GDAL library, contains an ASCII encoded nodata or background pixel value.
50215 => 'Oce Scanjob Description', // Used in the Oce scanning process.
50216 => 'Oce Application Selector', // Used in the Oce scanning process.
50217 => 'Oce Identification Number', // Used in the Oce scanning process.
50218 => 'Oce ImageLogic Characteristics', // Used in the Oce scanning process.
50706 => 'DNGVersion', // Used in IFD 0 of DNG files.
50707 => 'DNGBackwardVersion', // Used in IFD 0 of DNG files.
50708 => 'UniqueCameraModel', // Used in IFD 0 of DNG files.
50709 => 'LocalizedCameraModel', // Used in IFD 0 of DNG files.
50710 => 'CFAPlaneColor', // Used in Raw IFD of DNG files.
50711 => 'CFALayout', // Used in Raw IFD of DNG files.
50712 => 'LinearizationTable', // Used in Raw IFD of DNG files.
50713 => 'BlackLevelRepeatDim', // Used in Raw IFD of DNG files.
50714 => 'BlackLevel', // Used in Raw IFD of DNG files.
50715 => 'BlackLevelDeltaH', // Used in Raw IFD of DNG files.
50716 => 'BlackLevelDeltaV', // Used in Raw IFD of DNG files.
50717 => 'WhiteLevel', // Used in Raw IFD of DNG files.
50718 => 'DefaultScale', // Used in Raw IFD of DNG files.
50719 => 'DefaultCropOrigin', // Used in Raw IFD of DNG files.
50720 => 'DefaultCropSize', // Used in Raw IFD of DNG files.
50721 => 'ColorMatrix1', // Used in IFD 0 of DNG files.
50722 => 'ColorMatrix2', // Used in IFD 0 of DNG files.
50723 => 'CameraCalibration1', // Used in IFD 0 of DNG files.
50724 => 'CameraCalibration2', // Used in IFD 0 of DNG files.
50725 => 'ReductionMatrix1', // Used in IFD 0 of DNG files.
50726 => 'ReductionMatrix2', // Used in IFD 0 of DNG files.
50727 => 'AnalogBalance', // Used in IFD 0 of DNG files.
50728 => 'AsShotNeutral', // Used in IFD 0 of DNG files.
50729 => 'AsShotWhiteXY', // Used in IFD 0 of DNG files.
50730 => 'BaselineExposure', // Used in IFD 0 of DNG files.
50731 => 'BaselineNoise', // Used in IFD 0 of DNG files.
50732 => 'BaselineSharpness', // Used in IFD 0 of DNG files.
50733 => 'BayerGreenSplit', // Used in Raw IFD of DNG files.
50734 => 'LinearResponseLimit', // Used in IFD 0 of DNG files.
50735 => 'CameraSerialNumber', // Used in IFD 0 of DNG files.
50736 => 'LensInfo', // Used in IFD 0 of DNG files.
50737 => 'ChromaBlurRadius', // Used in Raw IFD of DNG files.
50738 => 'AntiAliasStrength', // Used in Raw IFD of DNG files.
50740 => 'DNGPrivateData', // Used in IFD 0 of DNG files.
50741 => 'MakerNoteSafety', // Used in IFD 0 of DNG files.
50778 => 'CalibrationIlluminant1', // Used in IFD 0 of DNG files.
50779 => 'CalibrationIlluminant2', // Used in IFD 0 of DNG files.
50780 => 'BestQualityScale', // Used in Raw IFD of DNG files.
50784 => 'Alias Layer Metadata', // Alias Sketchbook Pro layer usage description.
50908 => 'TIFF_RSID', // This private tag is used in a GEOTIFF standard by DGIWG.
50909 => 'GEO_METADATA', // This private tag is used in a GEOTIFF standard by DGIWG.
// EXIF tags - https://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html
33434 => 'ExposureTime', // Exposure time, given in seconds.
33437 => 'FNumber', // The F number.
34850 => 'ExposureProgram', // The class of the program used by the camera to set exposure when the picture is taken.
34852 => 'SpectralSensitivity', // Indicates the spectral sensitivity of each channel of the camera used.
34855 => 'ISOSpeedRatings', // Indicates the ISO Speed and ISO Latitude of the camera or input device as specified in ISO 12232.
34856 => 'OECF', // Indicates the Opto-Electric Conversion Function (OECF) specified in ISO 14524.
36864 => 'ExifVersion', // The version of the supported Exif standard.
36867 => 'DateTimeOriginal', // The date and time when the original image data was generated.
36868 => 'DateTimeDigitized', // The date and time when the image was stored as digital data.
37121 => 'ComponentsConfiguration', // Specific to compressed data; specifies the channels and complements PhotometricInterpretation
37122 => 'CompressedBitsPerPixel', // Specific to compressed data; states the compressed bits per pixel.
37377 => 'ShutterSpeedValue', // Shutter speed.
37378 => 'ApertureValue', // The lens aperture.
37379 => 'BrightnessValue', // The value of brightness.
37380 => 'ExposureBiasValue', // The exposure bias.
37381 => 'MaxApertureValue', // The smallest F number of the lens.
37382 => 'SubjectDistance', // The distance to the subject, given in meters.
37383 => 'MeteringMode', // The metering mode.
37384 => 'LightSource', // The kind of light source.
37385 => 'Flash', // Indicates the status of flash when the image was shot.
37386 => 'FocalLength', // The actual focal length of the lens, in mm.
37396 => 'SubjectArea', // Indicates the location and area of the main subject in the overall scene.
37500 => 'MakerNote', // Manufacturer specific information.
37510 => 'UserComment', // Keywords or comments on the image; complements ImageDescription.
37520 => 'SubsecTime', // A tag used to record fractions of seconds for the DateTime tag.
37521 => 'SubsecTimeOriginal', // A tag used to record fractions of seconds for the DateTimeOriginal tag.
37522 => 'SubsecTimeDigitized', // A tag used to record fractions of seconds for the DateTimeDigitized tag.
40960 => 'FlashpixVersion', // The Flashpix format version supported by a FPXR file.
40961 => 'ColorSpace', // The color space information tag is always recorded as the color space specifier.
40962 => 'PixelXDimension', // Specific to compressed data; the valid width of the meaningful image.
40963 => 'PixelYDimension', // Specific to compressed data; the valid height of the meaningful image.
40964 => 'RelatedSoundFile', // Used to record the name of an audio file related to the image data.
41483 => 'FlashEnergy', // Indicates the strobe energy at the time the image is captured, as measured in Beam Candle Power Seconds
41484 => 'SpatialFrequencyResponse', // Records the camera or input device spatial frequency table and SFR values in the direction of image width, image height, and diagonal direction, as specified in ISO 12233.
41486 => 'FocalPlaneXResolution', // Indicates the number of pixels in the image width (X) direction per FocalPlaneResolutionUnit on the camera focal plane.
41487 => 'FocalPlaneYResolution', // Indicates the number of pixels in the image height (Y) direction per FocalPlaneResolutionUnit on the camera focal plane.
41488 => 'FocalPlaneResolutionUnit', // Indicates the unit for measuring FocalPlaneXResolution and FocalPlaneYResolution.
41492 => 'SubjectLocation', // Indicates the location of the main subject in the scene.
41493 => 'ExposureIndex', // Indicates the exposure index selected on the camera or input device at the time the image is captured.
41495 => 'SensingMethod', // Indicates the image sensor type on the camera or input device.
41728 => 'FileSource', // Indicates the image source.
41729 => 'SceneType', // Indicates the type of scene.
41730 => 'CFAPattern', // Indicates the color filter array (CFA) geometric pattern of the image sensor when a one-chip color area sensor is used.
41985 => 'CustomRendered', // Indicates the use of special processing on image data, such as rendering geared to output.
41986 => 'ExposureMode', // Indicates the exposure mode set when the image was shot.
41987 => 'WhiteBalance', // Indicates the white balance mode set when the image was shot.
41988 => 'DigitalZoomRatio', // Indicates the digital zoom ratio when the image was shot.
41989 => 'FocalLengthIn35mmFilm', // Indicates the equivalent focal length assuming a 35mm film camera, in mm.
41990 => 'SceneCaptureType', // Indicates the type of scene that was shot.
41991 => 'GainControl', // Indicates the degree of overall image gain adjustment.
41992 => 'Contrast', // Indicates the direction of contrast processing applied by the camera when the image was shot.
41993 => 'Saturation', // Indicates the direction of saturation processing applied by the camera when the image was shot.
41994 => 'Sharpness', // Indicates the direction of sharpness processing applied by the camera when the image was shot.
41995 => 'DeviceSettingDescription', // This tag indicates information on the picture-taking conditions of a particular camera model.
41996 => 'SubjectDistanceRange', // Indicates the distance to the subject.
42016 => 'ImageUniqueID', // Indicates an identifier assigned uniquely to each image.
);
}
return (isset($TIFFcommentName[$id]) ? $TIFFcommentName[$id] : 'unknown/invalid ('.$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.misc.cue.php //
// module for analyzing CUEsheet files //
// dependencies: NONE //
// //
/////////////////////////////////////////////////////////////////
// //
// Module originally written [2009-Mar-25] by //
// Nigel Barnes <ngbarnesØhotmail*com> //
// Minor reformatting and similar small changes to integrate //
// into getID3 by James Heinrich <info@getid3.org> //
// ///
/////////////////////////////////////////////////////////////////
/*
* CueSheet parser by Nigel Barnes.
*
* This is a PHP conversion of CueSharp 0.5 by Wyatt O'Day (wyday.com/cuesharp)
*/
/**
* A CueSheet class used to open and parse cuesheets.
*
*/
if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
exit;
}
class getid3_cue extends getid3_handler
{
public $cuesheet = array();
/**
* @return bool
*/
public function Analyze() {
$info = &$this->getid3->info;
$info['fileformat'] = 'cue';
$this->readCueSheetFilename($info['filenamepath']);
$info['cue'] = $this->cuesheet;
return true;
}
/**
* @param string $filename
*
* @return array
*/
public function readCueSheetFilename($filename)
{
$filedata = file_get_contents($filename);
return $this->readCueSheet($filedata);
}
/**
* Parses a cue sheet file.
*
* @param string $filedata
*
* @return array
*/
public function readCueSheet(&$filedata)
{
$cue_lines = array();
foreach (explode("\n", str_replace("\r", null, $filedata)) as $line)
{
if ( (strlen($line) > 0) && ($line[0] != '#'))
{
$cue_lines[] = trim($line);
}
}
$this->parseCueSheet($cue_lines);
return $this->cuesheet;
}
/**
* Parses the cue sheet array.
*
* @param array $file - The cuesheet as an array of each line.
*/
public function parseCueSheet($file)
{
//-1 means still global, all others are track specific
$track_on = -1;
for ($i=0; $i < count($file); $i++)
{
list($key) = explode(' ', strtolower($file[$i]), 2);
switch ($key)
{
case 'catalog':
case 'cdtextfile':
case 'isrc':
case 'performer':
case 'songwriter':
case 'title':
$this->parseString($file[$i], $track_on);
break;
case 'file':
$currentFile = $this->parseFile($file[$i]);
break;
case 'flags':
$this->parseFlags($file[$i], $track_on);
break;
case 'index':
case 'postgap':
case 'pregap':
$this->parseIndex($file[$i], $track_on);
break;
case 'rem':
$this->parseComment($file[$i], $track_on);
break;
case 'track':
$track_on++;
$this->parseTrack($file[$i], $track_on);
if (isset($currentFile)) // if there's a file
{
$this->cuesheet['tracks'][$track_on]['datafile'] = $currentFile;
}
break;
default:
//save discarded junk and place string[] with track it was found in
$this->parseGarbage($file[$i], $track_on);
break;
}
}
}
/**
* Parses the REM command.
*
* @param string $line - The line in the cue file that contains the TRACK command.
* @param integer $track_on - The track currently processing.
*/
public function parseComment($line, $track_on)
{
$explodedline = explode(' ', $line, 3);
$comment_REM = (isset($explodedline[0]) ? $explodedline[0] : '');
$comment_type = (isset($explodedline[1]) ? $explodedline[1] : '');
$comment_data = (isset($explodedline[2]) ? $explodedline[2] : '');
if (($comment_REM == 'REM') && $comment_type) {
$comment_type = strtolower($comment_type);
$commment_data = trim($comment_data, ' "');
if ($track_on != -1) {
$this->cuesheet['tracks'][$track_on]['comments'][$comment_type][] = $comment_data;
} else {
$this->cuesheet['comments'][$comment_type][] = $comment_data;
}
}
}
/**
* Parses the FILE command.
*
* @param string $line - The line in the cue file that contains the FILE command.
*
* @return array - Array of FILENAME and TYPE of file..
*/
public function parseFile($line)
{
$line = substr($line, strpos($line, ' ') + 1);
$type = strtolower(substr($line, strrpos($line, ' ')));
//remove type
$line = substr($line, 0, strrpos($line, ' ') - 1);
//if quotes around it, remove them.
$line = trim($line, '"');
return array('filename'=>$line, 'type'=>$type);
}
/**
* Parses the FLAG command.
*
* @param string $line - The line in the cue file that contains the TRACK command.
* @param integer $track_on - The track currently processing.
*/
public function parseFlags($line, $track_on)
{
if ($track_on != -1)
{
foreach (explode(' ', strtolower($line)) as $type)
{
switch ($type)
{
case 'flags':
// first entry in this line
$this->cuesheet['tracks'][$track_on]['flags'] = array(
'4ch' => false,
'data' => false,
'dcp' => false,
'pre' => false,
'scms' => false,
);
break;
case 'data':
case 'dcp':
case '4ch':
case 'pre':
case 'scms':
$this->cuesheet['tracks'][$track_on]['flags'][$type] = true;
break;
default:
break;
}
}
}
}
/**
* Collect any unidentified data.
*
* @param string $line - The line in the cue file that contains the TRACK command.
* @param integer $track_on - The track currently processing.
*/
public function parseGarbage($line, $track_on)
{
if ( strlen($line) > 0 )
{
if ($track_on == -1)
{
$this->cuesheet['garbage'][] = $line;
}
else
{
$this->cuesheet['tracks'][$track_on]['garbage'][] = $line;
}
}
}
/**
* Parses the INDEX command of a TRACK.
*
* @param string $line - The line in the cue file that contains the TRACK command.
* @param integer $track_on - The track currently processing.
*/
public function parseIndex($line, $track_on)
{
$type = strtolower(substr($line, 0, strpos($line, ' ')));
$line = substr($line, strpos($line, ' ') + 1);
$number = 0;
if ($type == 'index')
{
//read the index number
$number = intval(substr($line, 0, strpos($line, ' ')));
$line = substr($line, strpos($line, ' ') + 1);
}
//extract the minutes, seconds, and frames
$explodedline = explode(':', $line);
$minutes = (isset($explodedline[0]) ? $explodedline[0] : '');
$seconds = (isset($explodedline[1]) ? $explodedline[1] : '');
$frames = (isset($explodedline[2]) ? $explodedline[2] : '');
switch ($type) {
case 'index':
$this->cuesheet['tracks'][$track_on][$type][$number] = array('minutes'=>intval($minutes), 'seconds'=>intval($seconds), 'frames'=>intval($frames));
break;
case 'pregap':
case 'postgap':
$this->cuesheet['tracks'][$track_on][$type] = array('minutes'=>intval($minutes), 'seconds'=>intval($seconds), 'frames'=>intval($frames));
break;
}
}
/**
* @param string $line
* @param int $track_on
*/
public function parseString($line, $track_on)
{
$category = strtolower(substr($line, 0, strpos($line, ' ')));
$line = substr($line, strpos($line, ' ') + 1);
//get rid of the quotes
$line = trim($line, '"');
switch ($category)
{
case 'catalog':
case 'cdtextfile':
case 'isrc':
case 'performer':
case 'songwriter':
case 'title':
if ($track_on == -1)
{
$this->cuesheet[$category] = $line;
}
else
{
$this->cuesheet['tracks'][$track_on][$category] = $line;
}
break;
default:
break;
}
}
/**
* Parses the TRACK command.
*
* @param string $line - The line in the cue file that contains the TRACK command.
* @param integer $track_on - The track currently processing.
*/
public function parseTrack($line, $track_on)
{
$line = substr($line, strpos($line, ' ') + 1);
$track = ltrim(substr($line, 0, strpos($line, ' ')), '0');
//find the data type.
$datatype = strtolower(substr($line, strpos($line, ' ') + 1));
$this->cuesheet['tracks'][$track_on] = array('track_number'=>$track, 'datatype'=>$datatype);
}
}
<?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.misc.exe.php //
// module for analyzing EXE files //
// dependencies: NONE //
// ///
/////////////////////////////////////////////////////////////////
if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
exit;
}
class getid3_exe extends getid3_handler
{
/**
* @return bool
*/
public function Analyze() {
$info = &$this->getid3->info;
$this->fseek($info['avdataoffset']);
$EXEheader = $this->fread(28);
$magic = 'MZ';
if (substr($EXEheader, 0, 2) != $magic) {
$this->error('Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes(substr($EXEheader, 0, 2)).'"');
return false;
}
$info['fileformat'] = 'exe';
$info['exe']['mz']['magic'] = 'MZ';
$info['exe']['mz']['raw']['last_page_size'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 2, 2));
$info['exe']['mz']['raw']['page_count'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 4, 2));
$info['exe']['mz']['raw']['relocation_count'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 6, 2));
$info['exe']['mz']['raw']['header_paragraphs'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 8, 2));
$info['exe']['mz']['raw']['min_memory_paragraphs'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 10, 2));
$info['exe']['mz']['raw']['max_memory_paragraphs'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 12, 2));
$info['exe']['mz']['raw']['initial_ss'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 14, 2));
$info['exe']['mz']['raw']['initial_sp'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 16, 2));
$info['exe']['mz']['raw']['checksum'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 18, 2));
$info['exe']['mz']['raw']['cs_ip'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 20, 4));
$info['exe']['mz']['raw']['relocation_table_offset'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 24, 2));
$info['exe']['mz']['raw']['overlay_number'] = getid3_lib::LittleEndian2Int(substr($EXEheader, 26, 2));
$info['exe']['mz']['byte_size'] = (($info['exe']['mz']['raw']['page_count'] - 1)) * 512 + $info['exe']['mz']['raw']['last_page_size'];
$info['exe']['mz']['header_size'] = $info['exe']['mz']['raw']['header_paragraphs'] * 16;
$info['exe']['mz']['memory_minimum'] = $info['exe']['mz']['raw']['min_memory_paragraphs'] * 16;
$info['exe']['mz']['memory_recommended'] = $info['exe']['mz']['raw']['max_memory_paragraphs'] * 16;
$this->error('EXE 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.misc.iso.php //
// module for analyzing ISO files //
// dependencies: NONE //
// ///
/////////////////////////////////////////////////////////////////
if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
exit;
}
class getid3_iso extends getid3_handler
{
/**
* @return bool
*/
public function Analyze() {
$info = &$this->getid3->info;
$info['fileformat'] = 'iso';
for ($i = 16; $i <= 19; $i++) {
$this->fseek(2048 * $i);
$ISOheader = $this->fread(2048);
if (substr($ISOheader, 1, 5) == 'CD001') {
switch (ord($ISOheader[0])) {
case 1:
$info['iso']['primary_volume_descriptor']['offset'] = 2048 * $i;
$this->ParsePrimaryVolumeDescriptor($ISOheader);
break;
case 2:
$info['iso']['supplementary_volume_descriptor']['offset'] = 2048 * $i;
$this->ParseSupplementaryVolumeDescriptor($ISOheader);
break;
default:
// skip
break;
}
}
}
$this->ParsePathTable();
$info['iso']['files'] = array();
foreach ($info['iso']['path_table']['directories'] as $directorynum => $directorydata) {
$info['iso']['directories'][$directorynum] = $this->ParseDirectoryRecord($directorydata);
}
return true;
}
/**
* @param string $ISOheader
*
* @return bool
*/
public function ParsePrimaryVolumeDescriptor(&$ISOheader) {
// ISO integer values are stored *BOTH* Little-Endian AND Big-Endian format!!
// ie 12345 == 0x3039 is stored as $39 $30 $30 $39 in a 4-byte field
// shortcuts
$info = &$this->getid3->info;
$info['iso']['primary_volume_descriptor']['raw'] = array();
$thisfile_iso_primaryVD = &$info['iso']['primary_volume_descriptor'];
$thisfile_iso_primaryVD_raw = &$thisfile_iso_primaryVD['raw'];
$thisfile_iso_primaryVD_raw['volume_descriptor_type'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 0, 1));
$thisfile_iso_primaryVD_raw['standard_identifier'] = substr($ISOheader, 1, 5);
if ($thisfile_iso_primaryVD_raw['standard_identifier'] != 'CD001') {
$this->error('Expected "CD001" at offset ('.($thisfile_iso_primaryVD['offset'] + 1).'), found "'.$thisfile_iso_primaryVD_raw['standard_identifier'].'" instead');
unset($info['fileformat']);
unset($info['iso']);
return false;
}
$thisfile_iso_primaryVD_raw['volume_descriptor_version'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 6, 1));
//$thisfile_iso_primaryVD_raw['unused_1'] = substr($ISOheader, 7, 1);
$thisfile_iso_primaryVD_raw['system_identifier'] = substr($ISOheader, 8, 32);
$thisfile_iso_primaryVD_raw['volume_identifier'] = substr($ISOheader, 40, 32);
//$thisfile_iso_primaryVD_raw['unused_2'] = substr($ISOheader, 72, 8);
$thisfile_iso_primaryVD_raw['volume_space_size'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 80, 4));
//$thisfile_iso_primaryVD_raw['unused_3'] = substr($ISOheader, 88, 32);
$thisfile_iso_primaryVD_raw['volume_set_size'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 120, 2));
$thisfile_iso_primaryVD_raw['volume_sequence_number'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 124, 2));
$thisfile_iso_primaryVD_raw['logical_block_size'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 128, 2));
$thisfile_iso_primaryVD_raw['path_table_size'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 132, 4));
$thisfile_iso_primaryVD_raw['path_table_l_location'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 140, 2));
$thisfile_iso_primaryVD_raw['path_table_l_opt_location'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 144, 2));
$thisfile_iso_primaryVD_raw['path_table_m_location'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 148, 2));
$thisfile_iso_primaryVD_raw['path_table_m_opt_location'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 152, 2));
$thisfile_iso_primaryVD_raw['root_directory_record'] = substr($ISOheader, 156, 34);
$thisfile_iso_primaryVD_raw['volume_set_identifier'] = substr($ISOheader, 190, 128);
$thisfile_iso_primaryVD_raw['publisher_identifier'] = substr($ISOheader, 318, 128);
$thisfile_iso_primaryVD_raw['data_preparer_identifier'] = substr($ISOheader, 446, 128);
$thisfile_iso_primaryVD_raw['application_identifier'] = substr($ISOheader, 574, 128);
$thisfile_iso_primaryVD_raw['copyright_file_identifier'] = substr($ISOheader, 702, 37);
$thisfile_iso_primaryVD_raw['abstract_file_identifier'] = substr($ISOheader, 739, 37);
$thisfile_iso_primaryVD_raw['bibliographic_file_identifier'] = substr($ISOheader, 776, 37);
$thisfile_iso_primaryVD_raw['volume_creation_date_time'] = substr($ISOheader, 813, 17);
$thisfile_iso_primaryVD_raw['volume_modification_date_time'] = substr($ISOheader, 830, 17);
$thisfile_iso_primaryVD_raw['volume_expiration_date_time'] = substr($ISOheader, 847, 17);
$thisfile_iso_primaryVD_raw['volume_effective_date_time'] = substr($ISOheader, 864, 17);
$thisfile_iso_primaryVD_raw['file_structure_version'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 881, 1));
//$thisfile_iso_primaryVD_raw['unused_4'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 882, 1));
$thisfile_iso_primaryVD_raw['application_data'] = substr($ISOheader, 883, 512);
//$thisfile_iso_primaryVD_raw['unused_5'] = substr($ISOheader, 1395, 653);
$thisfile_iso_primaryVD['system_identifier'] = trim($thisfile_iso_primaryVD_raw['system_identifier']);
$thisfile_iso_primaryVD['volume_identifier'] = trim($thisfile_iso_primaryVD_raw['volume_identifier']);
$thisfile_iso_primaryVD['volume_set_identifier'] = trim($thisfile_iso_primaryVD_raw['volume_set_identifier']);
$thisfile_iso_primaryVD['publisher_identifier'] = trim($thisfile_iso_primaryVD_raw['publisher_identifier']);
$thisfile_iso_primaryVD['data_preparer_identifier'] = trim($thisfile_iso_primaryVD_raw['data_preparer_identifier']);
$thisfile_iso_primaryVD['application_identifier'] = trim($thisfile_iso_primaryVD_raw['application_identifier']);
$thisfile_iso_primaryVD['copyright_file_identifier'] = trim($thisfile_iso_primaryVD_raw['copyright_file_identifier']);
$thisfile_iso_primaryVD['abstract_file_identifier'] = trim($thisfile_iso_primaryVD_raw['abstract_file_identifier']);
$thisfile_iso_primaryVD['bibliographic_file_identifier'] = trim($thisfile_iso_primaryVD_raw['bibliographic_file_identifier']);
$thisfile_iso_primaryVD['volume_creation_date_time'] = $this->ISOtimeText2UNIXtime($thisfile_iso_primaryVD_raw['volume_creation_date_time']);
$thisfile_iso_primaryVD['volume_modification_date_time'] = $this->ISOtimeText2UNIXtime($thisfile_iso_primaryVD_raw['volume_modification_date_time']);
$thisfile_iso_primaryVD['volume_expiration_date_time'] = $this->ISOtimeText2UNIXtime($thisfile_iso_primaryVD_raw['volume_expiration_date_time']);
$thisfile_iso_primaryVD['volume_effective_date_time'] = $this->ISOtimeText2UNIXtime($thisfile_iso_primaryVD_raw['volume_effective_date_time']);
if (($thisfile_iso_primaryVD_raw['volume_space_size'] * 2048) > $info['filesize']) {
$this->error('Volume Space Size ('.($thisfile_iso_primaryVD_raw['volume_space_size'] * 2048).' bytes) is larger than the file size ('.$info['filesize'].' bytes) (truncated file?)');
}
return true;
}
/**
* @param string $ISOheader
*
* @return bool
*/
public function ParseSupplementaryVolumeDescriptor(&$ISOheader) {
// ISO integer values are stored Both-Endian format!!
// ie 12345 == 0x3039 is stored as $39 $30 $30 $39 in a 4-byte field
// shortcuts
$info = &$this->getid3->info;
$info['iso']['supplementary_volume_descriptor']['raw'] = array();
$thisfile_iso_supplementaryVD = &$info['iso']['supplementary_volume_descriptor'];
$thisfile_iso_supplementaryVD_raw = &$thisfile_iso_supplementaryVD['raw'];
$thisfile_iso_supplementaryVD_raw['volume_descriptor_type'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 0, 1));
$thisfile_iso_supplementaryVD_raw['standard_identifier'] = substr($ISOheader, 1, 5);
if ($thisfile_iso_supplementaryVD_raw['standard_identifier'] != 'CD001') {
$this->error('Expected "CD001" at offset ('.($thisfile_iso_supplementaryVD['offset'] + 1).'), found "'.$thisfile_iso_supplementaryVD_raw['standard_identifier'].'" instead');
unset($info['fileformat']);
unset($info['iso']);
return false;
}
$thisfile_iso_supplementaryVD_raw['volume_descriptor_version'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 6, 1));
//$thisfile_iso_supplementaryVD_raw['unused_1'] = substr($ISOheader, 7, 1);
$thisfile_iso_supplementaryVD_raw['system_identifier'] = substr($ISOheader, 8, 32);
$thisfile_iso_supplementaryVD_raw['volume_identifier'] = substr($ISOheader, 40, 32);
//$thisfile_iso_supplementaryVD_raw['unused_2'] = substr($ISOheader, 72, 8);
$thisfile_iso_supplementaryVD_raw['volume_space_size'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 80, 4));
if ($thisfile_iso_supplementaryVD_raw['volume_space_size'] == 0) {
// Supplementary Volume Descriptor not used
//unset($thisfile_iso_supplementaryVD);
//return false;
}
//$thisfile_iso_supplementaryVD_raw['unused_3'] = substr($ISOheader, 88, 32);
$thisfile_iso_supplementaryVD_raw['volume_set_size'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 120, 2));
$thisfile_iso_supplementaryVD_raw['volume_sequence_number'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 124, 2));
$thisfile_iso_supplementaryVD_raw['logical_block_size'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 128, 2));
$thisfile_iso_supplementaryVD_raw['path_table_size'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 132, 4));
$thisfile_iso_supplementaryVD_raw['path_table_l_location'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 140, 2));
$thisfile_iso_supplementaryVD_raw['path_table_l_opt_location'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 144, 2));
$thisfile_iso_supplementaryVD_raw['path_table_m_location'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 148, 2));
$thisfile_iso_supplementaryVD_raw['path_table_m_opt_location'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 152, 2));
$thisfile_iso_supplementaryVD_raw['root_directory_record'] = substr($ISOheader, 156, 34);
$thisfile_iso_supplementaryVD_raw['volume_set_identifier'] = substr($ISOheader, 190, 128);
$thisfile_iso_supplementaryVD_raw['publisher_identifier'] = substr($ISOheader, 318, 128);
$thisfile_iso_supplementaryVD_raw['data_preparer_identifier'] = substr($ISOheader, 446, 128);
$thisfile_iso_supplementaryVD_raw['application_identifier'] = substr($ISOheader, 574, 128);
$thisfile_iso_supplementaryVD_raw['copyright_file_identifier'] = substr($ISOheader, 702, 37);
$thisfile_iso_supplementaryVD_raw['abstract_file_identifier'] = substr($ISOheader, 739, 37);
$thisfile_iso_supplementaryVD_raw['bibliographic_file_identifier'] = substr($ISOheader, 776, 37);
$thisfile_iso_supplementaryVD_raw['volume_creation_date_time'] = substr($ISOheader, 813, 17);
$thisfile_iso_supplementaryVD_raw['volume_modification_date_time'] = substr($ISOheader, 830, 17);
$thisfile_iso_supplementaryVD_raw['volume_expiration_date_time'] = substr($ISOheader, 847, 17);
$thisfile_iso_supplementaryVD_raw['volume_effective_date_time'] = substr($ISOheader, 864, 17);
$thisfile_iso_supplementaryVD_raw['file_structure_version'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 881, 1));
//$thisfile_iso_supplementaryVD_raw['unused_4'] = getid3_lib::LittleEndian2Int(substr($ISOheader, 882, 1));
$thisfile_iso_supplementaryVD_raw['application_data'] = substr($ISOheader, 883, 512);
//$thisfile_iso_supplementaryVD_raw['unused_5'] = substr($ISOheader, 1395, 653);
$thisfile_iso_supplementaryVD['system_identifier'] = trim($thisfile_iso_supplementaryVD_raw['system_identifier']);
$thisfile_iso_supplementaryVD['volume_identifier'] = trim($thisfile_iso_supplementaryVD_raw['volume_identifier']);
$thisfile_iso_supplementaryVD['volume_set_identifier'] = trim($thisfile_iso_supplementaryVD_raw['volume_set_identifier']);
$thisfile_iso_supplementaryVD['publisher_identifier'] = trim($thisfile_iso_supplementaryVD_raw['publisher_identifier']);
$thisfile_iso_supplementaryVD['data_preparer_identifier'] = trim($thisfile_iso_supplementaryVD_raw['data_preparer_identifier']);
$thisfile_iso_supplementaryVD['application_identifier'] = trim($thisfile_iso_supplementaryVD_raw['application_identifier']);
$thisfile_iso_supplementaryVD['copyright_file_identifier'] = trim($thisfile_iso_supplementaryVD_raw['copyright_file_identifier']);
$thisfile_iso_supplementaryVD['abstract_file_identifier'] = trim($thisfile_iso_supplementaryVD_raw['abstract_file_identifier']);
$thisfile_iso_supplementaryVD['bibliographic_file_identifier'] = trim($thisfile_iso_supplementaryVD_raw['bibliographic_file_identifier']);
$thisfile_iso_supplementaryVD['volume_creation_date_time'] = $this->ISOtimeText2UNIXtime($thisfile_iso_supplementaryVD_raw['volume_creation_date_time']);
$thisfile_iso_supplementaryVD['volume_modification_date_time'] = $this->ISOtimeText2UNIXtime($thisfile_iso_supplementaryVD_raw['volume_modification_date_time']);
$thisfile_iso_supplementaryVD['volume_expiration_date_time'] = $this->ISOtimeText2UNIXtime($thisfile_iso_supplementaryVD_raw['volume_expiration_date_time']);
$thisfile_iso_supplementaryVD['volume_effective_date_time'] = $this->ISOtimeText2UNIXtime($thisfile_iso_supplementaryVD_raw['volume_effective_date_time']);
if (($thisfile_iso_supplementaryVD_raw['volume_space_size'] * $thisfile_iso_supplementaryVD_raw['logical_block_size']) > $info['filesize']) {
$this->error('Volume Space Size ('.($thisfile_iso_supplementaryVD_raw['volume_space_size'] * $thisfile_iso_supplementaryVD_raw['logical_block_size']).' bytes) is larger than the file size ('.$info['filesize'].' bytes) (truncated file?)');
}
return true;
}
/**
* @return bool
*/
public function ParsePathTable() {
$info = &$this->getid3->info;
if (!isset($info['iso']['supplementary_volume_descriptor']['raw']['path_table_l_location']) && !isset($info['iso']['primary_volume_descriptor']['raw']['path_table_l_location'])) {
return false;
}
if (isset($info['iso']['supplementary_volume_descriptor']['raw']['path_table_l_location'])) {
$PathTableLocation = $info['iso']['supplementary_volume_descriptor']['raw']['path_table_l_location'];
$PathTableSize = $info['iso']['supplementary_volume_descriptor']['raw']['path_table_size'];
$TextEncoding = 'UTF-16BE'; // Big-Endian Unicode
} else {
$PathTableLocation = $info['iso']['primary_volume_descriptor']['raw']['path_table_l_location'];
$PathTableSize = $info['iso']['primary_volume_descriptor']['raw']['path_table_size'];
$TextEncoding = 'ISO-8859-1'; // Latin-1
}
if (($PathTableLocation * 2048) > $info['filesize']) {
$this->error('Path Table Location specifies an offset ('.($PathTableLocation * 2048).') beyond the end-of-file ('.$info['filesize'].')');
return false;
}
$info['iso']['path_table']['offset'] = $PathTableLocation * 2048;
$this->fseek($info['iso']['path_table']['offset']);
$info['iso']['path_table']['raw'] = $this->fread($PathTableSize);
$offset = 0;
$pathcounter = 1;
while ($offset < $PathTableSize) {
// shortcut
$info['iso']['path_table']['directories'][$pathcounter] = array();
$thisfile_iso_pathtable_directories_current = &$info['iso']['path_table']['directories'][$pathcounter];
$thisfile_iso_pathtable_directories_current['length'] = getid3_lib::LittleEndian2Int(substr($info['iso']['path_table']['raw'], $offset, 1));
$offset += 1;
$thisfile_iso_pathtable_directories_current['extended_length'] = getid3_lib::LittleEndian2Int(substr($info['iso']['path_table']['raw'], $offset, 1));
$offset += 1;
$thisfile_iso_pathtable_directories_current['location_logical'] = getid3_lib::LittleEndian2Int(substr($info['iso']['path_table']['raw'], $offset, 4));
$offset += 4;
$thisfile_iso_pathtable_directories_current['parent_directory'] = getid3_lib::LittleEndian2Int(substr($info['iso']['path_table']['raw'], $offset, 2));
$offset += 2;
$thisfile_iso_pathtable_directories_current['name'] = substr($info['iso']['path_table']['raw'], $offset, $thisfile_iso_pathtable_directories_current['length']);
$offset += $thisfile_iso_pathtable_directories_current['length'] + ($thisfile_iso_pathtable_directories_current['length'] % 2);
$thisfile_iso_pathtable_directories_current['name_ascii'] = getid3_lib::iconv_fallback($TextEncoding, $info['encoding'], $thisfile_iso_pathtable_directories_current['name']);
$thisfile_iso_pathtable_directories_current['location_bytes'] = $thisfile_iso_pathtable_directories_current['location_logical'] * 2048;
if ($pathcounter == 1) {
$thisfile_iso_pathtable_directories_current['full_path'] = '/';
} else {
$thisfile_iso_pathtable_directories_current['full_path'] = $info['iso']['path_table']['directories'][$thisfile_iso_pathtable_directories_current['parent_directory']]['full_path'].$thisfile_iso_pathtable_directories_current['name_ascii'].'/';
}
$FullPathArray[] = $thisfile_iso_pathtable_directories_current['full_path'];
$pathcounter++;
}
return true;
}
/**
* @param array $directorydata
*
* @return array
*/
public function ParseDirectoryRecord($directorydata) {
$info = &$this->getid3->info;
if (isset($info['iso']['supplementary_volume_descriptor'])) {
$TextEncoding = 'UTF-16BE'; // Big-Endian Unicode
} else {
$TextEncoding = 'ISO-8859-1'; // Latin-1
}
$this->fseek($directorydata['location_bytes']);
$DirectoryRecordData = $this->fread(1);
$DirectoryRecord = array();
while (ord($DirectoryRecordData[0]) > 33) {
$DirectoryRecordData .= $this->fread(ord($DirectoryRecordData[0]) - 1);
$ThisDirectoryRecord = array();
$ThisDirectoryRecord['raw']['length'] = getid3_lib::LittleEndian2Int(substr($DirectoryRecordData, 0, 1));
$ThisDirectoryRecord['raw']['extended_attribute_length'] = getid3_lib::LittleEndian2Int(substr($DirectoryRecordData, 1, 1));
$ThisDirectoryRecord['raw']['offset_logical'] = getid3_lib::LittleEndian2Int(substr($DirectoryRecordData, 2, 4));
$ThisDirectoryRecord['raw']['filesize'] = getid3_lib::LittleEndian2Int(substr($DirectoryRecordData, 10, 4));
$ThisDirectoryRecord['raw']['recording_date_time'] = substr($DirectoryRecordData, 18, 7);
$ThisDirectoryRecord['raw']['file_flags'] = getid3_lib::LittleEndian2Int(substr($DirectoryRecordData, 25, 1));
$ThisDirectoryRecord['raw']['file_unit_size'] = getid3_lib::LittleEndian2Int(substr($DirectoryRecordData, 26, 1));
$ThisDirectoryRecord['raw']['interleave_gap_size'] = getid3_lib::LittleEndian2Int(substr($DirectoryRecordData, 27, 1));
$ThisDirectoryRecord['raw']['volume_sequence_number'] = getid3_lib::LittleEndian2Int(substr($DirectoryRecordData, 28, 2));
$ThisDirectoryRecord['raw']['file_identifier_length'] = getid3_lib::LittleEndian2Int(substr($DirectoryRecordData, 32, 1));
$ThisDirectoryRecord['raw']['file_identifier'] = substr($DirectoryRecordData, 33, $ThisDirectoryRecord['raw']['file_identifier_length']);
$ThisDirectoryRecord['file_identifier_ascii'] = getid3_lib::iconv_fallback($TextEncoding, $info['encoding'], $ThisDirectoryRecord['raw']['file_identifier']);
$ThisDirectoryRecord['filesize'] = $ThisDirectoryRecord['raw']['filesize'];
$ThisDirectoryRecord['offset_bytes'] = $ThisDirectoryRecord['raw']['offset_logical'] * 2048;
$ThisDirectoryRecord['file_flags']['hidden'] = (bool) ($ThisDirectoryRecord['raw']['file_flags'] & 0x01);
$ThisDirectoryRecord['file_flags']['directory'] = (bool) ($ThisDirectoryRecord['raw']['file_flags'] & 0x02);
$ThisDirectoryRecord['file_flags']['associated'] = (bool) ($ThisDirectoryRecord['raw']['file_flags'] & 0x04);
$ThisDirectoryRecord['file_flags']['extended'] = (bool) ($ThisDirectoryRecord['raw']['file_flags'] & 0x08);
$ThisDirectoryRecord['file_flags']['permissions'] = (bool) ($ThisDirectoryRecord['raw']['file_flags'] & 0x10);
$ThisDirectoryRecord['file_flags']['multiple'] = (bool) ($ThisDirectoryRecord['raw']['file_flags'] & 0x80);
$ThisDirectoryRecord['recording_timestamp'] = $this->ISOtime2UNIXtime($ThisDirectoryRecord['raw']['recording_date_time']);
if ($ThisDirectoryRecord['file_flags']['directory']) {
$ThisDirectoryRecord['filename'] = $directorydata['full_path'];
} else {
$ThisDirectoryRecord['filename'] = $directorydata['full_path'].$this->ISOstripFilenameVersion($ThisDirectoryRecord['file_identifier_ascii']);
$info['iso']['files'] = getid3_lib::array_merge_clobber($info['iso']['files'], getid3_lib::CreateDeepArray($ThisDirectoryRecord['filename'], '/', $ThisDirectoryRecord['filesize']));
}
$DirectoryRecord[] = $ThisDirectoryRecord;
$DirectoryRecordData = $this->fread(1);
}
return $DirectoryRecord;
}
/**
* @param string $ISOfilename
*
* @return string
*/
public function ISOstripFilenameVersion($ISOfilename) {
// convert 'filename.ext;1' to 'filename.ext'
if (!strstr($ISOfilename, ';')) {
return $ISOfilename;
} else {
return substr($ISOfilename, 0, strpos($ISOfilename, ';'));
}
}
/**
* @param string $ISOtime
*
* @return int|false
*/
public function ISOtimeText2UNIXtime($ISOtime) {
$UNIXyear = (int) substr($ISOtime, 0, 4);
$UNIXmonth = (int) substr($ISOtime, 4, 2);
$UNIXday = (int) substr($ISOtime, 6, 2);
$UNIXhour = (int) substr($ISOtime, 8, 2);
$UNIXminute = (int) substr($ISOtime, 10, 2);
$UNIXsecond = (int) substr($ISOtime, 12, 2);
if (!$UNIXyear) {
return false;
}
return gmmktime($UNIXhour, $UNIXminute, $UNIXsecond, $UNIXmonth, $UNIXday, $UNIXyear);
}
/**
* @param string $ISOtime
*
* @return int
*/
public function ISOtime2UNIXtime($ISOtime) {
// Represented by seven bytes:
// 1: Number of years since 1900
// 2: Month of the year from 1 to 12
// 3: Day of the Month from 1 to 31
// 4: Hour of the day from 0 to 23
// 5: Minute of the hour from 0 to 59
// 6: second of the minute from 0 to 59
// 7: Offset from Greenwich Mean Time in number of 15 minute intervals from -48 (West) to +52 (East)
$UNIXyear = ord($ISOtime[0]) + 1900;
$UNIXmonth = ord($ISOtime[1]);
$UNIXday = ord($ISOtime[2]);
$UNIXhour = ord($ISOtime[3]);
$UNIXminute = ord($ISOtime[4]);
$UNIXsecond = ord($ISOtime[5]);
$GMToffset = $this->TwosCompliment2Decimal(ord($ISOtime[5]));
return gmmktime($UNIXhour, $UNIXminute, $UNIXsecond, $UNIXmonth, $UNIXday, $UNIXyear);
}
/**
* @param int $BinaryValue
*
* @return int
*/
public function TwosCompliment2Decimal($BinaryValue) {
// http://sandbox.mc.edu/~bennet/cs110/tc/tctod.html
// First check if the number is negative or positive by looking at the sign bit.
// If it is positive, simply convert it to decimal.
// If it is negative, make it positive by inverting the bits and adding one.
// Then, convert the result to decimal.
// The negative of this number is the value of the original binary.
if ($BinaryValue & 0x80) {
// negative number
return (0 - ((~$BinaryValue & 0xFF) + 1));
} else {
// positive number
return $BinaryValue;
}
}
}
<?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.misc.msoffice.php //
// module for analyzing MS Office (.doc, .xls, etc) files //
// dependencies: NONE //
// ///
/////////////////////////////////////////////////////////////////
if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
exit;
}
class getid3_msoffice extends getid3_handler
{
/**
* @return bool
*/
public function Analyze() {
$info = &$this->getid3->info;
$this->fseek($info['avdataoffset']);
$DOCFILEheader = $this->fread(8);
$magic = "\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1";
if (substr($DOCFILEheader, 0, 8) != $magic) {
$this->error('Expecting "'.getid3_lib::PrintHexBytes($magic).'" at '.$info['avdataoffset'].', found '.getid3_lib::PrintHexBytes(substr($DOCFILEheader, 0, 8)).' instead.');
return false;
}
$info['fileformat'] = 'msoffice';
$this->error('MS Office (.doc, .xls, etc) 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.misc.par2.php //
// module for analyzing PAR2 files //
// dependencies: NONE //
// ///
/////////////////////////////////////////////////////////////////
if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
exit;
}
class getid3_par2 extends getid3_handler
{
/**
* @return bool
*/
public function Analyze() {
$info = &$this->getid3->info;
$info['fileformat'] = 'par2';
$this->error('PAR2 parsing not enabled in this version of getID3()');
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.misc.pdf.php //
// module for analyzing PDF files //
// dependencies: NONE //
// ///
/////////////////////////////////////////////////////////////////
if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
exit;
}
class getid3_pdf extends getid3_handler
{
public $returnXREF = false; // return full details of PDF Cross-Reference Table (XREF)
/**
* @return bool
*/
public function Analyze() {
$info = &$this->getid3->info;
$this->fseek(0);
if (preg_match('#^%PDF-([0-9\\.]+)$#', rtrim($this->fgets()), $matches)) {
$info['pdf']['header']['version'] = floatval($matches[1]);
$info['fileformat'] = 'pdf';
// the PDF Cross-Reference Table (XREF) is located near the end of the file
// the starting offset is specified in the penultimate section, on the two lines just before "%%EOF"
// the first line is "startxref", the second line is the byte offset of the XREF.
// We know the length of "%%EOF" and "startxref", but the offset could be 2-10 bytes,
// and we're not sure if the line ends are one or two bytes, so we might find "startxref" as little as 18(?) bytes
// from EOF, but it could 30 bytes, so we start 40 bytes back just to be safe and do a search for the data we want.
$this->fseek(-40, SEEK_END);
if (preg_match('#[\r\n]startxref[ \r\n]+([0-9]+)[ \r\n]+#', $this->fread(40), $matches)) {
$info['pdf']['trailer']['startxref'] = intval($matches[1]);
$this->parseXREF($info['pdf']['trailer']['startxref']);
if (!empty($info['pdf']['xref']['offset'])) {
while (!$this->feof() && (max(array_keys($info['pdf']['xref']['offset'])) > $info['pdf']['xref']['count'])) {
// suspect that there may be another XREF entry somewhere in the file, brute-force scan for it
/*
// starting at last known entry of main XREF table
$this->fseek(max($info['pdf']['xref']['offset']));
*/
// starting at the beginning of the file
$this->fseek(0);
while (!$this->feof()) {
$XREFoffset = $this->ftell();
if (rtrim($this->fgets()) == 'xref') {
if (empty($info['pdf']['xref']['xref_offsets']) || !in_array($XREFoffset, $info['pdf']['xref']['xref_offsets'])) {
$this->parseXREF($XREFoffset);
break;
}
}
}
}
foreach ($info['pdf']['xref']['offset'] as $objectNumber => $offset) {
if ($info['pdf']['xref']['entry'][$objectNumber] == 'f') {
// "free" object means "deleted", ignore
continue;
}
$this->fseek($offset);
$line = rtrim($this->fgets());
if (preg_match('#^'.$objectNumber.' ([0-9]+) obj#', $line, $matches)) {
if (strlen($line) > strlen($matches[0])) {
// object header line not actually on its own line, rewind file pointer to start reading data
$this->fseek($offset + strlen($matches[0]));
}
$objectData = '';
while (true) {
$line = $this->fgets();
if (rtrim($line) == 'endobj') {
break;
}
$objectData .= $line;
}
if (preg_match('#^<<[\r\n\s]*(/Type|/Pages|/Parent [0-9]+ [0-9]+ [A-Z]|/Count [0-9]+|/Kids *\\[[0-9A-Z ]+\\]|[\r\n\s])+[\r\n\s]*>>#', $objectData, $matches)) {
if (preg_match('#/Count ([0-9]+)#', $objectData, $matches)) {
$info['pdf']['pages'] = (int) $matches[1];
break; // for now this is the only data we're looking for in the PDF not need to loop through every object in the file (and a large PDF may contain MANY objects). And it MAY be possible that there are other objects elsewhere in the file that define additional (or removed?) pages
}
}
} else {
$this->error('Unexpected structure "'.$line.'" at offset '.$offset);
break;
}
}
if (!$this->returnXREF) {
unset($info['pdf']['xref']['offset'], $info['pdf']['xref']['generation'], $info['pdf']['xref']['entry']);
}
} else {
$this->error('Did not find "xref" at offset '.$info['pdf']['trailer']['startxref']);
}
} else {
$this->error('Did not find "startxref" in the last 40 bytes of the PDF');
}
$this->warning('PDF parsing incomplete in this version of getID3() ['.$this->getid3->version().']');
return true;
}
$this->error('Did not find "%PDF" at the beginning of the PDF');
return false;
}
/**
* @return bool
*/
private function parseXREF($XREFoffset) {
$info = &$this->getid3->info;
$this->fseek($XREFoffset);
if (rtrim($this->fgets()) == 'xref') {
$info['pdf']['xref']['xref_offsets'][$XREFoffset] = $XREFoffset;
list($firstObjectNumber, $XREFcount) = explode(' ', rtrim($this->fgets()));
$XREFcount = (int) $XREFcount;
$info['pdf']['xref']['count'] = $XREFcount + (!empty($info['pdf']['xref']['count']) ? $info['pdf']['xref']['count'] : 0);
for ($i = 0; $i < $XREFcount; $i++) {
$line = rtrim($this->fgets());
if (preg_match('#^([0-9]+) ([0-9]+) ([nf])$#', $line, $matches)) {
$info['pdf']['xref']['offset'][($firstObjectNumber + $i)] = (int) $matches[1];
$info['pdf']['xref']['generation'][($firstObjectNumber + $i)] = (int) $matches[2];
$info['pdf']['xref']['entry'][($firstObjectNumber + $i)] = $matches[3];
} else {
$this->error('failed to parse XREF entry #'.$i.' in XREF table at offset '.$XREFoffset);
return false;
}
}
sort($info['pdf']['xref']['xref_offsets']);
return true;
}
$this->warning('failed to find expected XREF structure at offset '.$XREFoffset);
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.tag.apetag.php //
// module for analyzing APE tags //
// dependencies: NONE //
// ///
/////////////////////////////////////////////////////////////////
if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
exit;
}
class getid3_apetag extends getid3_handler
{
/**
* true: return full data for all attachments;
* false: return no data for all attachments;
* integer: return data for attachments <= than this;
* string: save as file to this directory.
*
* @var int|bool|string
*/
public $inline_attachments = true;
public $overrideendoffset = 0;
/**
* @return bool
*/
public function Analyze() {
$info = &$this->getid3->info;
if (!getid3_lib::intValueSupported($info['filesize'])) {
$this->warning('Unable to check for APEtags because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB');
return false;
}
$id3v1tagsize = 128;
$apetagheadersize = 32;
$lyrics3tagsize = 10;
if ($this->overrideendoffset == 0) {
$this->fseek(0 - $id3v1tagsize - $apetagheadersize - $lyrics3tagsize, SEEK_END);
$APEfooterID3v1 = $this->fread($id3v1tagsize + $apetagheadersize + $lyrics3tagsize);
//if (preg_match('/APETAGEX.{24}TAG.{125}$/i', $APEfooterID3v1)) {
if (substr($APEfooterID3v1, strlen($APEfooterID3v1) - $id3v1tagsize - $apetagheadersize, 8) == 'APETAGEX') {
// APE tag found before ID3v1
$info['ape']['tag_offset_end'] = $info['filesize'] - $id3v1tagsize;
//} elseif (preg_match('/APETAGEX.{24}$/i', $APEfooterID3v1)) {
} elseif (substr($APEfooterID3v1, strlen($APEfooterID3v1) - $apetagheadersize, 8) == 'APETAGEX') {
// APE tag found, no ID3v1
$info['ape']['tag_offset_end'] = $info['filesize'];
}
} else {
$this->fseek($this->overrideendoffset - $apetagheadersize);
if ($this->fread(8) == 'APETAGEX') {
$info['ape']['tag_offset_end'] = $this->overrideendoffset;
}
}
if (!isset($info['ape']['tag_offset_end'])) {
// APE tag not found
unset($info['ape']);
return false;
}
// shortcut
$thisfile_ape = &$info['ape'];
$this->fseek($thisfile_ape['tag_offset_end'] - $apetagheadersize);
$APEfooterData = $this->fread(32);
if (!($thisfile_ape['footer'] = $this->parseAPEheaderFooter($APEfooterData))) {
$this->error('Error parsing APE footer at offset '.$thisfile_ape['tag_offset_end']);
return false;
}
if (isset($thisfile_ape['footer']['flags']['header']) && $thisfile_ape['footer']['flags']['header']) {
$this->fseek($thisfile_ape['tag_offset_end'] - $thisfile_ape['footer']['raw']['tagsize'] - $apetagheadersize);
$thisfile_ape['tag_offset_start'] = $this->ftell();
$APEtagData = $this->fread($thisfile_ape['footer']['raw']['tagsize'] + $apetagheadersize);
} else {
$thisfile_ape['tag_offset_start'] = $thisfile_ape['tag_offset_end'] - $thisfile_ape['footer']['raw']['tagsize'];
$this->fseek($thisfile_ape['tag_offset_start']);
$APEtagData = $this->fread($thisfile_ape['footer']['raw']['tagsize']);
}
$info['avdataend'] = $thisfile_ape['tag_offset_start'];
if (isset($info['id3v1']['tag_offset_start']) && ($info['id3v1']['tag_offset_start'] < $thisfile_ape['tag_offset_end'])) {
$this->warning('ID3v1 tag information ignored since it appears to be a false synch in APEtag data');
unset($info['id3v1']);
foreach ($info['warning'] as $key => $value) {
if ($value == 'Some ID3v1 fields do not use NULL characters for padding') {
unset($info['warning'][$key]);
sort($info['warning']);
break;
}
}
}
$offset = 0;
if (isset($thisfile_ape['footer']['flags']['header']) && $thisfile_ape['footer']['flags']['header']) {
if ($thisfile_ape['header'] = $this->parseAPEheaderFooter(substr($APEtagData, 0, $apetagheadersize))) {
$offset += $apetagheadersize;
} else {
$this->error('Error parsing APE header at offset '.$thisfile_ape['tag_offset_start']);
return false;
}
}
// shortcut
$info['replay_gain'] = array();
$thisfile_replaygain = &$info['replay_gain'];
for ($i = 0; $i < $thisfile_ape['footer']['raw']['tag_items']; $i++) {
$value_size = getid3_lib::LittleEndian2Int(substr($APEtagData, $offset, 4));
$offset += 4;
$item_flags = getid3_lib::LittleEndian2Int(substr($APEtagData, $offset, 4));
$offset += 4;
if (strstr(substr($APEtagData, $offset), "\x00") === false) {
$this->error('Cannot find null-byte (0x00) separator between ItemKey #'.$i.' and value. ItemKey starts '.$offset.' bytes into the APE tag, at file offset '.($thisfile_ape['tag_offset_start'] + $offset));
return false;
}
$ItemKeyLength = strpos($APEtagData, "\x00", $offset) - $offset;
$item_key = strtolower(substr($APEtagData, $offset, $ItemKeyLength));
// shortcut
$thisfile_ape['items'][$item_key] = array();
$thisfile_ape_items_current = &$thisfile_ape['items'][$item_key];
$thisfile_ape_items_current['offset'] = $thisfile_ape['tag_offset_start'] + $offset;
$offset += ($ItemKeyLength + 1); // skip 0x00 terminator
$thisfile_ape_items_current['data'] = substr($APEtagData, $offset, $value_size);
$offset += $value_size;
$thisfile_ape_items_current['flags'] = $this->parseAPEtagFlags($item_flags);
switch ($thisfile_ape_items_current['flags']['item_contents_raw']) {
case 0: // UTF-8
case 2: // Locator (URL, filename, etc), UTF-8 encoded
$thisfile_ape_items_current['data'] = explode("\x00", $thisfile_ape_items_current['data']);
break;
case 1: // binary data
default:
break;
}
switch (strtolower($item_key)) {
// http://wiki.hydrogenaud.io/index.php?title=ReplayGain#MP3Gain
case 'replaygain_track_gain':
if (preg_match('#^([\\-\\+][0-9\\.,]{8})( dB)?$#', $thisfile_ape_items_current['data'][0], $matches)) {
$thisfile_replaygain['track']['adjustment'] = (float) str_replace(',', '.', $matches[1]); // float casting will see "0,95" as zero!
$thisfile_replaygain['track']['originator'] = 'unspecified';
} else {
$this->warning('MP3gainTrackGain value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"');
}
break;
case 'replaygain_track_peak':
if (preg_match('#^([0-9\\.,]{8})$#', $thisfile_ape_items_current['data'][0], $matches)) {
$thisfile_replaygain['track']['peak'] = (float) str_replace(',', '.', $matches[1]); // float casting will see "0,95" as zero!
$thisfile_replaygain['track']['originator'] = 'unspecified';
if ($thisfile_replaygain['track']['peak'] <= 0) {
$this->warning('ReplayGain Track peak from APEtag appears invalid: '.$thisfile_replaygain['track']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")');
}
} else {
$this->warning('MP3gainTrackPeak value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"');
}
break;
case 'replaygain_album_gain':
if (preg_match('#^([\\-\\+][0-9\\.,]{8})( dB)?$#', $thisfile_ape_items_current['data'][0], $matches)) {
$thisfile_replaygain['album']['adjustment'] = (float) str_replace(',', '.', $matches[1]); // float casting will see "0,95" as zero!
$thisfile_replaygain['album']['originator'] = 'unspecified';
} else {
$this->warning('MP3gainAlbumGain value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"');
}
break;
case 'replaygain_album_peak':
if (preg_match('#^([0-9\\.,]{8})$#', $thisfile_ape_items_current['data'][0], $matches)) {
$thisfile_replaygain['album']['peak'] = (float) str_replace(',', '.', $matches[1]); // float casting will see "0,95" as zero!
$thisfile_replaygain['album']['originator'] = 'unspecified';
if ($thisfile_replaygain['album']['peak'] <= 0) {
$this->warning('ReplayGain Album peak from APEtag appears invalid: '.$thisfile_replaygain['album']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")');
}
} else {
$this->warning('MP3gainAlbumPeak value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"');
}
break;
case 'mp3gain_undo':
if (preg_match('#^[\\-\\+][0-9]{3},[\\-\\+][0-9]{3},[NW]$#', $thisfile_ape_items_current['data'][0])) {
list($mp3gain_undo_left, $mp3gain_undo_right, $mp3gain_undo_wrap) = explode(',', $thisfile_ape_items_current['data'][0]);
$thisfile_replaygain['mp3gain']['undo_left'] = intval($mp3gain_undo_left);
$thisfile_replaygain['mp3gain']['undo_right'] = intval($mp3gain_undo_right);
$thisfile_replaygain['mp3gain']['undo_wrap'] = (($mp3gain_undo_wrap == 'Y') ? true : false);
} else {
$this->warning('MP3gainUndo value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"');
}
break;
case 'mp3gain_minmax':
if (preg_match('#^[0-9]{3},[0-9]{3}$#', $thisfile_ape_items_current['data'][0])) {
list($mp3gain_globalgain_min, $mp3gain_globalgain_max) = explode(',', $thisfile_ape_items_current['data'][0]);
$thisfile_replaygain['mp3gain']['globalgain_track_min'] = intval($mp3gain_globalgain_min);
$thisfile_replaygain['mp3gain']['globalgain_track_max'] = intval($mp3gain_globalgain_max);
} else {
$this->warning('MP3gainMinMax value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"');
}
break;
case 'mp3gain_album_minmax':
if (preg_match('#^[0-9]{3},[0-9]{3}$#', $thisfile_ape_items_current['data'][0])) {
list($mp3gain_globalgain_album_min, $mp3gain_globalgain_album_max) = explode(',', $thisfile_ape_items_current['data'][0]);
$thisfile_replaygain['mp3gain']['globalgain_album_min'] = intval($mp3gain_globalgain_album_min);
$thisfile_replaygain['mp3gain']['globalgain_album_max'] = intval($mp3gain_globalgain_album_max);
} else {
$this->warning('MP3gainAlbumMinMax value in APEtag appears invalid: "'.$thisfile_ape_items_current['data'][0].'"');
}
break;
case 'tracknumber':
if (is_array($thisfile_ape_items_current['data'])) {
foreach ($thisfile_ape_items_current['data'] as $comment) {
$thisfile_ape['comments']['track_number'][] = $comment;
}
}
break;
case 'cover art (artist)':
case 'cover art (back)':
case 'cover art (band logo)':
case 'cover art (band)':
case 'cover art (colored fish)':
case 'cover art (composer)':
case 'cover art (conductor)':
case 'cover art (front)':
case 'cover art (icon)':
case 'cover art (illustration)':
case 'cover art (lead)':
case 'cover art (leaflet)':
case 'cover art (lyricist)':
case 'cover art (media)':
case 'cover art (movie scene)':
case 'cover art (other icon)':
case 'cover art (other)':
case 'cover art (performance)':
case 'cover art (publisher logo)':
case 'cover art (recording)':
case 'cover art (studio)':
// list of possible cover arts from http://taglib-sharp.sourcearchive.com/documentation/2.0.3.0-2/Ape_2Tag_8cs-source.html
if (is_array($thisfile_ape_items_current['data'])) {
$this->warning('APEtag "'.$item_key.'" should be flagged as Binary data, but was incorrectly flagged as UTF-8');
$thisfile_ape_items_current['data'] = implode("\x00", $thisfile_ape_items_current['data']);
}
list($thisfile_ape_items_current['filename'], $thisfile_ape_items_current['data']) = explode("\x00", $thisfile_ape_items_current['data'], 2);
$thisfile_ape_items_current['data_offset'] = $thisfile_ape_items_current['offset'] + strlen($thisfile_ape_items_current['filename']."\x00");
$thisfile_ape_items_current['data_length'] = strlen($thisfile_ape_items_current['data']);
do {
$thisfile_ape_items_current['image_mime'] = '';
$imageinfo = array();
$imagechunkcheck = getid3_lib::GetDataImageSize($thisfile_ape_items_current['data'], $imageinfo);
if (($imagechunkcheck === false) || !isset($imagechunkcheck[2])) {
$this->warning('APEtag "'.$item_key.'" contains invalid image data');
break;
}
$thisfile_ape_items_current['image_mime'] = image_type_to_mime_type($imagechunkcheck[2]);
if ($this->inline_attachments === false) {
// skip entirely
unset($thisfile_ape_items_current['data']);
break;
}
if ($this->inline_attachments === true) {
// great
} elseif (is_int($this->inline_attachments)) {
if ($this->inline_attachments < $thisfile_ape_items_current['data_length']) {
// too big, skip
$this->warning('attachment at '.$thisfile_ape_items_current['offset'].' is too large to process inline ('.number_format($thisfile_ape_items_current['data_length']).' bytes)');
unset($thisfile_ape_items_current['data']);
break;
}
} elseif (is_string($this->inline_attachments)) {
$this->inline_attachments = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->inline_attachments), DIRECTORY_SEPARATOR);
if (!is_dir($this->inline_attachments) || !getID3::is_writable($this->inline_attachments)) {
// cannot write, skip
$this->warning('attachment at '.$thisfile_ape_items_current['offset'].' cannot be saved to "'.$this->inline_attachments.'" (not writable)');
unset($thisfile_ape_items_current['data']);
break;
}
}
// if we get this far, must be OK
if (is_string($this->inline_attachments)) {
$destination_filename = $this->inline_attachments.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$thisfile_ape_items_current['data_offset'];
if (!file_exists($destination_filename) || getID3::is_writable($destination_filename)) {
file_put_contents($destination_filename, $thisfile_ape_items_current['data']);
} else {
$this->warning('attachment at '.$thisfile_ape_items_current['offset'].' cannot be saved to "'.$destination_filename.'" (not writable)');
}
$thisfile_ape_items_current['data_filename'] = $destination_filename;
unset($thisfile_ape_items_current['data']);
} else {
if (!isset($info['ape']['comments']['picture'])) {
$info['ape']['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($thisfile_ape_items_current[$picture_key])) {
$comments_picture_data[$picture_key] = $thisfile_ape_items_current[$picture_key];
}
}
$info['ape']['comments']['picture'][] = $comments_picture_data;
unset($comments_picture_data);
}
} while (false);
break;
default:
if (is_array($thisfile_ape_items_current['data'])) {
foreach ($thisfile_ape_items_current['data'] as $comment) {
$thisfile_ape['comments'][strtolower($item_key)][] = $comment;
}
}
break;
}
}
if (empty($thisfile_replaygain)) {
unset($info['replay_gain']);
}
return true;
}
/**
* @param string $APEheaderFooterData
*
* @return array|false
*/
public function parseAPEheaderFooter($APEheaderFooterData) {
// http://www.uni-jena.de/~pfk/mpp/sv8/apeheader.html
// shortcut
$headerfooterinfo['raw'] = array();
$headerfooterinfo_raw = &$headerfooterinfo['raw'];
$headerfooterinfo_raw['footer_tag'] = substr($APEheaderFooterData, 0, 8);
if ($headerfooterinfo_raw['footer_tag'] != 'APETAGEX') {
return false;
}
$headerfooterinfo_raw['version'] = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData, 8, 4));
$headerfooterinfo_raw['tagsize'] = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData, 12, 4));
$headerfooterinfo_raw['tag_items'] = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData, 16, 4));
$headerfooterinfo_raw['global_flags'] = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData, 20, 4));
$headerfooterinfo_raw['reserved'] = substr($APEheaderFooterData, 24, 8);
$headerfooterinfo['tag_version'] = $headerfooterinfo_raw['version'] / 1000;
if ($headerfooterinfo['tag_version'] >= 2) {
$headerfooterinfo['flags'] = $this->parseAPEtagFlags($headerfooterinfo_raw['global_flags']);
}
return $headerfooterinfo;
}
/**
* @param int $rawflagint
*
* @return array
*/
public function parseAPEtagFlags($rawflagint) {
// "Note: APE Tags 1.0 do not use any of the APE Tag flags.
// All are set to zero on creation and ignored on reading."
// http://wiki.hydrogenaud.io/index.php?title=Ape_Tags_Flags
$flags['header'] = (bool) ($rawflagint & 0x80000000);
$flags['footer'] = (bool) ($rawflagint & 0x40000000);
$flags['this_is_header'] = (bool) ($rawflagint & 0x20000000);
$flags['item_contents_raw'] = ($rawflagint & 0x00000006) >> 1;
$flags['read_only'] = (bool) ($rawflagint & 0x00000001);
$flags['item_contents'] = $this->APEcontentTypeFlagLookup($flags['item_contents_raw']);
return $flags;
}
/**
* @param int $contenttypeid
*
* @return string
*/
public function APEcontentTypeFlagLookup($contenttypeid) {
static $APEcontentTypeFlagLookup = array(
0 => 'utf-8',
1 => 'binary',
2 => 'external',
3 => 'reserved'
);
return (isset($APEcontentTypeFlagLookup[$contenttypeid]) ? $APEcontentTypeFlagLookup[$contenttypeid] : 'invalid');
}
/**
* @param string $itemkey
*
* @return bool
*/
public function APEtagItemIsUTF8Lookup($itemkey) {
static $APEtagItemIsUTF8Lookup = array(
'title',
'subtitle',
'artist',
'album',
'debut album',
'publisher',
'conductor',
'track',
'composer',
'comment',
'copyright',
'publicationright',
'file',
'year',
'record date',
'record location',
'genre',
'media',
'related',
'isrc',
'abstract',
'language',
'bibliography'
);
return in_array(strtolower($itemkey), $APEtagItemIsUTF8Lookup);
}
}
<?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.tag.id3v1.php //
// module for analyzing ID3v1 tags //
// dependencies: NONE //
// ///
/////////////////////////////////////////////////////////////////
if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
exit;
}
class getid3_id3v1 extends getid3_handler
{
/**
* @return bool
*/
public function Analyze() {
$info = &$this->getid3->info;
if (!getid3_lib::intValueSupported($info['filesize'])) {
$this->warning('Unable to check for ID3v1 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB');
return false;
}
$this->fseek(-256, SEEK_END);
$preid3v1 = $this->fread(128);
$id3v1tag = $this->fread(128);
if (substr($id3v1tag, 0, 3) == 'TAG') {
$info['avdataend'] = $info['filesize'] - 128;
$ParsedID3v1['title'] = $this->cutfield(substr($id3v1tag, 3, 30));
$ParsedID3v1['artist'] = $this->cutfield(substr($id3v1tag, 33, 30));
$ParsedID3v1['album'] = $this->cutfield(substr($id3v1tag, 63, 30));
$ParsedID3v1['year'] = $this->cutfield(substr($id3v1tag, 93, 4));
$ParsedID3v1['comment'] = substr($id3v1tag, 97, 30); // can't remove nulls yet, track detection depends on them
$ParsedID3v1['genreid'] = ord(substr($id3v1tag, 127, 1));
// If second-last byte of comment field is null and last byte of comment field is non-null
// then this is ID3v1.1 and the comment field is 28 bytes long and the 30th byte is the track number
if (($id3v1tag[125] === "\x00") && ($id3v1tag[126] !== "\x00")) {
$ParsedID3v1['track_number'] = ord(substr($ParsedID3v1['comment'], 29, 1));
$ParsedID3v1['comment'] = substr($ParsedID3v1['comment'], 0, 28);
}
$ParsedID3v1['comment'] = $this->cutfield($ParsedID3v1['comment']);
$ParsedID3v1['genre'] = $this->LookupGenreName($ParsedID3v1['genreid']);
if (!empty($ParsedID3v1['genre'])) {
unset($ParsedID3v1['genreid']);
}
if (isset($ParsedID3v1['genre']) && (empty($ParsedID3v1['genre']) || ($ParsedID3v1['genre'] == 'Unknown'))) {
unset($ParsedID3v1['genre']);
}
foreach ($ParsedID3v1 as $key => $value) {
$ParsedID3v1['comments'][$key][0] = $value;
}
$ID3v1encoding = $this->getid3->encoding_id3v1;
if ($this->getid3->encoding_id3v1_autodetect) {
// ID3v1 encoding detection hack START
// ID3v1 is defined as always using ISO-8859-1 encoding, but it is not uncommon to find files tagged with ID3v1 using Windows-1251 or other character sets
// Since ID3v1 has no concept of character sets there is no certain way to know we have the correct non-ISO-8859-1 character set, but we can guess
foreach ($ParsedID3v1['comments'] as $tag_key => $valuearray) {
foreach ($valuearray as $key => $value) {
if (preg_match('#^[\\x00-\\x40\\x80-\\xFF]+$#', $value) && !ctype_digit((string) $value)) { // check for strings with only characters above chr(128) and punctuation/numbers, but not just numeric strings (e.g. track numbers or years)
foreach (array('Windows-1251', 'KOI8-R') as $id3v1_bad_encoding) {
if (function_exists('mb_convert_encoding') && @mb_convert_encoding($value, $id3v1_bad_encoding, $id3v1_bad_encoding) === $value) {
$ID3v1encoding = $id3v1_bad_encoding;
$this->warning('ID3v1 detected as '.$id3v1_bad_encoding.' text encoding in '.$tag_key);
break 3;
} elseif (function_exists('iconv') && @iconv($id3v1_bad_encoding, $id3v1_bad_encoding, $value) === $value) {
$ID3v1encoding = $id3v1_bad_encoding;
$this->warning('ID3v1 detected as '.$id3v1_bad_encoding.' text encoding in '.$tag_key);
break 3;
}
}
}
}
}
// ID3v1 encoding detection hack END
}
// ID3v1 data is supposed to be padded with NULL characters, but some taggers pad with spaces
$GoodFormatID3v1tag = $this->GenerateID3v1Tag(
$ParsedID3v1['title'],
$ParsedID3v1['artist'],
$ParsedID3v1['album'],
$ParsedID3v1['year'],
(isset($ParsedID3v1['genre']) ? $this->LookupGenreID($ParsedID3v1['genre']) : false),
$ParsedID3v1['comment'],
(!empty($ParsedID3v1['track_number']) ? $ParsedID3v1['track_number'] : ''));
$ParsedID3v1['padding_valid'] = true;
if ($id3v1tag !== $GoodFormatID3v1tag) {
$ParsedID3v1['padding_valid'] = false;
$this->warning('Some ID3v1 fields do not use NULL characters for padding');
}
$ParsedID3v1['tag_offset_end'] = $info['filesize'];
$ParsedID3v1['tag_offset_start'] = $ParsedID3v1['tag_offset_end'] - 128;
$info['id3v1'] = $ParsedID3v1;
$info['id3v1']['encoding'] = $ID3v1encoding;
}
if (substr($preid3v1, 0, 3) == 'TAG') {
// The way iTunes handles tags is, well, brain-damaged.
// It completely ignores v1 if ID3v2 is present.
// This goes as far as adding a new v1 tag *even if there already is one*
// A suspected double-ID3v1 tag has been detected, but it could be that
// the "TAG" identifier is a legitimate part of an APE or Lyrics3 tag
if (substr($preid3v1, 96, 8) == 'APETAGEX') {
// an APE tag footer was found before the last ID3v1, assume false "TAG" synch
} elseif (substr($preid3v1, 119, 6) == 'LYRICS') {
// a Lyrics3 tag footer was found before the last ID3v1, assume false "TAG" synch
} else {
// APE and Lyrics3 footers not found - assume double ID3v1
$this->warning('Duplicate ID3v1 tag detected - this has been known to happen with iTunes');
$info['avdataend'] -= 128;
}
}
return true;
}
/**
* @param string $str
*
* @return string
*/
public static function cutfield($str) {
return trim(substr($str, 0, strcspn($str, "\x00")));
}
/**
* @param bool $allowSCMPXextended
*
* @return string[]
*/
public static function ArrayOfGenres($allowSCMPXextended=false) {
static $GenreLookup = array(
0 => 'Blues',
1 => 'Classic Rock',
2 => 'Country',
3 => 'Dance',
4 => 'Disco',
5 => 'Funk',
6 => 'Grunge',
7 => 'Hip-Hop',
8 => 'Jazz',
9 => 'Metal',
10 => 'New Age',
11 => 'Oldies',
12 => 'Other',
13 => 'Pop',
14 => 'R&B',
15 => 'Rap',
16 => 'Reggae',
17 => 'Rock',
18 => 'Techno',
19 => 'Industrial',
20 => 'Alternative',
21 => 'Ska',
22 => 'Death Metal',
23 => 'Pranks',
24 => 'Soundtrack',
25 => 'Euro-Techno',
26 => 'Ambient',
27 => 'Trip-Hop',
28 => 'Vocal',
29 => 'Jazz+Funk',
30 => 'Fusion',
31 => 'Trance',
32 => 'Classical',
33 => 'Instrumental',
34 => 'Acid',
35 => 'House',
36 => 'Game',
37 => 'Sound Clip',
38 => 'Gospel',
39 => 'Noise',
40 => 'Alt. Rock',
41 => 'Bass',
42 => 'Soul',
43 => 'Punk',
44 => 'Space',
45 => 'Meditative',
46 => 'Instrumental Pop',
47 => 'Instrumental Rock',
48 => 'Ethnic',
49 => 'Gothic',
50 => 'Darkwave',
51 => 'Techno-Industrial',
52 => 'Electronic',
53 => 'Pop-Folk',
54 => 'Eurodance',
55 => 'Dream',
56 => 'Southern Rock',
57 => 'Comedy',
58 => 'Cult',
59 => 'Gangsta Rap',
60 => 'Top 40',
61 => 'Christian Rap',
62 => 'Pop/Funk',
63 => 'Jungle',
64 => 'Native American',
65 => 'Cabaret',
66 => 'New Wave',
67 => 'Psychedelic',
68 => 'Rave',
69 => 'Showtunes',
70 => 'Trailer',
71 => 'Lo-Fi',
72 => 'Tribal',
73 => 'Acid Punk',
74 => 'Acid Jazz',
75 => 'Polka',
76 => 'Retro',
77 => 'Musical',
78 => 'Rock & Roll',
79 => 'Hard Rock',
80 => 'Folk',
81 => 'Folk/Rock',
82 => 'National Folk',
83 => 'Swing',
84 => 'Fast-Fusion',
85 => 'Bebob',
86 => 'Latin',
87 => 'Revival',
88 => 'Celtic',
89 => 'Bluegrass',
90 => 'Avantgarde',
91 => 'Gothic Rock',
92 => 'Progressive Rock',
93 => 'Psychedelic Rock',
94 => 'Symphonic Rock',
95 => 'Slow Rock',
96 => 'Big Band',
97 => 'Chorus',
98 => 'Easy Listening',
99 => 'Acoustic',
100 => 'Humour',
101 => 'Speech',
102 => 'Chanson',
103 => 'Opera',
104 => 'Chamber Music',
105 => 'Sonata',
106 => 'Symphony',
107 => 'Booty Bass',
108 => 'Primus',
109 => 'Porn Groove',
110 => 'Satire',
111 => 'Slow Jam',
112 => 'Club',
113 => 'Tango',
114 => 'Samba',
115 => 'Folklore',
116 => 'Ballad',
117 => 'Power Ballad',
118 => 'Rhythmic Soul',
119 => 'Freestyle',
120 => 'Duet',
121 => 'Punk Rock',
122 => 'Drum Solo',
123 => 'A Cappella',
124 => 'Euro-House',
125 => 'Dance Hall',
126 => 'Goa',
127 => 'Drum & Bass',
128 => 'Club-House',
129 => 'Hardcore',
130 => 'Terror',
131 => 'Indie',
132 => 'BritPop',
133 => 'Negerpunk',
134 => 'Polsk Punk',
135 => 'Beat',
136 => 'Christian Gangsta Rap',
137 => 'Heavy Metal',
138 => 'Black Metal',
139 => 'Crossover',
140 => 'Contemporary Christian',
141 => 'Christian Rock',
142 => 'Merengue',
143 => 'Salsa',
144 => 'Thrash Metal',
145 => 'Anime',
146 => 'JPop',
147 => 'Synthpop',
255 => 'Unknown',
'CR' => 'Cover',
'RX' => 'Remix'
);
static $GenreLookupSCMPX = array();
if ($allowSCMPXextended && empty($GenreLookupSCMPX)) {
$GenreLookupSCMPX = $GenreLookup;
// http://www.geocities.co.jp/SiliconValley-Oakland/3664/alittle.html#GenreExtended
// Extended ID3v1 genres invented by SCMPX
// Note that 255 "Japanese Anime" conflicts with standard "Unknown"
$GenreLookupSCMPX[240] = 'Sacred';
$GenreLookupSCMPX[241] = 'Northern Europe';
$GenreLookupSCMPX[242] = 'Irish & Scottish';
$GenreLookupSCMPX[243] = 'Scotland';
$GenreLookupSCMPX[244] = 'Ethnic Europe';
$GenreLookupSCMPX[245] = 'Enka';
$GenreLookupSCMPX[246] = 'Children\'s Song';
$GenreLookupSCMPX[247] = 'Japanese Sky';
$GenreLookupSCMPX[248] = 'Japanese Heavy Rock';
$GenreLookupSCMPX[249] = 'Japanese Doom Rock';
$GenreLookupSCMPX[250] = 'Japanese J-POP';
$GenreLookupSCMPX[251] = 'Japanese Seiyu';
$GenreLookupSCMPX[252] = 'Japanese Ambient Techno';
$GenreLookupSCMPX[253] = 'Japanese Moemoe';
$GenreLookupSCMPX[254] = 'Japanese Tokusatsu';
//$GenreLookupSCMPX[255] = 'Japanese Anime';
}
return ($allowSCMPXextended ? $GenreLookupSCMPX : $GenreLookup);
}
/**
* @param string $genreid
* @param bool $allowSCMPXextended
*
* @return string|false
*/
public static function LookupGenreName($genreid, $allowSCMPXextended=true) {
switch ($genreid) {
case 'RX':
case 'CR':
break;
default:
if (!is_numeric($genreid)) {
return false;
}
$genreid = intval($genreid); // to handle 3 or '3' or '03'
break;
}
$GenreLookup = self::ArrayOfGenres($allowSCMPXextended);
return (isset($GenreLookup[$genreid]) ? $GenreLookup[$genreid] : false);
}
/**
* @param string $genre
* @param bool $allowSCMPXextended
*
* @return string|false
*/
public static function LookupGenreID($genre, $allowSCMPXextended=false) {
$GenreLookup = self::ArrayOfGenres($allowSCMPXextended);
$LowerCaseNoSpaceSearchTerm = strtolower(str_replace(' ', '', $genre));
foreach ($GenreLookup as $key => $value) {
if (strtolower(str_replace(' ', '', $value)) == $LowerCaseNoSpaceSearchTerm) {
return $key;
}
}
return false;
}
/**
* @param string $OriginalGenre
*
* @return string|false
*/
public static function StandardiseID3v1GenreName($OriginalGenre) {
if (($GenreID = self::LookupGenreID($OriginalGenre)) !== false) {
return self::LookupGenreName($GenreID);
}
return $OriginalGenre;
}
/**
* @param string $title
* @param string $artist
* @param string $album
* @param string $year
* @param int $genreid
* @param string $comment
* @param int|string $track
*
* @return string
*/
public static function GenerateID3v1Tag($title, $artist, $album, $year, $genreid, $comment, $track='') {
$ID3v1Tag = 'TAG';
$ID3v1Tag .= str_pad(trim(substr($title, 0, 30)), 30, "\x00", STR_PAD_RIGHT);
$ID3v1Tag .= str_pad(trim(substr($artist, 0, 30)), 30, "\x00", STR_PAD_RIGHT);
$ID3v1Tag .= str_pad(trim(substr($album, 0, 30)), 30, "\x00", STR_PAD_RIGHT);
$ID3v1Tag .= str_pad(trim(substr($year, 0, 4)), 4, "\x00", STR_PAD_LEFT);
if (!empty($track) && ($track > 0) && ($track <= 255)) {
$ID3v1Tag .= str_pad(trim(substr($comment, 0, 28)), 28, "\x00", STR_PAD_RIGHT);
$ID3v1Tag .= "\x00";
if (gettype($track) == 'string') {
$track = (int) $track;
}
$ID3v1Tag .= chr($track);
} else {
$ID3v1Tag .= str_pad(trim(substr($comment, 0, 30)), 30, "\x00", STR_PAD_RIGHT);
}
if (($genreid < 0) || ($genreid > 147)) {
$genreid = 255; // 'unknown' genre
}
switch (gettype($genreid)) {
case 'string':
case 'integer':
$ID3v1Tag .= chr(intval($genreid));
break;
default:
$ID3v1Tag .= chr(255); // 'unknown' genre
break;
}
return $ID3v1Tag;
}
}
<?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.tag.id3v2.php //
// module for analyzing ID3v2 tags //
// dependencies: module.tag.id3v1.php //
// ///
/////////////////////////////////////////////////////////////////
if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
exit;
}
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v1.php', __FILE__, true);
class getid3_id3v2 extends getid3_handler
{
public $StartingOffset = 0;
/**
* @return bool
*/
public function Analyze() {
$info = &$this->getid3->info;
// Overall tag structure:
// +-----------------------------+
// | Header (10 bytes) |
// +-----------------------------+
// | Extended Header |
// | (variable length, OPTIONAL) |
// +-----------------------------+
// | Frames (variable length) |
// +-----------------------------+
// | Padding |
// | (variable length, OPTIONAL) |
// +-----------------------------+
// | Footer (10 bytes, OPTIONAL) |
// +-----------------------------+
// Header
// ID3v2/file identifier "ID3"
// ID3v2 version $04 00
// ID3v2 flags (%ab000000 in v2.2, %abc00000 in v2.3, %abcd0000 in v2.4.x)
// ID3v2 size 4 * %0xxxxxxx
// shortcuts
$info['id3v2']['header'] = true;
$thisfile_id3v2 = &$info['id3v2'];
$thisfile_id3v2['flags'] = array();
$thisfile_id3v2_flags = &$thisfile_id3v2['flags'];
$this->fseek($this->StartingOffset);
$header = $this->fread(10);
if (substr($header, 0, 3) == 'ID3' && strlen($header) == 10) {
$thisfile_id3v2['majorversion'] = ord($header[3]);
$thisfile_id3v2['minorversion'] = ord($header[4]);
// shortcut
$id3v2_majorversion = &$thisfile_id3v2['majorversion'];
} else {
unset($info['id3v2']);
return false;
}
if ($id3v2_majorversion > 4) { // this script probably won't correctly parse ID3v2.5.x and above (if it ever exists)
$this->error('this script only parses up to ID3v2.4.x - this tag is ID3v2.'.$id3v2_majorversion.'.'.$thisfile_id3v2['minorversion']);
return false;
}
$id3_flags = ord($header[5]);
switch ($id3v2_majorversion) {
case 2:
// %ab000000 in v2.2
$thisfile_id3v2_flags['unsynch'] = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
$thisfile_id3v2_flags['compression'] = (bool) ($id3_flags & 0x40); // b - Compression
break;
case 3:
// %abc00000 in v2.3
$thisfile_id3v2_flags['unsynch'] = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
$thisfile_id3v2_flags['exthead'] = (bool) ($id3_flags & 0x40); // b - Extended header
$thisfile_id3v2_flags['experim'] = (bool) ($id3_flags & 0x20); // c - Experimental indicator
break;
case 4:
// %abcd0000 in v2.4
$thisfile_id3v2_flags['unsynch'] = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
$thisfile_id3v2_flags['exthead'] = (bool) ($id3_flags & 0x40); // b - Extended header
$thisfile_id3v2_flags['experim'] = (bool) ($id3_flags & 0x20); // c - Experimental indicator
$thisfile_id3v2_flags['isfooter'] = (bool) ($id3_flags & 0x10); // d - Footer present
break;
}
$thisfile_id3v2['headerlength'] = getid3_lib::BigEndian2Int(substr($header, 6, 4), 1) + 10; // length of ID3v2 tag in 10-byte header doesn't include 10-byte header length
$thisfile_id3v2['tag_offset_start'] = $this->StartingOffset;
$thisfile_id3v2['tag_offset_end'] = $thisfile_id3v2['tag_offset_start'] + $thisfile_id3v2['headerlength'];
// create 'encoding' key - used by getid3::HandleAllTags()
// in ID3v2 every field can have it's own encoding type
// so force everything to UTF-8 so it can be handled consistantly
$thisfile_id3v2['encoding'] = 'UTF-8';
// Frames
// All ID3v2 frames consists of one frame header followed by one or more
// fields containing the actual information. The header is always 10
// bytes and laid out as follows:
//
// Frame ID $xx xx xx xx (four characters)
// Size 4 * %0xxxxxxx
// Flags $xx xx
$sizeofframes = $thisfile_id3v2['headerlength'] - 10; // not including 10-byte initial header
if (!empty($thisfile_id3v2['exthead']['length'])) {
$sizeofframes -= ($thisfile_id3v2['exthead']['length'] + 4);
}
if (!empty($thisfile_id3v2_flags['isfooter'])) {
$sizeofframes -= 10; // footer takes last 10 bytes of ID3v2 header, after frame data, before audio
}
if ($sizeofframes > 0) {
$framedata = $this->fread($sizeofframes); // read all frames from file into $framedata variable
// if entire frame data is unsynched, de-unsynch it now (ID3v2.3.x)
if (!empty($thisfile_id3v2_flags['unsynch']) && ($id3v2_majorversion <= 3)) {
$framedata = $this->DeUnsynchronise($framedata);
}
// [in ID3v2.4.0] Unsynchronisation [S:6.1] is done on frame level, instead
// of on tag level, making it easier to skip frames, increasing the streamability
// of the tag. The unsynchronisation flag in the header [S:3.1] indicates that
// there exists an unsynchronised frame, while the new unsynchronisation flag in
// the frame header [S:4.1.2] indicates unsynchronisation.
//$framedataoffset = 10 + ($thisfile_id3v2['exthead']['length'] ? $thisfile_id3v2['exthead']['length'] + 4 : 0); // how many bytes into the stream - start from after the 10-byte header (and extended header length+4, if present)
$framedataoffset = 10; // how many bytes into the stream - start from after the 10-byte header
// Extended Header
if (!empty($thisfile_id3v2_flags['exthead'])) {
$extended_header_offset = 0;
if ($id3v2_majorversion == 3) {
// v2.3 definition:
//Extended header size $xx xx xx xx // 32-bit integer
//Extended Flags $xx xx
// %x0000000 %00000000 // v2.3
// x - CRC data present
//Size of padding $xx xx xx xx
$thisfile_id3v2['exthead']['length'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4), 0);
$extended_header_offset += 4;
$thisfile_id3v2['exthead']['flag_bytes'] = 2;
$thisfile_id3v2['exthead']['flag_raw'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $thisfile_id3v2['exthead']['flag_bytes']));
$extended_header_offset += $thisfile_id3v2['exthead']['flag_bytes'];
$thisfile_id3v2['exthead']['flags']['crc'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x8000);
$thisfile_id3v2['exthead']['padding_size'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4));
$extended_header_offset += 4;
if ($thisfile_id3v2['exthead']['flags']['crc']) {
$thisfile_id3v2['exthead']['flag_data']['crc'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4));
$extended_header_offset += 4;
}
$extended_header_offset += $thisfile_id3v2['exthead']['padding_size'];
} elseif ($id3v2_majorversion == 4) {
// v2.4 definition:
//Extended header size 4 * %0xxxxxxx // 28-bit synchsafe integer
//Number of flag bytes $01
//Extended Flags $xx
// %0bcd0000 // v2.4
// b - Tag is an update
// Flag data length $00
// c - CRC data present
// Flag data length $05
// Total frame CRC 5 * %0xxxxxxx
// d - Tag restrictions
// Flag data length $01
$thisfile_id3v2['exthead']['length'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4), true);
$extended_header_offset += 4;
$thisfile_id3v2['exthead']['flag_bytes'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should always be 1
$extended_header_offset += 1;
$thisfile_id3v2['exthead']['flag_raw'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $thisfile_id3v2['exthead']['flag_bytes']));
$extended_header_offset += $thisfile_id3v2['exthead']['flag_bytes'];
$thisfile_id3v2['exthead']['flags']['update'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x40);
$thisfile_id3v2['exthead']['flags']['crc'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x20);
$thisfile_id3v2['exthead']['flags']['restrictions'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x10);
if ($thisfile_id3v2['exthead']['flags']['update']) {
$ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 0
$extended_header_offset += 1;
}
if ($thisfile_id3v2['exthead']['flags']['crc']) {
$ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 5
$extended_header_offset += 1;
$thisfile_id3v2['exthead']['flag_data']['crc'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $ext_header_chunk_length), true, false);
$extended_header_offset += $ext_header_chunk_length;
}
if ($thisfile_id3v2['exthead']['flags']['restrictions']) {
$ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 1
$extended_header_offset += 1;
// %ppqrrstt
$restrictions_raw = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1));
$extended_header_offset += 1;
$thisfile_id3v2['exthead']['flags']['restrictions']['tagsize'] = ($restrictions_raw & 0xC0) >> 6; // p - Tag size restrictions
$thisfile_id3v2['exthead']['flags']['restrictions']['textenc'] = ($restrictions_raw & 0x20) >> 5; // q - Text encoding restrictions
$thisfile_id3v2['exthead']['flags']['restrictions']['textsize'] = ($restrictions_raw & 0x18) >> 3; // r - Text fields size restrictions
$thisfile_id3v2['exthead']['flags']['restrictions']['imgenc'] = ($restrictions_raw & 0x04) >> 2; // s - Image encoding restrictions
$thisfile_id3v2['exthead']['flags']['restrictions']['imgsize'] = ($restrictions_raw & 0x03) >> 0; // t - Image size restrictions
$thisfile_id3v2['exthead']['flags']['restrictions_text']['tagsize'] = $this->LookupExtendedHeaderRestrictionsTagSizeLimits($thisfile_id3v2['exthead']['flags']['restrictions']['tagsize']);
$thisfile_id3v2['exthead']['flags']['restrictions_text']['textenc'] = $this->LookupExtendedHeaderRestrictionsTextEncodings($thisfile_id3v2['exthead']['flags']['restrictions']['textenc']);
$thisfile_id3v2['exthead']['flags']['restrictions_text']['textsize'] = $this->LookupExtendedHeaderRestrictionsTextFieldSize($thisfile_id3v2['exthead']['flags']['restrictions']['textsize']);
$thisfile_id3v2['exthead']['flags']['restrictions_text']['imgenc'] = $this->LookupExtendedHeaderRestrictionsImageEncoding($thisfile_id3v2['exthead']['flags']['restrictions']['imgenc']);
$thisfile_id3v2['exthead']['flags']['restrictions_text']['imgsize'] = $this->LookupExtendedHeaderRestrictionsImageSizeSize($thisfile_id3v2['exthead']['flags']['restrictions']['imgsize']);
}
if ($thisfile_id3v2['exthead']['length'] != $extended_header_offset) {
$this->warning('ID3v2.4 extended header length mismatch (expecting '.intval($thisfile_id3v2['exthead']['length']).', found '.intval($extended_header_offset).')');
}
}
$framedataoffset += $extended_header_offset;
$framedata = substr($framedata, $extended_header_offset);
} // end extended header
while (isset($framedata) && (strlen($framedata) > 0)) { // cycle through until no more frame data is left to parse
if (strlen($framedata) <= $this->ID3v2HeaderLength($id3v2_majorversion)) {
// insufficient room left in ID3v2 header for actual data - must be padding
$thisfile_id3v2['padding']['start'] = $framedataoffset;
$thisfile_id3v2['padding']['length'] = strlen($framedata);
$thisfile_id3v2['padding']['valid'] = true;
for ($i = 0; $i < $thisfile_id3v2['padding']['length']; $i++) {
if ($framedata[$i] != "\x00") {
$thisfile_id3v2['padding']['valid'] = false;
$thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i;
$this->warning('Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)');
break;
}
}
break; // skip rest of ID3v2 header
}
$frame_header = null;
$frame_name = null;
$frame_size = null;
$frame_flags = null;
if ($id3v2_majorversion == 2) {
// Frame ID $xx xx xx (three characters)
// Size $xx xx xx (24-bit integer)
// Flags $xx xx
$frame_header = substr($framedata, 0, 6); // take next 6 bytes for header
$framedata = substr($framedata, 6); // and leave the rest in $framedata
$frame_name = substr($frame_header, 0, 3);
$frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 3, 3), 0);
$frame_flags = 0; // not used for anything in ID3v2.2, just set to avoid E_NOTICEs
} elseif ($id3v2_majorversion > 2) {
// Frame ID $xx xx xx xx (four characters)
// Size $xx xx xx xx (32-bit integer in v2.3, 28-bit synchsafe in v2.4+)
// Flags $xx xx
$frame_header = substr($framedata, 0, 10); // take next 10 bytes for header
$framedata = substr($framedata, 10); // and leave the rest in $framedata
$frame_name = substr($frame_header, 0, 4);
if ($id3v2_majorversion == 3) {
$frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0); // 32-bit integer
} else { // ID3v2.4+
$frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 1); // 32-bit synchsafe integer (28-bit value)
}
if ($frame_size < (strlen($framedata) + 4)) {
$nextFrameID = substr($framedata, $frame_size, 4);
if ($this->IsValidID3v2FrameName($nextFrameID, $id3v2_majorversion)) {
// next frame is OK
} elseif (($frame_name == "\x00".'MP3') || ($frame_name == "\x00\x00".'MP') || ($frame_name == ' MP3') || ($frame_name == 'MP3e')) {
// MP3ext known broken frames - "ok" for the purposes of this test
} elseif (($id3v2_majorversion == 4) && ($this->IsValidID3v2FrameName(substr($framedata, getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0), 4), 3))) {
$this->warning('ID3v2 tag written as ID3v2.4, but with non-synchsafe integers (ID3v2.3 style). Older versions of (Helium2; iTunes) are known culprits of this. Tag has been parsed as ID3v2.3');
$id3v2_majorversion = 3;
$frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0); // 32-bit integer
}
}
$frame_flags = getid3_lib::BigEndian2Int(substr($frame_header, 8, 2));
}
if ((($id3v2_majorversion == 2) && ($frame_name == "\x00\x00\x00")) || ($frame_name == "\x00\x00\x00\x00")) {
// padding encountered
$thisfile_id3v2['padding']['start'] = $framedataoffset;
$thisfile_id3v2['padding']['length'] = strlen($frame_header) + strlen($framedata);
$thisfile_id3v2['padding']['valid'] = true;
$len = strlen($framedata);
for ($i = 0; $i < $len; $i++) {
if ($framedata[$i] != "\x00") {
$thisfile_id3v2['padding']['valid'] = false;
$thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i;
$this->warning('Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)');
break;
}
}
break; // skip rest of ID3v2 header
}
if ($iTunesBrokenFrameNameFixed = self::ID3v22iTunesBrokenFrameName($frame_name)) {
$this->warning('error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))). [Note: this particular error has been known to happen with tags edited by iTunes (versions "X v2.0.3", "v3.0.1", "v7.0.0.70" are known-guilty, probably others too)]. Translated frame name from "'.str_replace("\x00", ' ', $frame_name).'" to "'.$iTunesBrokenFrameNameFixed.'" for parsing.');
$frame_name = $iTunesBrokenFrameNameFixed;
}
if (($frame_size <= strlen($framedata)) && ($this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion))) {
unset($parsedFrame);
$parsedFrame['frame_name'] = $frame_name;
$parsedFrame['frame_flags_raw'] = $frame_flags;
$parsedFrame['data'] = substr($framedata, 0, $frame_size);
$parsedFrame['datalength'] = getid3_lib::CastAsInt($frame_size);
$parsedFrame['dataoffset'] = $framedataoffset;
$this->ParseID3v2Frame($parsedFrame);
$thisfile_id3v2[$frame_name][] = $parsedFrame;
$framedata = substr($framedata, $frame_size);
} else { // invalid frame length or FrameID
if ($frame_size <= strlen($framedata)) {
if ($this->IsValidID3v2FrameName(substr($framedata, $frame_size, 4), $id3v2_majorversion)) {
// next frame is valid, just skip the current frame
$framedata = substr($framedata, $frame_size);
$this->warning('Next ID3v2 frame is valid, skipping current frame.');
} else {
// next frame is invalid too, abort processing
//unset($framedata);
$framedata = null;
$this->error('Next ID3v2 frame is also invalid, aborting processing.');
}
} elseif ($frame_size == strlen($framedata)) {
// this is the last frame, just skip
$this->warning('This was the last ID3v2 frame.');
} else {
// next frame is invalid too, abort processing
//unset($framedata);
$framedata = null;
$this->warning('Invalid ID3v2 frame size, aborting.');
}
if (!$this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion)) {
switch ($frame_name) {
case "\x00\x00".'MP':
case "\x00".'MP3':
case ' MP3':
case 'MP3e':
case "\x00".'MP':
case ' MP':
case 'MP3':
$this->warning('error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))). [Note: this particular error has been known to happen with tags edited by "MP3ext (www.mutschler.de/mp3ext/)"]');
break;
default:
$this->warning('error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))).');
break;
}
} elseif (!isset($framedata) || ($frame_size > strlen($framedata))) {
$this->error('error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: $frame_size ('.$frame_size.') > strlen($framedata) ('.(isset($framedata) ? strlen($framedata) : 'null').')).');
} else {
$this->error('error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag).');
}
}
$framedataoffset += ($frame_size + $this->ID3v2HeaderLength($id3v2_majorversion));
}
}
// Footer
// The footer is a copy of the header, but with a different identifier.
// ID3v2 identifier "3DI"
// ID3v2 version $04 00
// ID3v2 flags %abcd0000
// ID3v2 size 4 * %0xxxxxxx
if (isset($thisfile_id3v2_flags['isfooter']) && $thisfile_id3v2_flags['isfooter']) {
$footer = $this->fread(10);
if (substr($footer, 0, 3) == '3DI') {
$thisfile_id3v2['footer'] = true;
$thisfile_id3v2['majorversion_footer'] = ord($footer[3]);
$thisfile_id3v2['minorversion_footer'] = ord($footer[4]);
}
if ($thisfile_id3v2['majorversion_footer'] <= 4) {
$id3_flags = ord($footer[5]);
$thisfile_id3v2_flags['unsynch_footer'] = (bool) ($id3_flags & 0x80);
$thisfile_id3v2_flags['extfoot_footer'] = (bool) ($id3_flags & 0x40);
$thisfile_id3v2_flags['experim_footer'] = (bool) ($id3_flags & 0x20);
$thisfile_id3v2_flags['isfooter_footer'] = (bool) ($id3_flags & 0x10);
$thisfile_id3v2['footerlength'] = getid3_lib::BigEndian2Int(substr($footer, 6, 4), 1);
}
} // end footer
if (isset($thisfile_id3v2['comments']['genre'])) {
$genres = array();
foreach ($thisfile_id3v2['comments']['genre'] as $key => $value) {
foreach ($this->ParseID3v2GenreString($value) as $genre) {
$genres[] = $genre;
}
}
$thisfile_id3v2['comments']['genre'] = array_unique($genres);
unset($key, $value, $genres, $genre);
}
if (isset($thisfile_id3v2['comments']['track_number'])) {
foreach ($thisfile_id3v2['comments']['track_number'] as $key => $value) {
if (strstr($value, '/')) {
list($thisfile_id3v2['comments']['track_number'][$key], $thisfile_id3v2['comments']['totaltracks'][$key]) = explode('/', $thisfile_id3v2['comments']['track_number'][$key]);
}
}
}
if (!isset($thisfile_id3v2['comments']['year']) && !empty($thisfile_id3v2['comments']['recording_time'][0]) && preg_match('#^([0-9]{4})#', trim($thisfile_id3v2['comments']['recording_time'][0]), $matches)) {
$thisfile_id3v2['comments']['year'] = array($matches[1]);
}
if (!empty($thisfile_id3v2['TXXX'])) {
// MediaMonkey does this, maybe others: write a blank RGAD frame, but put replay-gain adjustment values in TXXX frames
foreach ($thisfile_id3v2['TXXX'] as $txxx_array) {
switch ($txxx_array['description']) {
case 'replaygain_track_gain':
if (empty($info['replay_gain']['track']['adjustment']) && !empty($txxx_array['data'])) {
$info['replay_gain']['track']['adjustment'] = floatval(trim(str_replace('dB', '', $txxx_array['data'])));
}
break;
case 'replaygain_track_peak':
if (empty($info['replay_gain']['track']['peak']) && !empty($txxx_array['data'])) {
$info['replay_gain']['track']['peak'] = floatval($txxx_array['data']);
}
break;
case 'replaygain_album_gain':
if (empty($info['replay_gain']['album']['adjustment']) && !empty($txxx_array['data'])) {
$info['replay_gain']['album']['adjustment'] = floatval(trim(str_replace('dB', '', $txxx_array['data'])));
}
break;
}
}
}
// Set avdataoffset
$info['avdataoffset'] = $thisfile_id3v2['headerlength'];
if (isset($thisfile_id3v2['footer'])) {
$info['avdataoffset'] += 10;
}
return true;
}
/**
* @param string $genrestring
*
* @return array
*/
public function ParseID3v2GenreString($genrestring) {
// Parse genres into arrays of genreName and genreID
// ID3v2.2.x, ID3v2.3.x: '(21)' or '(4)Eurodisco' or '(51)(39)' or '(55)((I think...)'
// ID3v2.4.x: '21' $00 'Eurodisco' $00
$clean_genres = array();
// hack-fixes for some badly-written ID3v2.3 taggers, while trying not to break correctly-written tags
if (($this->getid3->info['id3v2']['majorversion'] == 3) && !preg_match('#[\x00]#', $genrestring)) {
// note: MusicBrainz Picard incorrectly stores plaintext genres separated by "/" when writing in ID3v2.3 mode, hack-fix here:
// replace / with NULL, then replace back the two ID3v1 genres that legitimately have "/" as part of the single genre name
if (strpos($genrestring, '/') !== false) {
$LegitimateSlashedGenreList = array( // https://github.com/JamesHeinrich/getID3/issues/223
'Pop/Funk', // ID3v1 genre #62 - https://en.wikipedia.org/wiki/ID3#standard
'Cut-up/DJ', // Discogs - https://www.discogs.com/style/cut-up/dj
'RnB/Swing', // Discogs - https://www.discogs.com/style/rnb/swing
'Funk / Soul', // Discogs (note spaces) - https://www.discogs.com/genre/funk+%2F+soul
);
$genrestring = str_replace('/', "\x00", $genrestring);
foreach ($LegitimateSlashedGenreList as $SlashedGenre) {
$genrestring = str_ireplace(str_replace('/', "\x00", $SlashedGenre), $SlashedGenre, $genrestring);
}
}
// some other taggers separate multiple genres with semicolon, e.g. "Heavy Metal;Thrash Metal;Metal"
if (strpos($genrestring, ';') !== false) {
$genrestring = str_replace(';', "\x00", $genrestring);
}
}
if (strpos($genrestring, "\x00") === false) {
$genrestring = preg_replace('#\(([0-9]{1,3})\)#', '$1'."\x00", $genrestring);
}
$genre_elements = explode("\x00", $genrestring);
foreach ($genre_elements as $element) {
$element = trim($element);
if ($element) {
if (preg_match('#^[0-9]{1,3}$#', $element)) {
$clean_genres[] = getid3_id3v1::LookupGenreName($element);
} else {
$clean_genres[] = str_replace('((', '(', $element);
}
}
}
return $clean_genres;
}
/**
* @param array $parsedFrame
*
* @return bool
*/
public function ParseID3v2Frame(&$parsedFrame) {
// shortcuts
$info = &$this->getid3->info;
$id3v2_majorversion = $info['id3v2']['majorversion'];
$parsedFrame['framenamelong'] = $this->FrameNameLongLookup($parsedFrame['frame_name']);
if (empty($parsedFrame['framenamelong'])) {
unset($parsedFrame['framenamelong']);
}
$parsedFrame['framenameshort'] = $this->FrameNameShortLookup($parsedFrame['frame_name']);
if (empty($parsedFrame['framenameshort'])) {
unset($parsedFrame['framenameshort']);
}
if ($id3v2_majorversion >= 3) { // frame flags are not part of the ID3v2.2 standard
if ($id3v2_majorversion == 3) {
// Frame Header Flags
// %abc00000 %ijk00000
$parsedFrame['flags']['TagAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x8000); // a - Tag alter preservation
$parsedFrame['flags']['FileAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x4000); // b - File alter preservation
$parsedFrame['flags']['ReadOnly'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x2000); // c - Read only
$parsedFrame['flags']['compression'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0080); // i - Compression
$parsedFrame['flags']['Encryption'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0040); // j - Encryption
$parsedFrame['flags']['GroupingIdentity'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0020); // k - Grouping identity
} elseif ($id3v2_majorversion == 4) {
// Frame Header Flags
// %0abc0000 %0h00kmnp
$parsedFrame['flags']['TagAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x4000); // a - Tag alter preservation
$parsedFrame['flags']['FileAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x2000); // b - File alter preservation
$parsedFrame['flags']['ReadOnly'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x1000); // c - Read only
$parsedFrame['flags']['GroupingIdentity'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0040); // h - Grouping identity
$parsedFrame['flags']['compression'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0008); // k - Compression
$parsedFrame['flags']['Encryption'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0004); // m - Encryption
$parsedFrame['flags']['Unsynchronisation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0002); // n - Unsynchronisation
$parsedFrame['flags']['DataLengthIndicator'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0001); // p - Data length indicator
// Frame-level de-unsynchronisation - ID3v2.4
if ($parsedFrame['flags']['Unsynchronisation']) {
$parsedFrame['data'] = $this->DeUnsynchronise($parsedFrame['data']);
}
if ($parsedFrame['flags']['DataLengthIndicator']) {
$parsedFrame['data_length_indicator'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 4), 1);
$parsedFrame['data'] = substr($parsedFrame['data'], 4);
}
}
// Frame-level de-compression
if ($parsedFrame['flags']['compression']) {
$parsedFrame['decompressed_size'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 4));
if (!function_exists('gzuncompress')) {
$this->warning('gzuncompress() support required to decompress ID3v2 frame "'.$parsedFrame['frame_name'].'"');
} else {
if ($decompresseddata = @gzuncompress(substr($parsedFrame['data'], 4))) {
//if ($decompresseddata = @gzuncompress($parsedFrame['data'])) {
$parsedFrame['data'] = $decompresseddata;
unset($decompresseddata);
} else {
$this->warning('gzuncompress() failed on compressed contents of ID3v2 frame "'.$parsedFrame['frame_name'].'"');
}
}
}
}
if (!empty($parsedFrame['flags']['DataLengthIndicator'])) {
if ($parsedFrame['data_length_indicator'] != strlen($parsedFrame['data'])) {
$this->warning('ID3v2 frame "'.$parsedFrame['frame_name'].'" should be '.$parsedFrame['data_length_indicator'].' bytes long according to DataLengthIndicator, but found '.strlen($parsedFrame['data']).' bytes of data');
}
}
if (isset($parsedFrame['datalength']) && ($parsedFrame['datalength'] == 0)) {
$warning = 'Frame "'.$parsedFrame['frame_name'].'" at offset '.$parsedFrame['dataoffset'].' has no data portion';
switch ($parsedFrame['frame_name']) {
case 'WCOM':
$warning .= ' (this is known to happen with files tagged by RioPort)';
break;
default:
break;
}
$this->warning($warning);
} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'UFID')) || // 4.1 UFID Unique file identifier
(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'UFI'))) { // 4.1 UFI Unique file identifier
// There may be more than one 'UFID' frame in a tag,
// but only one with the same 'Owner identifier'.
// <Header for 'Unique file identifier', ID: 'UFID'>
// Owner identifier <text string> $00
// Identifier <up to 64 bytes binary data>
$exploded = explode("\x00", $parsedFrame['data'], 2);
$parsedFrame['ownerid'] = (isset($exploded[0]) ? $exploded[0] : '');
$parsedFrame['data'] = (isset($exploded[1]) ? $exploded[1] : '');
} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'TXXX')) || // 4.2.2 TXXX User defined text information frame
(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'TXX'))) { // 4.2.2 TXX User defined text information frame
// There may be more than one 'TXXX' frame in each tag,
// but only one with the same description.
// <Header for 'User defined text information frame', ID: 'TXXX'>
// Text encoding $xx
// Description <text string according to encoding> $00 (00)
// Value <text string according to encoding>
$frame_offset = 0;
$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
$frame_textencoding_terminator = "\x00";
}
$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
}
$parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
$parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
$parsedFrame['encodingid'] = $frame_textencoding;
$parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
$parsedFrame['description'] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['description']));
$parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
$parsedFrame['data'] = $this->RemoveStringTerminator($parsedFrame['data'], $frame_textencoding_terminator);
if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
$commentkey = ($parsedFrame['description'] ? $parsedFrame['description'] : (isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) ? count($info['id3v2']['comments'][$parsedFrame['framenameshort']]) : 0));
if (!isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) || !array_key_exists($commentkey, $info['id3v2']['comments'][$parsedFrame['framenameshort']])) {
$info['id3v2']['comments'][$parsedFrame['framenameshort']][$commentkey] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']));
} else {
$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']));
}
}
//unset($parsedFrame['data']); do not unset, may be needed elsewhere, e.g. for replaygain
} elseif ($parsedFrame['frame_name'][0] == 'T') { // 4.2. T??[?] Text information frame
// There may only be one text information frame of its kind in an tag.
// <Header for 'Text information frame', ID: 'T000' - 'TZZZ',
// excluding 'TXXX' described in 4.2.6.>
// Text encoding $xx
// Information <text string(s) according to encoding>
$frame_offset = 0;
$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
}
$parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
$parsedFrame['data'] = $this->RemoveStringTerminator($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding));
$parsedFrame['encodingid'] = $frame_textencoding;
$parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
// ID3v2.3 specs say that TPE1 (and others) can contain multiple artist values separated with /
// This of course breaks when an artist name contains slash character, e.g. "AC/DC"
// MP3tag (maybe others) implement alternative system where multiple artists are null-separated, which makes more sense
// getID3 will split null-separated artists into multiple artists and leave slash-separated ones to the user
switch ($parsedFrame['encoding']) {
case 'UTF-16':
case 'UTF-16BE':
case 'UTF-16LE':
$wordsize = 2;
break;
case 'ISO-8859-1':
case 'UTF-8':
default:
$wordsize = 1;
break;
}
$Txxx_elements = array();
$Txxx_elements_start_offset = 0;
for ($i = 0; $i < strlen($parsedFrame['data']); $i += $wordsize) {
if (substr($parsedFrame['data'], $i, $wordsize) == str_repeat("\x00", $wordsize)) {
$Txxx_elements[] = substr($parsedFrame['data'], $Txxx_elements_start_offset, $i - $Txxx_elements_start_offset);
$Txxx_elements_start_offset = $i + $wordsize;
}
}
$Txxx_elements[] = substr($parsedFrame['data'], $Txxx_elements_start_offset, $i - $Txxx_elements_start_offset);
foreach ($Txxx_elements as $Txxx_element) {
$string = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $Txxx_element);
if (!empty($string)) {
$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $string;
}
}
unset($string, $wordsize, $i, $Txxx_elements, $Txxx_element, $Txxx_elements_start_offset);
}
} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'WXXX')) || // 4.3.2 WXXX User defined URL link frame
(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'WXX'))) { // 4.3.2 WXX User defined URL link frame
// There may be more than one 'WXXX' frame in each tag,
// but only one with the same description
// <Header for 'User defined URL link frame', ID: 'WXXX'>
// Text encoding $xx
// Description <text string according to encoding> $00 (00)
// URL <text string>
$frame_offset = 0;
$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
$frame_textencoding_terminator = "\x00";
}
$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
}
$parsedFrame['encodingid'] = $frame_textencoding;
$parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
$parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset); // according to the frame text encoding
$parsedFrame['url'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator)); // always ISO-8859-1
$parsedFrame['description'] = $this->RemoveStringTerminator($parsedFrame['description'], $frame_textencoding_terminator);
$parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback('ISO-8859-1', $info['id3v2']['encoding'], $parsedFrame['url']);
}
unset($parsedFrame['data']);
} elseif ($parsedFrame['frame_name'][0] == 'W') { // 4.3. W??? URL link frames
// There may only be one URL link frame of its kind in a tag,
// except when stated otherwise in the frame description
// <Header for 'URL link frame', ID: 'W000' - 'WZZZ', excluding 'WXXX'
// described in 4.3.2.>
// URL <text string>
$parsedFrame['url'] = trim($parsedFrame['data']); // always ISO-8859-1
if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback('ISO-8859-1', $info['id3v2']['encoding'], $parsedFrame['url']);
}
unset($parsedFrame['data']);
} elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'IPLS')) || // 4.4 IPLS Involved people list (ID3v2.3 only)
(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'IPL'))) { // 4.4 IPL Involved people list (ID3v2.2 only)
// http://id3.org/id3v2.3.0#sec4.4
// There may only be one 'IPL' frame in each tag
// <Header for 'User defined URL link frame', ID: 'IPL'>
// Text encoding $xx
// People list strings <textstrings>
$frame_offset = 0;
$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
}
$parsedFrame['encodingid'] = $frame_textencoding;
$parsedFrame['encoding'] = $this->TextEncodingNameLookup($parsedFrame['encodingid']);
$parsedFrame['data_raw'] = (string) substr($parsedFrame['data'], $frame_offset);
// https://www.getid3.org/phpBB3/viewtopic.php?t=1369
// "this tag typically contains null terminated strings, which are associated in pairs"
// "there are users that use the tag incorrectly"
$IPLS_parts = array();
if (strpos($parsedFrame['data_raw'], "\x00") !== false) {
$IPLS_parts_unsorted = array();
if (((strlen($parsedFrame['data_raw']) % 2) == 0) && ((substr($parsedFrame['data_raw'], 0, 2) == "\xFF\xFE") || (substr($parsedFrame['data_raw'], 0, 2) == "\xFE\xFF"))) {
// UTF-16, be careful looking for null bytes since most 2-byte characters may contain one; you need to find twin null bytes, and on even padding
$thisILPS = '';
for ($i = 0; $i < strlen($parsedFrame['data_raw']); $i += 2) {
$twobytes = substr($parsedFrame['data_raw'], $i, 2);
if ($twobytes === "\x00\x00") {
$IPLS_parts_unsorted[] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $thisILPS);
$thisILPS = '';
} else {
$thisILPS .= $twobytes;
}
}
if (strlen($thisILPS) > 2) { // 2-byte BOM
$IPLS_parts_unsorted[] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $thisILPS);
}
} else {
// ISO-8859-1 or UTF-8 or other single-byte-null character set
$IPLS_parts_unsorted = explode("\x00", $parsedFrame['data_raw']);
}
if (count($IPLS_parts_unsorted) == 1) {
// just a list of names, e.g. "Dino Baptiste, Jimmy Copley, John Gordon, Bernie Marsden, Sharon Watson"
foreach ($IPLS_parts_unsorted as $key => $value) {
$IPLS_parts_sorted = preg_split('#[;,\\r\\n\\t]#', $value);
$position = '';
foreach ($IPLS_parts_sorted as $person) {
$IPLS_parts[] = array('position'=>$position, 'person'=>$person);
}
}
} elseif ((count($IPLS_parts_unsorted) % 2) == 0) {
$position = '';
$person = '';
foreach ($IPLS_parts_unsorted as $key => $value) {
if (($key % 2) == 0) {
$position = $value;
} else {
$person = $value;
$IPLS_parts[] = array('position'=>$position, 'person'=>$person);
$position = '';
$person = '';
}
}
} else {
foreach ($IPLS_parts_unsorted as $key => $value) {
$IPLS_parts[] = array($value);
}
}
} else {
$IPLS_parts = preg_split('#[;,\\r\\n\\t]#', $parsedFrame['data_raw']);
}
$parsedFrame['data'] = $IPLS_parts;
if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data'];
}
} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'MCDI')) || // 4.4 MCDI Music CD identifier
(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'MCI'))) { // 4.5 MCI Music CD identifier
// There may only be one 'MCDI' frame in each tag
// <Header for 'Music CD identifier', ID: 'MCDI'>
// CD TOC <binary data>
if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data'];
}
} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'ETCO')) || // 4.5 ETCO Event timing codes
(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'ETC'))) { // 4.6 ETC Event timing codes
// There may only be one 'ETCO' frame in each tag
// <Header for 'Event timing codes', ID: 'ETCO'>
// Time stamp format $xx
// Where time stamp format is:
// $01 (32-bit value) MPEG frames from beginning of file
// $02 (32-bit value) milliseconds from beginning of file
// Followed by a list of key events in the following format:
// Type of event $xx
// Time stamp $xx (xx ...)
// The 'Time stamp' is set to zero if directly at the beginning of the sound
// or after the previous event. All events MUST be sorted in chronological order.
$frame_offset = 0;
$parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
while ($frame_offset < strlen($parsedFrame['data'])) {
$parsedFrame['typeid'] = substr($parsedFrame['data'], $frame_offset++, 1);
$parsedFrame['type'] = $this->ETCOEventLookup($parsedFrame['typeid']);
$parsedFrame['timestamp'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
$frame_offset += 4;
}
unset($parsedFrame['data']);
} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'MLLT')) || // 4.6 MLLT MPEG location lookup table
(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'MLL'))) { // 4.7 MLL MPEG location lookup table
// There may only be one 'MLLT' frame in each tag
// <Header for 'Location lookup table', ID: 'MLLT'>
// MPEG frames between reference $xx xx
// Bytes between reference $xx xx xx
// Milliseconds between reference $xx xx xx
// Bits for bytes deviation $xx
// Bits for milliseconds dev. $xx
// Then for every reference the following data is included;
// Deviation in bytes %xxx....
// Deviation in milliseconds %xxx....
$frame_offset = 0;
$parsedFrame['framesbetweenreferences'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 2));
$parsedFrame['bytesbetweenreferences'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 2, 3));
$parsedFrame['msbetweenreferences'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 5, 3));
$parsedFrame['bitsforbytesdeviation'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 8, 1));
$parsedFrame['bitsformsdeviation'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 9, 1));
$parsedFrame['data'] = substr($parsedFrame['data'], 10);
$deviationbitstream = '';
while ($frame_offset < strlen($parsedFrame['data'])) {
$deviationbitstream .= getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1));
}
$reference_counter = 0;
while (strlen($deviationbitstream) > 0) {
$parsedFrame[$reference_counter]['bytedeviation'] = bindec(substr($deviationbitstream, 0, $parsedFrame['bitsforbytesdeviation']));
$parsedFrame[$reference_counter]['msdeviation'] = bindec(substr($deviationbitstream, $parsedFrame['bitsforbytesdeviation'], $parsedFrame['bitsformsdeviation']));
$deviationbitstream = substr($deviationbitstream, $parsedFrame['bitsforbytesdeviation'] + $parsedFrame['bitsformsdeviation']);
$reference_counter++;
}
unset($parsedFrame['data']);
} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'SYTC')) || // 4.7 SYTC Synchronised tempo codes
(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'STC'))) { // 4.8 STC Synchronised tempo codes
// There may only be one 'SYTC' frame in each tag
// <Header for 'Synchronised tempo codes', ID: 'SYTC'>
// Time stamp format $xx
// Tempo data <binary data>
// Where time stamp format is:
// $01 (32-bit value) MPEG frames from beginning of file
// $02 (32-bit value) milliseconds from beginning of file
$frame_offset = 0;
$parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
$timestamp_counter = 0;
while ($frame_offset < strlen($parsedFrame['data'])) {
$parsedFrame[$timestamp_counter]['tempo'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
if ($parsedFrame[$timestamp_counter]['tempo'] == 255) {
$parsedFrame[$timestamp_counter]['tempo'] += ord(substr($parsedFrame['data'], $frame_offset++, 1));
}
$parsedFrame[$timestamp_counter]['timestamp'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
$frame_offset += 4;
$timestamp_counter++;
}
unset($parsedFrame['data']);
} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'USLT')) || // 4.8 USLT Unsynchronised lyric/text transcription
(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'ULT'))) { // 4.9 ULT Unsynchronised lyric/text transcription
// There may be more than one 'Unsynchronised lyrics/text transcription' frame
// in each tag, but only one with the same language and content descriptor.
// <Header for 'Unsynchronised lyrics/text transcription', ID: 'USLT'>
// Text encoding $xx
// Language $xx xx xx
// Content descriptor <text string according to encoding> $00 (00)
// Lyrics/text <full text string according to encoding>
$frame_offset = 0;
$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
$frame_textencoding_terminator = "\x00";
}
$frame_language = substr($parsedFrame['data'], $frame_offset, 3);
$frame_offset += 3;
$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
}
$parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
$parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
$parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
$parsedFrame['data'] = $this->RemoveStringTerminator($parsedFrame['data'], $frame_textencoding_terminator);
$parsedFrame['encodingid'] = $frame_textencoding;
$parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
$parsedFrame['language'] = $frame_language;
$parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
}
unset($parsedFrame['data']);
} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'SYLT')) || // 4.9 SYLT Synchronised lyric/text
(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'SLT'))) { // 4.10 SLT Synchronised lyric/text
// There may be more than one 'SYLT' frame in each tag,
// but only one with the same language and content descriptor.
// <Header for 'Synchronised lyrics/text', ID: 'SYLT'>
// Text encoding $xx
// Language $xx xx xx
// Time stamp format $xx
// $01 (32-bit value) MPEG frames from beginning of file
// $02 (32-bit value) milliseconds from beginning of file
// Content type $xx
// Content descriptor <text string according to encoding> $00 (00)
// Terminated text to be synced (typically a syllable)
// Sync identifier (terminator to above string) $00 (00)
// Time stamp $xx (xx ...)
$frame_offset = 0;
$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
$frame_textencoding_terminator = "\x00";
}
$frame_language = substr($parsedFrame['data'], $frame_offset, 3);
$frame_offset += 3;
$parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
$parsedFrame['contenttypeid'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
$parsedFrame['contenttype'] = $this->SYTLContentTypeLookup($parsedFrame['contenttypeid']);
$parsedFrame['encodingid'] = $frame_textencoding;
$parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
$parsedFrame['language'] = $frame_language;
$parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
$timestampindex = 0;
$frame_remainingdata = substr($parsedFrame['data'], $frame_offset);
while (strlen($frame_remainingdata)) {
$frame_offset = 0;
$frame_terminatorpos = strpos($frame_remainingdata, $frame_textencoding_terminator);
if ($frame_terminatorpos === false) {
$frame_remainingdata = '';
} else {
if (ord(substr($frame_remainingdata, $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
}
$parsedFrame['lyrics'][$timestampindex]['data'] = substr($frame_remainingdata, $frame_offset, $frame_terminatorpos - $frame_offset);
$frame_remainingdata = substr($frame_remainingdata, $frame_terminatorpos + strlen($frame_textencoding_terminator));
if (($timestampindex == 0) && (ord($frame_remainingdata[0]) != 0)) {
// timestamp probably omitted for first data item
} else {
$parsedFrame['lyrics'][$timestampindex]['timestamp'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 0, 4));
$frame_remainingdata = substr($frame_remainingdata, 4);
}
$timestampindex++;
}
}
unset($parsedFrame['data']);
} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'COMM')) || // 4.10 COMM Comments
(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'COM'))) { // 4.11 COM Comments
// There may be more than one comment frame in each tag,
// but only one with the same language and content descriptor.
// <Header for 'Comment', ID: 'COMM'>
// Text encoding $xx
// Language $xx xx xx
// Short content descrip. <text string according to encoding> $00 (00)
// The actual text <full text string according to encoding>
if (strlen($parsedFrame['data']) < 5) {
$this->warning('Invalid data (too short) for "'.$parsedFrame['frame_name'].'" frame at offset '.$parsedFrame['dataoffset']);
} else {
$frame_offset = 0;
$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
$frame_textencoding_terminator = "\x00";
}
$frame_language = substr($parsedFrame['data'], $frame_offset, 3);
$frame_offset += 3;
$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
}
$parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
$parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
$frame_text = (string) substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
$frame_text = $this->RemoveStringTerminator($frame_text, $frame_textencoding_terminator);
$parsedFrame['encodingid'] = $frame_textencoding;
$parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
$parsedFrame['language'] = $frame_language;
$parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
$parsedFrame['data'] = $frame_text;
if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
$commentkey = ($parsedFrame['description'] ? $parsedFrame['description'] : (!empty($info['id3v2']['comments'][$parsedFrame['framenameshort']]) ? count($info['id3v2']['comments'][$parsedFrame['framenameshort']]) : 0));
if (!isset($info['id3v2']['comments'][$parsedFrame['framenameshort']]) || !array_key_exists($commentkey, $info['id3v2']['comments'][$parsedFrame['framenameshort']])) {
$info['id3v2']['comments'][$parsedFrame['framenameshort']][$commentkey] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
} else {
$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
}
}
}
} elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'RVA2')) { // 4.11 RVA2 Relative volume adjustment (2) (ID3v2.4+ only)
// There may be more than one 'RVA2' frame in each tag,
// but only one with the same identification string
// <Header for 'Relative volume adjustment (2)', ID: 'RVA2'>
// Identification <text string> $00
// The 'identification' string is used to identify the situation and/or
// device where this adjustment should apply. The following is then
// repeated for every channel:
// Type of channel $xx
// Volume adjustment $xx xx
// Bits representing peak $xx
// Peak volume $xx (xx ...)
$frame_terminatorpos = strpos($parsedFrame['data'], "\x00");
$frame_idstring = substr($parsedFrame['data'], 0, $frame_terminatorpos);
if (ord($frame_idstring) === 0) {
$frame_idstring = '';
}
$frame_remainingdata = substr($parsedFrame['data'], $frame_terminatorpos + strlen("\x00"));
$parsedFrame['description'] = $frame_idstring;
$RVA2channelcounter = 0;
while (strlen($frame_remainingdata) >= 5) {
$frame_offset = 0;
$frame_channeltypeid = ord(substr($frame_remainingdata, $frame_offset++, 1));
$parsedFrame[$RVA2channelcounter]['channeltypeid'] = $frame_channeltypeid;
$parsedFrame[$RVA2channelcounter]['channeltype'] = $this->RVA2ChannelTypeLookup($frame_channeltypeid);
$parsedFrame[$RVA2channelcounter]['volumeadjust'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, 2), false, true); // 16-bit signed
$frame_offset += 2;
$parsedFrame[$RVA2channelcounter]['bitspeakvolume'] = ord(substr($frame_remainingdata, $frame_offset++, 1));
if (($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] < 1) || ($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] > 4)) {
$this->warning('ID3v2::RVA2 frame['.$RVA2channelcounter.'] contains invalid '.$parsedFrame[$RVA2channelcounter]['bitspeakvolume'].'-byte bits-representing-peak value');
break;
}
$frame_bytespeakvolume = ceil($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] / 8);
$parsedFrame[$RVA2channelcounter]['peakvolume'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, $frame_bytespeakvolume));
$frame_remainingdata = substr($frame_remainingdata, $frame_offset + $frame_bytespeakvolume);
$RVA2channelcounter++;
}
unset($parsedFrame['data']);
} elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'RVAD')) || // 4.12 RVAD Relative volume adjustment (ID3v2.3 only)
(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'RVA'))) { // 4.12 RVA Relative volume adjustment (ID3v2.2 only)
// There may only be one 'RVA' frame in each tag
// <Header for 'Relative volume adjustment', ID: 'RVA'>
// ID3v2.2 => Increment/decrement %000000ba
// ID3v2.3 => Increment/decrement %00fedcba
// Bits used for volume descr. $xx
// Relative volume change, right $xx xx (xx ...) // a
// Relative volume change, left $xx xx (xx ...) // b
// Peak volume right $xx xx (xx ...)
// Peak volume left $xx xx (xx ...)
// ID3v2.3 only, optional (not present in ID3v2.2):
// Relative volume change, right back $xx xx (xx ...) // c
// Relative volume change, left back $xx xx (xx ...) // d
// Peak volume right back $xx xx (xx ...)
// Peak volume left back $xx xx (xx ...)
// ID3v2.3 only, optional (not present in ID3v2.2):
// Relative volume change, center $xx xx (xx ...) // e
// Peak volume center $xx xx (xx ...)
// ID3v2.3 only, optional (not present in ID3v2.2):
// Relative volume change, bass $xx xx (xx ...) // f
// Peak volume bass $xx xx (xx ...)
$frame_offset = 0;
$frame_incrdecrflags = getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1));
$parsedFrame['incdec']['right'] = (bool) substr($frame_incrdecrflags, 6, 1);
$parsedFrame['incdec']['left'] = (bool) substr($frame_incrdecrflags, 7, 1);
$parsedFrame['bitsvolume'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
$frame_bytesvolume = ceil($parsedFrame['bitsvolume'] / 8);
$parsedFrame['volumechange']['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
if ($parsedFrame['incdec']['right'] === false) {
$parsedFrame['volumechange']['right'] *= -1;
}
$frame_offset += $frame_bytesvolume;
$parsedFrame['volumechange']['left'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
if ($parsedFrame['incdec']['left'] === false) {
$parsedFrame['volumechange']['left'] *= -1;
}
$frame_offset += $frame_bytesvolume;
$parsedFrame['peakvolume']['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
$frame_offset += $frame_bytesvolume;
$parsedFrame['peakvolume']['left'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
$frame_offset += $frame_bytesvolume;
if ($id3v2_majorversion == 3) {
$parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
if (strlen($parsedFrame['data']) > 0) {
$parsedFrame['incdec']['rightrear'] = (bool) substr($frame_incrdecrflags, 4, 1);
$parsedFrame['incdec']['leftrear'] = (bool) substr($frame_incrdecrflags, 5, 1);
$parsedFrame['volumechange']['rightrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
if ($parsedFrame['incdec']['rightrear'] === false) {
$parsedFrame['volumechange']['rightrear'] *= -1;
}
$frame_offset += $frame_bytesvolume;
$parsedFrame['volumechange']['leftrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
if ($parsedFrame['incdec']['leftrear'] === false) {
$parsedFrame['volumechange']['leftrear'] *= -1;
}
$frame_offset += $frame_bytesvolume;
$parsedFrame['peakvolume']['rightrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
$frame_offset += $frame_bytesvolume;
$parsedFrame['peakvolume']['leftrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
$frame_offset += $frame_bytesvolume;
}
$parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
if (strlen($parsedFrame['data']) > 0) {
$parsedFrame['incdec']['center'] = (bool) substr($frame_incrdecrflags, 3, 1);
$parsedFrame['volumechange']['center'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
if ($parsedFrame['incdec']['center'] === false) {
$parsedFrame['volumechange']['center'] *= -1;
}
$frame_offset += $frame_bytesvolume;
$parsedFrame['peakvolume']['center'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
$frame_offset += $frame_bytesvolume;
}
$parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
if (strlen($parsedFrame['data']) > 0) {
$parsedFrame['incdec']['bass'] = (bool) substr($frame_incrdecrflags, 2, 1);
$parsedFrame['volumechange']['bass'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
if ($parsedFrame['incdec']['bass'] === false) {
$parsedFrame['volumechange']['bass'] *= -1;
}
$frame_offset += $frame_bytesvolume;
$parsedFrame['peakvolume']['bass'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
$frame_offset += $frame_bytesvolume;
}
}
unset($parsedFrame['data']);
} elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'EQU2')) { // 4.12 EQU2 Equalisation (2) (ID3v2.4+ only)
// There may be more than one 'EQU2' frame in each tag,
// but only one with the same identification string
// <Header of 'Equalisation (2)', ID: 'EQU2'>
// Interpolation method $xx
// $00 Band
// $01 Linear
// Identification <text string> $00
// The following is then repeated for every adjustment point
// Frequency $xx xx
// Volume adjustment $xx xx
$frame_offset = 0;
$frame_interpolationmethod = ord(substr($parsedFrame['data'], $frame_offset++, 1));
$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
$frame_idstring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
if (ord($frame_idstring) === 0) {
$frame_idstring = '';
}
$parsedFrame['description'] = $frame_idstring;
$frame_remainingdata = substr($parsedFrame['data'], $frame_terminatorpos + strlen("\x00"));
while (strlen($frame_remainingdata)) {
$frame_frequency = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 0, 2)) / 2;
$parsedFrame['data'][$frame_frequency] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 2, 2), false, true);
$frame_remainingdata = substr($frame_remainingdata, 4);
}
$parsedFrame['interpolationmethod'] = $frame_interpolationmethod;
unset($parsedFrame['data']);
} elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'EQUA')) || // 4.12 EQUA Equalisation (ID3v2.3 only)
(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'EQU'))) { // 4.13 EQU Equalisation (ID3v2.2 only)
// There may only be one 'EQUA' frame in each tag
// <Header for 'Relative volume adjustment', ID: 'EQU'>
// Adjustment bits $xx
// This is followed by 2 bytes + ('adjustment bits' rounded up to the
// nearest byte) for every equalisation band in the following format,
// giving a frequency range of 0 - 32767Hz:
// Increment/decrement %x (MSB of the Frequency)
// Frequency (lower 15 bits)
// Adjustment $xx (xx ...)
$frame_offset = 0;
$parsedFrame['adjustmentbits'] = substr($parsedFrame['data'], $frame_offset++, 1);
$frame_adjustmentbytes = ceil($parsedFrame['adjustmentbits'] / 8);
$frame_remainingdata = (string) substr($parsedFrame['data'], $frame_offset);
while (strlen($frame_remainingdata) > 0) {
$frame_frequencystr = getid3_lib::BigEndian2Bin(substr($frame_remainingdata, 0, 2));
$frame_incdec = (bool) substr($frame_frequencystr, 0, 1);
$frame_frequency = bindec(substr($frame_frequencystr, 1, 15));
$parsedFrame[$frame_frequency]['incdec'] = $frame_incdec;
$parsedFrame[$frame_frequency]['adjustment'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 2, $frame_adjustmentbytes));
if ($parsedFrame[$frame_frequency]['incdec'] === false) {
$parsedFrame[$frame_frequency]['adjustment'] *= -1;
}
$frame_remainingdata = substr($frame_remainingdata, 2 + $frame_adjustmentbytes);
}
unset($parsedFrame['data']);
} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RVRB')) || // 4.13 RVRB Reverb
(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'REV'))) { // 4.14 REV Reverb
// There may only be one 'RVRB' frame in each tag.
// <Header for 'Reverb', ID: 'RVRB'>
// Reverb left (ms) $xx xx
// Reverb right (ms) $xx xx
// Reverb bounces, left $xx
// Reverb bounces, right $xx
// Reverb feedback, left to left $xx
// Reverb feedback, left to right $xx
// Reverb feedback, right to right $xx
// Reverb feedback, right to left $xx
// Premix left to right $xx
// Premix right to left $xx
$frame_offset = 0;
$parsedFrame['left'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
$frame_offset += 2;
$parsedFrame['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
$frame_offset += 2;
$parsedFrame['bouncesL'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
$parsedFrame['bouncesR'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
$parsedFrame['feedbackLL'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
$parsedFrame['feedbackLR'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
$parsedFrame['feedbackRR'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
$parsedFrame['feedbackRL'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
$parsedFrame['premixLR'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
$parsedFrame['premixRL'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
unset($parsedFrame['data']);
} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'APIC')) || // 4.14 APIC Attached picture
(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'PIC'))) { // 4.15 PIC Attached picture
// There may be several pictures attached to one file,
// each in their individual 'APIC' frame, but only one
// with the same content descriptor
// <Header for 'Attached picture', ID: 'APIC'>
// Text encoding $xx
// ID3v2.3+ => MIME type <text string> $00
// ID3v2.2 => Image format $xx xx xx
// Picture type $xx
// Description <text string according to encoding> $00 (00)
// Picture data <binary data>
$frame_offset = 0;
$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
$frame_textencoding_terminator = "\x00";
}
if ($id3v2_majorversion == 2 && strlen($parsedFrame['data']) > $frame_offset) {
$frame_imagetype = substr($parsedFrame['data'], $frame_offset, 3);
if (strtolower($frame_imagetype) == 'ima') {
// complete hack for mp3Rage (www.chaoticsoftware.com) that puts ID3v2.3-formatted
// MIME type instead of 3-char ID3v2.2-format image type (thanks xbhoffØpacbell*net)
$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
$frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
if (ord($frame_mimetype) === 0) {
$frame_mimetype = '';
}
$frame_imagetype = strtoupper(str_replace('image/', '', strtolower($frame_mimetype)));
if ($frame_imagetype == 'JPEG') {
$frame_imagetype = 'JPG';
}
$frame_offset = $frame_terminatorpos + strlen("\x00");
} else {
$frame_offset += 3;
}
}
if ($id3v2_majorversion > 2 && strlen($parsedFrame['data']) > $frame_offset) {
$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
$frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
if (ord($frame_mimetype) === 0) {
$frame_mimetype = '';
}
$frame_offset = $frame_terminatorpos + strlen("\x00");
}
$frame_picturetype = ord(substr($parsedFrame['data'], $frame_offset++, 1));
if ($frame_offset >= $parsedFrame['datalength']) {
$this->warning('data portion of APIC frame is missing at offset '.($parsedFrame['dataoffset'] + 8 + $frame_offset));
} else {
$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
}
$parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
$parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
$parsedFrame['encodingid'] = $frame_textencoding;
$parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
if ($id3v2_majorversion == 2) {
$parsedFrame['imagetype'] = isset($frame_imagetype) ? $frame_imagetype : null;
} else {
$parsedFrame['mime'] = isset($frame_mimetype) ? $frame_mimetype : null;
}
$parsedFrame['picturetypeid'] = $frame_picturetype;
$parsedFrame['picturetype'] = $this->APICPictureTypeLookup($frame_picturetype);
$parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator));
$parsedFrame['datalength'] = strlen($parsedFrame['data']);
$parsedFrame['image_mime'] = '';
$imageinfo = array();
if ($imagechunkcheck = getid3_lib::GetDataImageSize($parsedFrame['data'], $imageinfo)) {
if (($imagechunkcheck[2] >= 1) && ($imagechunkcheck[2] <= 3)) {
$parsedFrame['image_mime'] = image_type_to_mime_type($imagechunkcheck[2]);
if ($imagechunkcheck[0]) {
$parsedFrame['image_width'] = $imagechunkcheck[0];
}
if ($imagechunkcheck[1]) {
$parsedFrame['image_height'] = $imagechunkcheck[1];
}
}
}
do {
if ($this->getid3->option_save_attachments === false) {
// skip entirely
unset($parsedFrame['data']);
break;
}
$dir = '';
if ($this->getid3->option_save_attachments === true) {
// great
/*
} elseif (is_int($this->getid3->option_save_attachments)) {
if ($this->getid3->option_save_attachments < $parsedFrame['data_length']) {
// too big, skip
$this->warning('attachment at '.$frame_offset.' is too large to process inline ('.number_format($parsedFrame['data_length']).' bytes)');
unset($parsedFrame['data']);
break;
}
*/
} elseif (is_string($this->getid3->option_save_attachments)) {
$dir = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->getid3->option_save_attachments), DIRECTORY_SEPARATOR);
if (!is_dir($dir) || !getID3::is_writable($dir)) {
// cannot write, skip
$this->warning('attachment at '.$frame_offset.' cannot be saved to "'.$dir.'" (not writable)');
unset($parsedFrame['data']);
break;
}
}
// if we get this far, must be OK
if (is_string($this->getid3->option_save_attachments)) {
$destination_filename = $dir.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$frame_offset;
if (!file_exists($destination_filename) || getID3::is_writable($destination_filename)) {
file_put_contents($destination_filename, $parsedFrame['data']);
} else {
$this->warning('attachment at '.$frame_offset.' cannot be saved to "'.$destination_filename.'" (not writable)');
}
$parsedFrame['data_filename'] = $destination_filename;
unset($parsedFrame['data']);
} else {
if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
if (!isset($info['id3v2']['comments']['picture'])) {
$info['id3v2']['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($parsedFrame[$picture_key])) {
$comments_picture_data[$picture_key] = $parsedFrame[$picture_key];
}
}
$info['id3v2']['comments']['picture'][] = $comments_picture_data;
unset($comments_picture_data);
}
}
} while (false);
}
} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'GEOB')) || // 4.15 GEOB General encapsulated object
(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'GEO'))) { // 4.16 GEO General encapsulated object
// There may be more than one 'GEOB' frame in each tag,
// but only one with the same content descriptor
// <Header for 'General encapsulated object', ID: 'GEOB'>
// Text encoding $xx
// MIME type <text string> $00
// Filename <text string according to encoding> $00 (00)
// Content description <text string according to encoding> $00 (00)
// Encapsulated object <binary data>
$frame_offset = 0;
$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
$frame_textencoding_terminator = "\x00";
}
$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
$frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
if (ord($frame_mimetype) === 0) {
$frame_mimetype = '';
}
$frame_offset = $frame_terminatorpos + strlen("\x00");
$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
}
$frame_filename = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
if (ord($frame_filename) === 0) {
$frame_filename = '';
}
$frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator);
$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
}
$parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
$parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
$frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator);
$parsedFrame['objectdata'] = (string) substr($parsedFrame['data'], $frame_offset);
$parsedFrame['encodingid'] = $frame_textencoding;
$parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
$parsedFrame['mime'] = $frame_mimetype;
$parsedFrame['filename'] = $frame_filename;
unset($parsedFrame['data']);
} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'PCNT')) || // 4.16 PCNT Play counter
(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CNT'))) { // 4.17 CNT Play counter
// There may only be one 'PCNT' frame in each tag.
// When the counter reaches all one's, one byte is inserted in
// front of the counter thus making the counter eight bits bigger
// <Header for 'Play counter', ID: 'PCNT'>
// Counter $xx xx xx xx (xx ...)
$parsedFrame['data'] = getid3_lib::BigEndian2Int($parsedFrame['data']);
} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'POPM')) || // 4.17 POPM Popularimeter
(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'POP'))) { // 4.18 POP Popularimeter
// There may be more than one 'POPM' frame in each tag,
// but only one with the same email address
// <Header for 'Popularimeter', ID: 'POPM'>
// Email to user <text string> $00
// Rating $xx
// Counter $xx xx xx xx (xx ...)
$frame_offset = 0;
$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
$frame_emailaddress = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
if (ord($frame_emailaddress) === 0) {
$frame_emailaddress = '';
}
$frame_offset = $frame_terminatorpos + strlen("\x00");
$frame_rating = ord(substr($parsedFrame['data'], $frame_offset++, 1));
$parsedFrame['counter'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset));
$parsedFrame['email'] = $frame_emailaddress;
$parsedFrame['rating'] = $frame_rating;
unset($parsedFrame['data']);
} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RBUF')) || // 4.18 RBUF Recommended buffer size
(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'BUF'))) { // 4.19 BUF Recommended buffer size
// There may only be one 'RBUF' frame in each tag
// <Header for 'Recommended buffer size', ID: 'RBUF'>
// Buffer size $xx xx xx
// Embedded info flag %0000000x
// Offset to next tag $xx xx xx xx
$frame_offset = 0;
$parsedFrame['buffersize'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 3));
$frame_offset += 3;
$frame_embeddedinfoflags = getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1));
$parsedFrame['flags']['embededinfo'] = (bool) substr($frame_embeddedinfoflags, 7, 1);
$parsedFrame['nexttagoffset'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
unset($parsedFrame['data']);
} elseif (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CRM')) { // 4.20 Encrypted meta frame (ID3v2.2 only)
// There may be more than one 'CRM' frame in a tag,
// but only one with the same 'owner identifier'
// <Header for 'Encrypted meta frame', ID: 'CRM'>
// Owner identifier <textstring> $00 (00)
// Content/explanation <textstring> $00 (00)
// Encrypted datablock <binary data>
$frame_offset = 0;
$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
$frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
$frame_offset = $frame_terminatorpos + strlen("\x00");
$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
$parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
$parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
$frame_offset = $frame_terminatorpos + strlen("\x00");
$parsedFrame['ownerid'] = $frame_ownerid;
$parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
unset($parsedFrame['data']);
} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'AENC')) || // 4.19 AENC Audio encryption
(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CRA'))) { // 4.21 CRA Audio encryption
// There may be more than one 'AENC' frames in a tag,
// but only one with the same 'Owner identifier'
// <Header for 'Audio encryption', ID: 'AENC'>
// Owner identifier <text string> $00
// Preview start $xx xx
// Preview length $xx xx
// Encryption info <binary data>
$frame_offset = 0;
$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
$frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
if (ord($frame_ownerid) === 0) {
$frame_ownerid = '';
}
$frame_offset = $frame_terminatorpos + strlen("\x00");
$parsedFrame['ownerid'] = $frame_ownerid;
$parsedFrame['previewstart'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
$frame_offset += 2;
$parsedFrame['previewlength'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
$frame_offset += 2;
$parsedFrame['encryptioninfo'] = (string) substr($parsedFrame['data'], $frame_offset);
unset($parsedFrame['data']);
} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'LINK')) || // 4.20 LINK Linked information
(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'LNK'))) { // 4.22 LNK Linked information
// There may be more than one 'LINK' frame in a tag,
// but only one with the same contents
// <Header for 'Linked information', ID: 'LINK'>
// ID3v2.3+ => Frame identifier $xx xx xx xx
// ID3v2.2 => Frame identifier $xx xx xx
// URL <text string> $00
// ID and additional data <text string(s)>
$frame_offset = 0;
if ($id3v2_majorversion == 2) {
$parsedFrame['frameid'] = substr($parsedFrame['data'], $frame_offset, 3);
$frame_offset += 3;
} else {
$parsedFrame['frameid'] = substr($parsedFrame['data'], $frame_offset, 4);
$frame_offset += 4;
}
$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
$frame_url = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
if (ord($frame_url) === 0) {
$frame_url = '';
}
$frame_offset = $frame_terminatorpos + strlen("\x00");
$parsedFrame['url'] = $frame_url;
$parsedFrame['additionaldata'] = (string) substr($parsedFrame['data'], $frame_offset);
if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback_iso88591_utf8($parsedFrame['url']);
}
unset($parsedFrame['data']);
} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'POSS')) { // 4.21 POSS Position synchronisation frame (ID3v2.3+ only)
// There may only be one 'POSS' frame in each tag
// <Head for 'Position synchronisation', ID: 'POSS'>
// Time stamp format $xx
// Position $xx (xx ...)
$frame_offset = 0;
$parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
$parsedFrame['position'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset));
unset($parsedFrame['data']);
} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'USER')) { // 4.22 USER Terms of use (ID3v2.3+ only)
// There may be more than one 'Terms of use' frame in a tag,
// but only one with the same 'Language'
// <Header for 'Terms of use frame', ID: 'USER'>
// Text encoding $xx
// Language $xx xx xx
// The actual text <text string according to encoding>
$frame_offset = 0;
$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
}
$frame_language = substr($parsedFrame['data'], $frame_offset, 3);
$frame_offset += 3;
$parsedFrame['language'] = $frame_language;
$parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
$parsedFrame['encodingid'] = $frame_textencoding;
$parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
$parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
$parsedFrame['data'] = $this->RemoveStringTerminator($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding));
if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
}
unset($parsedFrame['data']);
} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'OWNE')) { // 4.23 OWNE Ownership frame (ID3v2.3+ only)
// There may only be one 'OWNE' frame in a tag
// <Header for 'Ownership frame', ID: 'OWNE'>
// Text encoding $xx
// Price paid <text string> $00
// Date of purch. <text string>
// Seller <text string according to encoding>
$frame_offset = 0;
$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
}
$parsedFrame['encodingid'] = $frame_textencoding;
$parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
$frame_pricepaid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
$frame_offset = $frame_terminatorpos + strlen("\x00");
$parsedFrame['pricepaid']['currencyid'] = substr($frame_pricepaid, 0, 3);
$parsedFrame['pricepaid']['currency'] = $this->LookupCurrencyUnits($parsedFrame['pricepaid']['currencyid']);
$parsedFrame['pricepaid']['value'] = substr($frame_pricepaid, 3);
$parsedFrame['purchasedate'] = substr($parsedFrame['data'], $frame_offset, 8);
if ($this->IsValidDateStampString($parsedFrame['purchasedate'])) {
$parsedFrame['purchasedateunix'] = mktime (0, 0, 0, substr($parsedFrame['purchasedate'], 4, 2), substr($parsedFrame['purchasedate'], 6, 2), substr($parsedFrame['purchasedate'], 0, 4));
}
$frame_offset += 8;
$parsedFrame['seller'] = (string) substr($parsedFrame['data'], $frame_offset);
$parsedFrame['seller'] = $this->RemoveStringTerminator($parsedFrame['seller'], $this->TextEncodingTerminatorLookup($frame_textencoding));
unset($parsedFrame['data']);
} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'COMR')) { // 4.24 COMR Commercial frame (ID3v2.3+ only)
// There may be more than one 'commercial frame' in a tag,
// but no two may be identical
// <Header for 'Commercial frame', ID: 'COMR'>
// Text encoding $xx
// Price string <text string> $00
// Valid until <text string>
// Contact URL <text string> $00
// Received as $xx
// Name of seller <text string according to encoding> $00 (00)
// Description <text string according to encoding> $00 (00)
// Picture MIME type <string> $00
// Seller logo <binary data>
$frame_offset = 0;
$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
$frame_textencoding_terminator = $this->TextEncodingTerminatorLookup($frame_textencoding);
if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
$frame_textencoding_terminator = "\x00";
}
$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
$frame_pricestring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
$frame_offset = $frame_terminatorpos + strlen("\x00");
$frame_rawpricearray = explode('/', $frame_pricestring);
foreach ($frame_rawpricearray as $key => $val) {
$frame_currencyid = substr($val, 0, 3);
$parsedFrame['price'][$frame_currencyid]['currency'] = $this->LookupCurrencyUnits($frame_currencyid);
$parsedFrame['price'][$frame_currencyid]['value'] = substr($val, 3);
}
$frame_datestring = substr($parsedFrame['data'], $frame_offset, 8);
$frame_offset += 8;
$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
$frame_contacturl = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
$frame_offset = $frame_terminatorpos + strlen("\x00");
$frame_receivedasid = ord(substr($parsedFrame['data'], $frame_offset++, 1));
$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
}
$frame_sellername = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
if (ord($frame_sellername) === 0) {
$frame_sellername = '';
}
$frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator);
$frame_terminatorpos = strpos($parsedFrame['data'], $frame_textencoding_terminator, $frame_offset);
if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($frame_textencoding_terminator), 1)) === 0) {
$frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
}
$parsedFrame['description'] = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
$parsedFrame['description'] = $this->MakeUTF16emptyStringEmpty($parsedFrame['description']);
$frame_offset = $frame_terminatorpos + strlen($frame_textencoding_terminator);
$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
$frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
$frame_offset = $frame_terminatorpos + strlen("\x00");
$frame_sellerlogo = substr($parsedFrame['data'], $frame_offset);
$parsedFrame['encodingid'] = $frame_textencoding;
$parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
$parsedFrame['pricevaliduntil'] = $frame_datestring;
$parsedFrame['contacturl'] = $frame_contacturl;
$parsedFrame['receivedasid'] = $frame_receivedasid;
$parsedFrame['receivedas'] = $this->COMRReceivedAsLookup($frame_receivedasid);
$parsedFrame['sellername'] = $frame_sellername;
$parsedFrame['mime'] = $frame_mimetype;
$parsedFrame['logo'] = $frame_sellerlogo;
unset($parsedFrame['data']);
} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'ENCR')) { // 4.25 ENCR Encryption method registration (ID3v2.3+ only)
// There may be several 'ENCR' frames in a tag,
// but only one containing the same symbol
// and only one containing the same owner identifier
// <Header for 'Encryption method registration', ID: 'ENCR'>
// Owner identifier <text string> $00
// Method symbol $xx
// Encryption data <binary data>
$frame_offset = 0;
$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
$frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
if (ord($frame_ownerid) === 0) {
$frame_ownerid = '';
}
$frame_offset = $frame_terminatorpos + strlen("\x00");
$parsedFrame['ownerid'] = $frame_ownerid;
$parsedFrame['methodsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
$parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'GRID')) { // 4.26 GRID Group identification registration (ID3v2.3+ only)
// There may be several 'GRID' frames in a tag,
// but only one containing the same symbol
// and only one containing the same owner identifier
// <Header for 'Group ID registration', ID: 'GRID'>
// Owner identifier <text string> $00
// Group symbol $xx
// Group dependent data <binary data>
$frame_offset = 0;
$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
$frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
if (ord($frame_ownerid) === 0) {
$frame_ownerid = '';
}
$frame_offset = $frame_terminatorpos + strlen("\x00");
$parsedFrame['ownerid'] = $frame_ownerid;
$parsedFrame['groupsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
$parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'PRIV')) { // 4.27 PRIV Private frame (ID3v2.3+ only)
// The tag may contain more than one 'PRIV' frame
// but only with different contents
// <Header for 'Private frame', ID: 'PRIV'>
// Owner identifier <text string> $00
// The private data <binary data>
$frame_offset = 0;
$frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
$frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
if (ord($frame_ownerid) === 0) {
$frame_ownerid = '';
}
$frame_offset = $frame_terminatorpos + strlen("\x00");
$parsedFrame['ownerid'] = $frame_ownerid;
$parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
} elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'SIGN')) { // 4.28 SIGN Signature frame (ID3v2.4+ only)
// There may be more than one 'signature frame' in a tag,
// but no two may be identical
// <Header for 'Signature frame', ID: 'SIGN'>
// Group symbol $xx
// Signature <binary data>
$frame_offset = 0;
$parsedFrame['groupsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
$parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
} elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'SEEK')) { // 4.29 SEEK Seek frame (ID3v2.4+ only)
// There may only be one 'seek frame' in a tag
// <Header for 'Seek frame', ID: 'SEEK'>
// Minimum offset to next tag $xx xx xx xx
$frame_offset = 0;
$parsedFrame['data'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
} elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'ASPI')) { // 4.30 ASPI Audio seek point index (ID3v2.4+ only)
// There may only be one 'audio seek point index' frame in a tag
// <Header for 'Seek Point Index', ID: 'ASPI'>
// Indexed data start (S) $xx xx xx xx
// Indexed data length (L) $xx xx xx xx
// Number of index points (N) $xx xx
// Bits per index point (b) $xx
// Then for every index point the following data is included:
// Fraction at index (Fi) $xx (xx)
$frame_offset = 0;
$parsedFrame['datastart'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
$frame_offset += 4;
$parsedFrame['indexeddatalength'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
$frame_offset += 4;
$parsedFrame['indexpoints'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
$frame_offset += 2;
$parsedFrame['bitsperpoint'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
$frame_bytesperpoint = ceil($parsedFrame['bitsperpoint'] / 8);
for ($i = 0; $i < $parsedFrame['indexpoints']; $i++) {
$parsedFrame['indexes'][$i] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesperpoint));
$frame_offset += $frame_bytesperpoint;
}
unset($parsedFrame['data']);
} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RGAD')) { // Replay Gain Adjustment
// http://privatewww.essex.ac.uk/~djmrob/replaygain/file_format_id3v2.html
// There may only be one 'RGAD' frame in a tag
// <Header for 'Replay Gain Adjustment', ID: 'RGAD'>
// Peak Amplitude $xx $xx $xx $xx
// Radio Replay Gain Adjustment %aaabbbcd %dddddddd
// Audiophile Replay Gain Adjustment %aaabbbcd %dddddddd
// a - name code
// b - originator code
// c - sign bit
// d - replay gain adjustment
$frame_offset = 0;
$parsedFrame['peakamplitude'] = getid3_lib::BigEndian2Float(substr($parsedFrame['data'], $frame_offset, 4));
$frame_offset += 4;
$rg_track_adjustment = getid3_lib::Dec2Bin(substr($parsedFrame['data'], $frame_offset, 2));
$frame_offset += 2;
$rg_album_adjustment = getid3_lib::Dec2Bin(substr($parsedFrame['data'], $frame_offset, 2));
$frame_offset += 2;
$parsedFrame['raw']['track']['name'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 0, 3));
$parsedFrame['raw']['track']['originator'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 3, 3));
$parsedFrame['raw']['track']['signbit'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 6, 1));
$parsedFrame['raw']['track']['adjustment'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 7, 9));
$parsedFrame['raw']['album']['name'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 0, 3));
$parsedFrame['raw']['album']['originator'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 3, 3));
$parsedFrame['raw']['album']['signbit'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 6, 1));
$parsedFrame['raw']['album']['adjustment'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 7, 9));
$parsedFrame['track']['name'] = getid3_lib::RGADnameLookup($parsedFrame['raw']['track']['name']);
$parsedFrame['track']['originator'] = getid3_lib::RGADoriginatorLookup($parsedFrame['raw']['track']['originator']);
$parsedFrame['track']['adjustment'] = getid3_lib::RGADadjustmentLookup($parsedFrame['raw']['track']['adjustment'], $parsedFrame['raw']['track']['signbit']);
$parsedFrame['album']['name'] = getid3_lib::RGADnameLookup($parsedFrame['raw']['album']['name']);
$parsedFrame['album']['originator'] = getid3_lib::RGADoriginatorLookup($parsedFrame['raw']['album']['originator']);
$parsedFrame['album']['adjustment'] = getid3_lib::RGADadjustmentLookup($parsedFrame['raw']['album']['adjustment'], $parsedFrame['raw']['album']['signbit']);
$info['replay_gain']['track']['peak'] = $parsedFrame['peakamplitude'];
$info['replay_gain']['track']['originator'] = $parsedFrame['track']['originator'];
$info['replay_gain']['track']['adjustment'] = $parsedFrame['track']['adjustment'];
$info['replay_gain']['album']['originator'] = $parsedFrame['album']['originator'];
$info['replay_gain']['album']['adjustment'] = $parsedFrame['album']['adjustment'];
unset($parsedFrame['data']);
} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'CHAP')) { // CHAP Chapters frame (ID3v2.3+ only)
// http://id3.org/id3v2-chapters-1.0
// <ID3v2.3 or ID3v2.4 frame header, ID: "CHAP"> (10 bytes)
// Element ID <text string> $00
// Start time $xx xx xx xx
// End time $xx xx xx xx
// Start offset $xx xx xx xx
// End offset $xx xx xx xx
// <Optional embedded sub-frames>
$frame_offset = 0;
@list($parsedFrame['element_id']) = explode("\x00", $parsedFrame['data'], 2);
$frame_offset += strlen($parsedFrame['element_id']."\x00");
$parsedFrame['time_begin'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
$frame_offset += 4;
$parsedFrame['time_end'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
$frame_offset += 4;
if (substr($parsedFrame['data'], $frame_offset, 4) != "\xFF\xFF\xFF\xFF") {
// "If these bytes are all set to 0xFF then the value should be ignored and the start time value should be utilized."
$parsedFrame['offset_begin'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
}
$frame_offset += 4;
if (substr($parsedFrame['data'], $frame_offset, 4) != "\xFF\xFF\xFF\xFF") {
// "If these bytes are all set to 0xFF then the value should be ignored and the start time value should be utilized."
$parsedFrame['offset_end'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
}
$frame_offset += 4;
if ($frame_offset < strlen($parsedFrame['data'])) {
$parsedFrame['subframes'] = array();
while ($frame_offset < strlen($parsedFrame['data'])) {
// <Optional embedded sub-frames>
$subframe = array();
$subframe['name'] = substr($parsedFrame['data'], $frame_offset, 4);
$frame_offset += 4;
$subframe['size'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
$frame_offset += 4;
$subframe['flags_raw'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
$frame_offset += 2;
if ($subframe['size'] > (strlen($parsedFrame['data']) - $frame_offset)) {
$this->warning('CHAP subframe "'.$subframe['name'].'" at frame offset '.$frame_offset.' claims to be "'.$subframe['size'].'" bytes, which is more than the available data ('.(strlen($parsedFrame['data']) - $frame_offset).' bytes)');
break;
}
$subframe_rawdata = substr($parsedFrame['data'], $frame_offset, $subframe['size']);
$frame_offset += $subframe['size'];
$subframe['encodingid'] = ord(substr($subframe_rawdata, 0, 1));
$subframe['text'] = substr($subframe_rawdata, 1);
$subframe['encoding'] = $this->TextEncodingNameLookup($subframe['encodingid']);
$encoding_converted_text = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe['text']));
switch (substr($encoding_converted_text, 0, 2)) {
case "\xFF\xFE":
case "\xFE\xFF":
switch (strtoupper($info['id3v2']['encoding'])) {
case 'ISO-8859-1':
case 'UTF-8':
$encoding_converted_text = substr($encoding_converted_text, 2);
// remove unwanted byte-order-marks
break;
default:
// ignore
break;
}
break;
default:
// do not remove BOM
break;
}
switch ($subframe['name']) {
case 'TIT2':
$parsedFrame['chapter_name'] = $encoding_converted_text;
$parsedFrame['subframes'][] = $subframe;
break;
case 'TIT3':
$parsedFrame['chapter_description'] = $encoding_converted_text;
$parsedFrame['subframes'][] = $subframe;
break;
case 'WXXX':
list($subframe['chapter_url_description'], $subframe['chapter_url']) = explode("\x00", $encoding_converted_text, 2);
$parsedFrame['chapter_url'][$subframe['chapter_url_description']] = $subframe['chapter_url'];
$parsedFrame['subframes'][] = $subframe;
break;
case 'APIC':
if (preg_match('#^([^\\x00]+)*\\x00(.)([^\\x00]+)*\\x00(.+)$#s', $subframe['text'], $matches)) {
list($dummy, $subframe_apic_mime, $subframe_apic_picturetype, $subframe_apic_description, $subframe_apic_picturedata) = $matches;
$subframe['image_mime'] = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe_apic_mime));
$subframe['picture_type'] = $this->APICPictureTypeLookup($subframe_apic_picturetype);
$subframe['description'] = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe_apic_description));
if (strlen($this->TextEncodingTerminatorLookup($subframe['encoding'])) == 2) {
// the null terminator between "description" and "picture data" could be either 1 byte (ISO-8859-1, UTF-8) or two bytes (UTF-16)
// the above regex assumes one byte, if it's actually two then strip the second one here
$subframe_apic_picturedata = substr($subframe_apic_picturedata, 1);
}
$subframe['data'] = $subframe_apic_picturedata;
unset($dummy, $subframe_apic_mime, $subframe_apic_picturetype, $subframe_apic_description, $subframe_apic_picturedata);
unset($subframe['text'], $parsedFrame['text']);
$parsedFrame['subframes'][] = $subframe;
$parsedFrame['picture_present'] = true;
} else {
$this->warning('ID3v2.CHAP subframe #'.(count($parsedFrame['subframes']) + 1).' "'.$subframe['name'].'" not in expected format');
}
break;
default:
$this->warning('ID3v2.CHAP subframe "'.$subframe['name'].'" not handled (supported: TIT2, TIT3, WXXX, APIC)');
break;
}
}
unset($subframe_rawdata, $subframe, $encoding_converted_text);
unset($parsedFrame['data']); // debatable whether this this be here, without it the returned structure may contain a large amount of duplicate data if chapters contain APIC
}
$id3v2_chapter_entry = array();
foreach (array('id', 'time_begin', 'time_end', 'offset_begin', 'offset_end', 'chapter_name', 'chapter_description', 'chapter_url', 'picture_present') as $id3v2_chapter_key) {
if (isset($parsedFrame[$id3v2_chapter_key])) {
$id3v2_chapter_entry[$id3v2_chapter_key] = $parsedFrame[$id3v2_chapter_key];
}
}
if (!isset($info['id3v2']['chapters'])) {
$info['id3v2']['chapters'] = array();
}
$info['id3v2']['chapters'][] = $id3v2_chapter_entry;
unset($id3v2_chapter_entry, $id3v2_chapter_key);
} elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'CTOC')) { // CTOC Chapters Table Of Contents frame (ID3v2.3+ only)
// http://id3.org/id3v2-chapters-1.0
// <ID3v2.3 or ID3v2.4 frame header, ID: "CTOC"> (10 bytes)
// Element ID <text string> $00
// CTOC flags %xx
// Entry count $xx
// Child Element ID <string>$00 /* zero or more child CHAP or CTOC entries */
// <Optional embedded sub-frames>
$frame_offset = 0;
@list($parsedFrame['element_id']) = explode("\x00", $parsedFrame['data'], 2);
$frame_offset += strlen($parsedFrame['element_id']."\x00");
$ctoc_flags_raw = ord(substr($parsedFrame['data'], $frame_offset, 1));
$frame_offset += 1;
$parsedFrame['entry_count'] = ord(substr($parsedFrame['data'], $frame_offset, 1));
$frame_offset += 1;
$terminator_position = null;
for ($i = 0; $i < $parsedFrame['entry_count']; $i++) {
$terminator_position = strpos($parsedFrame['data'], "\x00", $frame_offset);
$parsedFrame['child_element_ids'][$i] = substr($parsedFrame['data'], $frame_offset, $terminator_position - $frame_offset);
$frame_offset = $terminator_position + 1;
}
$parsedFrame['ctoc_flags']['ordered'] = (bool) ($ctoc_flags_raw & 0x01);
$parsedFrame['ctoc_flags']['top_level'] = (bool) ($ctoc_flags_raw & 0x03);
unset($ctoc_flags_raw, $terminator_position);
if ($frame_offset < strlen($parsedFrame['data'])) {
$parsedFrame['subframes'] = array();
while ($frame_offset < strlen($parsedFrame['data'])) {
// <Optional embedded sub-frames>
$subframe = array();
$subframe['name'] = substr($parsedFrame['data'], $frame_offset, 4);
$frame_offset += 4;
$subframe['size'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
$frame_offset += 4;
$subframe['flags_raw'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
$frame_offset += 2;
if ($subframe['size'] > (strlen($parsedFrame['data']) - $frame_offset)) {
$this->warning('CTOS subframe "'.$subframe['name'].'" at frame offset '.$frame_offset.' claims to be "'.$subframe['size'].'" bytes, which is more than the available data ('.(strlen($parsedFrame['data']) - $frame_offset).' bytes)');
break;
}
$subframe_rawdata = substr($parsedFrame['data'], $frame_offset, $subframe['size']);
$frame_offset += $subframe['size'];
$subframe['encodingid'] = ord(substr($subframe_rawdata, 0, 1));
$subframe['text'] = substr($subframe_rawdata, 1);
$subframe['encoding'] = $this->TextEncodingNameLookup($subframe['encodingid']);
$encoding_converted_text = trim(getid3_lib::iconv_fallback($subframe['encoding'], $info['encoding'], $subframe['text']));;
switch (substr($encoding_converted_text, 0, 2)) {
case "\xFF\xFE":
case "\xFE\xFF":
switch (strtoupper($info['id3v2']['encoding'])) {
case 'ISO-8859-1':
case 'UTF-8':
$encoding_converted_text = substr($encoding_converted_text, 2);
// remove unwanted byte-order-marks
break;
default:
// ignore
break;
}
break;
default:
// do not remove BOM
break;
}
if (($subframe['name'] == 'TIT2') || ($subframe['name'] == 'TIT3')) {
if ($subframe['name'] == 'TIT2') {
$parsedFrame['toc_name'] = $encoding_converted_text;
} elseif ($subframe['name'] == 'TIT3') {
$parsedFrame['toc_description'] = $encoding_converted_text;
}
$parsedFrame['subframes'][] = $subframe;
} else {
$this->warning('ID3v2.CTOC subframe "'.$subframe['name'].'" not handled (only TIT2 and TIT3)');
}
}
unset($subframe_rawdata, $subframe, $encoding_converted_text);
}
}
return true;
}
/**
* @param string $data
*
* @return string
*/
public function DeUnsynchronise($data) {
return str_replace("\xFF\x00", "\xFF", $data);
}
/**
* @param int $index
*
* @return string
*/
public function LookupExtendedHeaderRestrictionsTagSizeLimits($index) {
static $LookupExtendedHeaderRestrictionsTagSizeLimits = array(
0x00 => 'No more than 128 frames and 1 MB total tag size',
0x01 => 'No more than 64 frames and 128 KB total tag size',
0x02 => 'No more than 32 frames and 40 KB total tag size',
0x03 => 'No more than 32 frames and 4 KB total tag size',
);
return (isset($LookupExtendedHeaderRestrictionsTagSizeLimits[$index]) ? $LookupExtendedHeaderRestrictionsTagSizeLimits[$index] : '');
}
/**
* @param int $index
*
* @return string
*/
public function LookupExtendedHeaderRestrictionsTextEncodings($index) {
static $LookupExtendedHeaderRestrictionsTextEncodings = array(
0x00 => 'No restrictions',
0x01 => 'Strings are only encoded with ISO-8859-1 or UTF-8',
);
return (isset($LookupExtendedHeaderRestrictionsTextEncodings[$index]) ? $LookupExtendedHeaderRestrictionsTextEncodings[$index] : '');
}
/**
* @param int $index
*
* @return string
*/
public function LookupExtendedHeaderRestrictionsTextFieldSize($index) {
static $LookupExtendedHeaderRestrictionsTextFieldSize = array(
0x00 => 'No restrictions',
0x01 => 'No string is longer than 1024 characters',
0x02 => 'No string is longer than 128 characters',
0x03 => 'No string is longer than 30 characters',
);
return (isset($LookupExtendedHeaderRestrictionsTextFieldSize[$index]) ? $LookupExtendedHeaderRestrictionsTextFieldSize[$index] : '');
}
/**
* @param int $index
*
* @return string
*/
public function LookupExtendedHeaderRestrictionsImageEncoding($index) {
static $LookupExtendedHeaderRestrictionsImageEncoding = array(
0x00 => 'No restrictions',
0x01 => 'Images are encoded only with PNG or JPEG',
);
return (isset($LookupExtendedHeaderRestrictionsImageEncoding[$index]) ? $LookupExtendedHeaderRestrictionsImageEncoding[$index] : '');
}
/**
* @param int $index
*
* @return string
*/
public function LookupExtendedHeaderRestrictionsImageSizeSize($index) {
static $LookupExtendedHeaderRestrictionsImageSizeSize = array(
0x00 => 'No restrictions',
0x01 => 'All images are 256x256 pixels or smaller',
0x02 => 'All images are 64x64 pixels or smaller',
0x03 => 'All images are exactly 64x64 pixels, unless required otherwise',
);
return (isset($LookupExtendedHeaderRestrictionsImageSizeSize[$index]) ? $LookupExtendedHeaderRestrictionsImageSizeSize[$index] : '');
}
/**
* @param string $currencyid
*
* @return string
*/
public function LookupCurrencyUnits($currencyid) {
$begin = __LINE__;
/** This is not a comment!
AED Dirhams
AFA Afghanis
ALL Leke
AMD Drams
ANG Guilders
AOA Kwanza
ARS Pesos
ATS Schillings
AUD Dollars
AWG Guilders
AZM Manats
BAM Convertible Marka
BBD Dollars
BDT Taka
BEF Francs
BGL Leva
BHD Dinars
BIF Francs
BMD Dollars
BND Dollars
BOB Bolivianos
BRL Brazil Real
BSD Dollars
BTN Ngultrum
BWP Pulas
BYR Rubles
BZD Dollars
CAD Dollars
CDF Congolese Francs
CHF Francs
CLP Pesos
CNY Yuan Renminbi
COP Pesos
CRC Colones
CUP Pesos
CVE Escudos
CYP Pounds
CZK Koruny
DEM Deutsche Marks
DJF Francs
DKK Kroner
DOP Pesos
DZD Algeria Dinars
EEK Krooni
EGP Pounds
ERN Nakfa
ESP Pesetas
ETB Birr
EUR Euro
FIM Markkaa
FJD Dollars
FKP Pounds
FRF Francs
GBP Pounds
GEL Lari
GGP Pounds
GHC Cedis
GIP Pounds
GMD Dalasi
GNF Francs
GRD Drachmae
GTQ Quetzales
GYD Dollars
HKD Dollars
HNL Lempiras
HRK Kuna
HTG Gourdes
HUF Forints
IDR Rupiahs
IEP Pounds
ILS New Shekels
IMP Pounds
INR Rupees
IQD Dinars
IRR Rials
ISK Kronur
ITL Lire
JEP Pounds
JMD Dollars
JOD Dinars
JPY Yen
KES Shillings
KGS Soms
KHR Riels
KMF Francs
KPW Won
KWD Dinars
KYD Dollars
KZT Tenge
LAK Kips
LBP Pounds
LKR Rupees
LRD Dollars
LSL Maloti
LTL Litai
LUF Francs
LVL Lati
LYD Dinars
MAD Dirhams
MDL Lei
MGF Malagasy Francs
MKD Denars
MMK Kyats
MNT Tugriks
MOP Patacas
MRO Ouguiyas
MTL Liri
MUR Rupees
MVR Rufiyaa
MWK Kwachas
MXN Pesos
MYR Ringgits
MZM Meticais
NAD Dollars
NGN Nairas
NIO Gold Cordobas
NLG Guilders
NOK Krone
NPR Nepal Rupees
NZD Dollars
OMR Rials
PAB Balboa
PEN Nuevos Soles
PGK Kina
PHP Pesos
PKR Rupees
PLN Zlotych
PTE Escudos
PYG Guarani
QAR Rials
ROL Lei
RUR Rubles
RWF Rwanda Francs
SAR Riyals
SBD Dollars
SCR Rupees
SDD Dinars
SEK Kronor
SGD Dollars
SHP Pounds
SIT Tolars
SKK Koruny
SLL Leones
SOS Shillings
SPL Luigini
SRG Guilders
STD Dobras
SVC Colones
SYP Pounds
SZL Emalangeni
THB Baht
TJR Rubles
TMM Manats
TND Dinars
TOP Pa'anga
TRL Liras
TTD Dollars
TVD Tuvalu Dollars
TWD New Dollars
TZS Shillings
UAH Hryvnia
UGX Shillings
USD Dollars
UYU Pesos
UZS Sums
VAL Lire
VEB Bolivares
VND Dong
VUV Vatu
WST Tala
XAF Francs
XAG Ounces
XAU Ounces
XCD Dollars
XDR Special Drawing Rights
XPD Ounces
XPF Francs
XPT Ounces
YER Rials
YUM New Dinars
ZAR Rand
ZMK Kwacha
ZWD Zimbabwe Dollars
*/
return getid3_lib::EmbeddedLookup($currencyid, $begin, __LINE__, __FILE__, 'id3v2-currency-units');
}
/**
* @param string $currencyid
*
* @return string
*/
public function LookupCurrencyCountry($currencyid) {
$begin = __LINE__;
/** This is not a comment!
AED United Arab Emirates
AFA Afghanistan
ALL Albania
AMD Armenia
ANG Netherlands Antilles
AOA Angola
ARS Argentina
ATS Austria
AUD Australia
AWG Aruba
AZM Azerbaijan
BAM Bosnia and Herzegovina
BBD Barbados
BDT Bangladesh
BEF Belgium
BGL Bulgaria
BHD Bahrain
BIF Burundi
BMD Bermuda
BND Brunei Darussalam
BOB Bolivia
BRL Brazil
BSD Bahamas
BTN Bhutan
BWP Botswana
BYR Belarus
BZD Belize
CAD Canada
CDF Congo/Kinshasa
CHF Switzerland
CLP Chile
CNY China
COP Colombia
CRC Costa Rica
CUP Cuba
CVE Cape Verde
CYP Cyprus
CZK Czech Republic
DEM Germany
DJF Djibouti
DKK Denmark
DOP Dominican Republic
DZD Algeria
EEK Estonia
EGP Egypt
ERN Eritrea
ESP Spain
ETB Ethiopia
EUR Euro Member Countries
FIM Finland
FJD Fiji
FKP Falkland Islands (Malvinas)
FRF France
GBP United Kingdom
GEL Georgia
GGP Guernsey
GHC Ghana
GIP Gibraltar
GMD Gambia
GNF Guinea
GRD Greece
GTQ Guatemala
GYD Guyana
HKD Hong Kong
HNL Honduras
HRK Croatia
HTG Haiti
HUF Hungary
IDR Indonesia
IEP Ireland (Eire)
ILS Israel
IMP Isle of Man
INR India
IQD Iraq
IRR Iran
ISK Iceland
ITL Italy
JEP Jersey
JMD Jamaica
JOD Jordan
JPY Japan
KES Kenya
KGS Kyrgyzstan
KHR Cambodia
KMF Comoros
KPW Korea
KWD Kuwait
KYD Cayman Islands
KZT Kazakstan
LAK Laos
LBP Lebanon
LKR Sri Lanka
LRD Liberia
LSL Lesotho
LTL Lithuania
LUF Luxembourg
LVL Latvia
LYD Libya
MAD Morocco
MDL Moldova
MGF Madagascar
MKD Macedonia
MMK Myanmar (Burma)
MNT Mongolia
MOP Macau
MRO Mauritania
MTL Malta
MUR Mauritius
MVR Maldives (Maldive Islands)
MWK Malawi
MXN Mexico
MYR Malaysia
MZM Mozambique
NAD Namibia
NGN Nigeria
NIO Nicaragua
NLG Netherlands (Holland)
NOK Norway
NPR Nepal
NZD New Zealand
OMR Oman
PAB Panama
PEN Peru
PGK Papua New Guinea
PHP Philippines
PKR Pakistan
PLN Poland
PTE Portugal
PYG Paraguay
QAR Qatar
ROL Romania
RUR Russia
RWF Rwanda
SAR Saudi Arabia
SBD Solomon Islands
SCR Seychelles
SDD Sudan
SEK Sweden
SGD Singapore
SHP Saint Helena
SIT Slovenia
SKK Slovakia
SLL Sierra Leone
SOS Somalia
SPL Seborga
SRG Suriname
STD São Tome and Principe
SVC El Salvador
SYP Syria
SZL Swaziland
THB Thailand
TJR Tajikistan
TMM Turkmenistan
TND Tunisia
TOP Tonga
TRL Turkey
TTD Trinidad and Tobago
TVD Tuvalu
TWD Taiwan
TZS Tanzania
UAH Ukraine
UGX Uganda
USD United States of America
UYU Uruguay
UZS Uzbekistan
VAL Vatican City
VEB Venezuela
VND Viet Nam
VUV Vanuatu
WST Samoa
XAF Communauté Financière Africaine
XAG Silver
XAU Gold
XCD East Caribbean
XDR International Monetary Fund
XPD Palladium
XPF Comptoirs Français du Pacifique
XPT Platinum
YER Yemen
YUM Yugoslavia
ZAR South Africa
ZMK Zambia
ZWD Zimbabwe
*/
return getid3_lib::EmbeddedLookup($currencyid, $begin, __LINE__, __FILE__, 'id3v2-currency-country');
}
/**
* @param string $languagecode
* @param bool $casesensitive
*
* @return string
*/
public static function LanguageLookup($languagecode, $casesensitive=false) {
if (!$casesensitive) {
$languagecode = strtolower($languagecode);
}
// http://www.id3.org/id3v2.4.0-structure.txt
// [4. ID3v2 frame overview]
// The three byte language field, present in several frames, is used to
// describe the language of the frame's content, according to ISO-639-2
// [ISO-639-2]. The language should be represented in lower case. If the
// language is not known the string "XXX" should be used.
// ISO 639-2 - http://www.id3.org/iso639-2.html
$begin = __LINE__;
/** This is not a comment!
XXX unknown
xxx unknown
aar Afar
abk Abkhazian
ace Achinese
ach Acoli
ada Adangme
afa Afro-Asiatic (Other)
afh Afrihili
afr Afrikaans
aka Akan
akk Akkadian
alb Albanian
ale Aleut
alg Algonquian Languages
amh Amharic
ang English, Old (ca. 450-1100)
apa Apache Languages
ara Arabic
arc Aramaic
arm Armenian
arn Araucanian
arp Arapaho
art Artificial (Other)
arw Arawak
asm Assamese
ath Athapascan Languages
ava Avaric
ave Avestan
awa Awadhi
aym Aymara
aze Azerbaijani
bad Banda
bai Bamileke Languages
bak Bashkir
bal Baluchi
bam Bambara
ban Balinese
baq Basque
bas Basa
bat Baltic (Other)
bej Beja
bel Byelorussian
bem Bemba
ben Bengali
ber Berber (Other)
bho Bhojpuri
bih Bihari
bik Bikol
bin Bini
bis Bislama
bla Siksika
bnt Bantu (Other)
bod Tibetan
bra Braj
bre Breton
bua Buriat
bug Buginese
bul Bulgarian
bur Burmese
cad Caddo
cai Central American Indian (Other)
car Carib
cat Catalan
cau Caucasian (Other)
ceb Cebuano
cel Celtic (Other)
ces Czech
cha Chamorro
chb Chibcha
che Chechen
chg Chagatai
chi Chinese
chm Mari
chn Chinook jargon
cho Choctaw
chr Cherokee
chu Church Slavic
chv Chuvash
chy Cheyenne
cop Coptic
cor Cornish
cos Corsican
cpe Creoles and Pidgins, English-based (Other)
cpf Creoles and Pidgins, French-based (Other)
cpp Creoles and Pidgins, Portuguese-based (Other)
cre Cree
crp Creoles and Pidgins (Other)
cus Cushitic (Other)
cym Welsh
cze Czech
dak Dakota
dan Danish
del Delaware
deu German
din Dinka
div Divehi
doi Dogri
dra Dravidian (Other)
dua Duala
dum Dutch, Middle (ca. 1050-1350)
dut Dutch
dyu Dyula
dzo Dzongkha
efi Efik
egy Egyptian (Ancient)
eka Ekajuk
ell Greek, Modern (1453-)
elx Elamite
eng English
enm English, Middle (ca. 1100-1500)
epo Esperanto
esk Eskimo (Other)
esl Spanish
est Estonian
eus Basque
ewe Ewe
ewo Ewondo
fan Fang
fao Faroese
fas Persian
fat Fanti
fij Fijian
fin Finnish
fiu Finno-Ugrian (Other)
fon Fon
fra French
fre French
frm French, Middle (ca. 1400-1600)
fro French, Old (842- ca. 1400)
fry Frisian
ful Fulah
gaa Ga
gae Gaelic (Scots)
gai Irish
gay Gayo
gdh Gaelic (Scots)
gem Germanic (Other)
geo Georgian
ger German
gez Geez
gil Gilbertese
glg Gallegan
gmh German, Middle High (ca. 1050-1500)
goh German, Old High (ca. 750-1050)
gon Gondi
got Gothic
grb Grebo
grc Greek, Ancient (to 1453)
gre Greek, Modern (1453-)
grn Guarani
guj Gujarati
hai Haida
hau Hausa
haw Hawaiian
heb Hebrew
her Herero
hil Hiligaynon
him Himachali
hin Hindi
hmo Hiri Motu
hun Hungarian
hup Hupa
hye Armenian
iba Iban
ibo Igbo
ice Icelandic
ijo Ijo
iku Inuktitut
ilo Iloko
ina Interlingua (International Auxiliary language Association)
inc Indic (Other)
ind Indonesian
ine Indo-European (Other)
ine Interlingue
ipk Inupiak
ira Iranian (Other)
iri Irish
iro Iroquoian uages
isl Icelandic
ita Italian
jav Javanese
jaw Javanese
jpn Japanese
jpr Judeo-Persian
jrb Judeo-Arabic
kaa Kara-Kalpak
kab Kabyle
kac Kachin
kal Greenlandic
kam Kamba
kan Kannada
kar Karen
kas Kashmiri
kat Georgian
kau Kanuri
kaw Kawi
kaz Kazakh
kha Khasi
khi Khoisan (Other)
khm Khmer
kho Khotanese
kik Kikuyu
kin Kinyarwanda
kir Kirghiz
kok Konkani
kom Komi
kon Kongo
kor Korean
kpe Kpelle
kro Kru
kru Kurukh
kua Kuanyama
kum Kumyk
kur Kurdish
kus Kusaie
kut Kutenai
lad Ladino
lah Lahnda
lam Lamba
lao Lao
lat Latin
lav Latvian
lez Lezghian
lin Lingala
lit Lithuanian
lol Mongo
loz Lozi
ltz Letzeburgesch
lub Luba-Katanga
lug Ganda
lui Luiseno
lun Lunda
luo Luo (Kenya and Tanzania)
mac Macedonian
mad Madurese
mag Magahi
mah Marshall
mai Maithili
mak Macedonian
mak Makasar
mal Malayalam
man Mandingo
mao Maori
map Austronesian (Other)
mar Marathi
mas Masai
max Manx
may Malay
men Mende
mga Irish, Middle (900 - 1200)
mic Micmac
min Minangkabau
mis Miscellaneous (Other)
mkh Mon-Kmer (Other)
mlg Malagasy
mlt Maltese
mni Manipuri
mno Manobo Languages
moh Mohawk
mol Moldavian
mon Mongolian
mos Mossi
mri Maori
msa Malay
mul Multiple Languages
mun Munda Languages
mus Creek
mwr Marwari
mya Burmese
myn Mayan Languages
nah Aztec
nai North American Indian (Other)
nau Nauru
nav Navajo
nbl Ndebele, South
nde Ndebele, North
ndo Ndongo
nep Nepali
new Newari
nic Niger-Kordofanian (Other)
niu Niuean
nla Dutch
nno Norwegian (Nynorsk)
non Norse, Old
nor Norwegian
nso Sotho, Northern
nub Nubian Languages
nya Nyanja
nym Nyamwezi
nyn Nyankole
nyo Nyoro
nzi Nzima
oci Langue d'Oc (post 1500)
oji Ojibwa
ori Oriya
orm Oromo
osa Osage
oss Ossetic
ota Turkish, Ottoman (1500 - 1928)
oto Otomian Languages
paa Papuan-Australian (Other)
pag Pangasinan
pal Pahlavi
pam Pampanga
pan Panjabi
pap Papiamento
pau Palauan
peo Persian, Old (ca 600 - 400 B.C.)
per Persian
phn Phoenician
pli Pali
pol Polish
pon Ponape
por Portuguese
pra Prakrit uages
pro Provencal, Old (to 1500)
pus Pushto
que Quechua
raj Rajasthani
rar Rarotongan
roa Romance (Other)
roh Rhaeto-Romance
rom Romany
ron Romanian
rum Romanian
run Rundi
rus Russian
sad Sandawe
sag Sango
sah Yakut
sai South American Indian (Other)
sal Salishan Languages
sam Samaritan Aramaic
san Sanskrit
sco Scots
scr Serbo-Croatian
sel Selkup
sem Semitic (Other)
sga Irish, Old (to 900)
shn Shan
sid Sidamo
sin Singhalese
sio Siouan Languages
sit Sino-Tibetan (Other)
sla Slavic (Other)
slk Slovak
slo Slovak
slv Slovenian
smi Sami Languages
smo Samoan
sna Shona
snd Sindhi
sog Sogdian
som Somali
son Songhai
sot Sotho, Southern
spa Spanish
sqi Albanian
srd Sardinian
srr Serer
ssa Nilo-Saharan (Other)
ssw Siswant
ssw Swazi
suk Sukuma
sun Sudanese
sus Susu
sux Sumerian
sve Swedish
swa Swahili
swe Swedish
syr Syriac
tah Tahitian
tam Tamil
tat Tatar
tel Telugu
tem Timne
ter Tereno
tgk Tajik
tgl Tagalog
tha Thai
tib Tibetan
tig Tigre
tir Tigrinya
tiv Tivi
tli Tlingit
tmh Tamashek
tog Tonga (Nyasa)
ton Tonga (Tonga Islands)
tru Truk
tsi Tsimshian
tsn Tswana
tso Tsonga
tuk Turkmen
tum Tumbuka
tur Turkish
tut Altaic (Other)
twi Twi
tyv Tuvinian
uga Ugaritic
uig Uighur
ukr Ukrainian
umb Umbundu
und Undetermined
urd Urdu
uzb Uzbek
vai Vai
ven Venda
vie Vietnamese
vol Volapük
vot Votic
wak Wakashan Languages
wal Walamo
war Waray
was Washo
wel Welsh
wen Sorbian Languages
wol Wolof
xho Xhosa
yao Yao
yap Yap
yid Yiddish
yor Yoruba
zap Zapotec
zen Zenaga
zha Zhuang
zho Chinese
zul Zulu
zun Zuni
*/
return getid3_lib::EmbeddedLookup($languagecode, $begin, __LINE__, __FILE__, 'id3v2-languagecode');
}
/**
* @param int $index
*
* @return string
*/
public static function ETCOEventLookup($index) {
if (($index >= 0x17) && ($index <= 0xDF)) {
return 'reserved for future use';
}
if (($index >= 0xE0) && ($index <= 0xEF)) {
return 'not predefined synch 0-F';
}
if (($index >= 0xF0) && ($index <= 0xFC)) {
return 'reserved for future use';
}
static $EventLookup = array(
0x00 => 'padding (has no meaning)',
0x01 => 'end of initial silence',
0x02 => 'intro start',
0x03 => 'main part start',
0x04 => 'outro start',
0x05 => 'outro end',
0x06 => 'verse start',
0x07 => 'refrain start',
0x08 => 'interlude start',
0x09 => 'theme start',
0x0A => 'variation start',
0x0B => 'key change',
0x0C => 'time change',
0x0D => 'momentary unwanted noise (Snap, Crackle & Pop)',
0x0E => 'sustained noise',
0x0F => 'sustained noise end',
0x10 => 'intro end',
0x11 => 'main part end',
0x12 => 'verse end',
0x13 => 'refrain end',
0x14 => 'theme end',
0x15 => 'profanity',
0x16 => 'profanity end',
0xFD => 'audio end (start of silence)',
0xFE => 'audio file ends',
0xFF => 'one more byte of events follows'
);
return (isset($EventLookup[$index]) ? $EventLookup[$index] : '');
}
/**
* @param int $index
*
* @return string
*/
public static function SYTLContentTypeLookup($index) {
static $SYTLContentTypeLookup = array(
0x00 => 'other',
0x01 => 'lyrics',
0x02 => 'text transcription',
0x03 => 'movement/part name', // (e.g. 'Adagio')
0x04 => 'events', // (e.g. 'Don Quijote enters the stage')
0x05 => 'chord', // (e.g. 'Bb F Fsus')
0x06 => 'trivia/\'pop up\' information',
0x07 => 'URLs to webpages',
0x08 => 'URLs to images'
);
return (isset($SYTLContentTypeLookup[$index]) ? $SYTLContentTypeLookup[$index] : '');
}
/**
* @param int $index
* @param bool $returnarray
*
* @return array|string
*/
public static function APICPictureTypeLookup($index, $returnarray=false) {
static $APICPictureTypeLookup = array(
0x00 => 'Other',
0x01 => '32x32 pixels \'file icon\' (PNG only)',
0x02 => 'Other file icon',
0x03 => 'Cover (front)',
0x04 => 'Cover (back)',
0x05 => 'Leaflet page',
0x06 => 'Media (e.g. label side of CD)',
0x07 => 'Lead artist/lead performer/soloist',
0x08 => 'Artist/performer',
0x09 => 'Conductor',
0x0A => 'Band/Orchestra',
0x0B => 'Composer',
0x0C => 'Lyricist/text writer',
0x0D => 'Recording Location',
0x0E => 'During recording',
0x0F => 'During performance',
0x10 => 'Movie/video screen capture',
0x11 => 'A bright coloured fish',
0x12 => 'Illustration',
0x13 => 'Band/artist logotype',
0x14 => 'Publisher/Studio logotype'
);
if ($returnarray) {
return $APICPictureTypeLookup;
}
return (isset($APICPictureTypeLookup[$index]) ? $APICPictureTypeLookup[$index] : '');
}
/**
* @param int $index
*
* @return string
*/
public static function COMRReceivedAsLookup($index) {
static $COMRReceivedAsLookup = array(
0x00 => 'Other',
0x01 => 'Standard CD album with other songs',
0x02 => 'Compressed audio on CD',
0x03 => 'File over the Internet',
0x04 => 'Stream over the Internet',
0x05 => 'As note sheets',
0x06 => 'As note sheets in a book with other sheets',
0x07 => 'Music on other media',
0x08 => 'Non-musical merchandise'
);
return (isset($COMRReceivedAsLookup[$index]) ? $COMRReceivedAsLookup[$index] : '');
}
/**
* @param int $index
*
* @return string
*/
public static function RVA2ChannelTypeLookup($index) {
static $RVA2ChannelTypeLookup = array(
0x00 => 'Other',
0x01 => 'Master volume',
0x02 => 'Front right',
0x03 => 'Front left',
0x04 => 'Back right',
0x05 => 'Back left',
0x06 => 'Front centre',
0x07 => 'Back centre',
0x08 => 'Subwoofer'
);
return (isset($RVA2ChannelTypeLookup[$index]) ? $RVA2ChannelTypeLookup[$index] : '');
}
/**
* @param string $framename
*
* @return string
*/
public static function FrameNameLongLookup($framename) {
$begin = __LINE__;
/** This is not a comment!
AENC Audio encryption
APIC Attached picture
ASPI Audio seek point index
BUF Recommended buffer size
CNT Play counter
COM Comments
COMM Comments
COMR Commercial frame
CRA Audio encryption
CRM Encrypted meta frame
ENCR Encryption method registration
EQU Equalisation
EQU2 Equalisation (2)
EQUA Equalisation
ETC Event timing codes
ETCO Event timing codes
GEO General encapsulated object
GEOB General encapsulated object
GRID Group identification registration
IPL Involved people list
IPLS Involved people list
LINK Linked information
LNK Linked information
MCDI Music CD identifier
MCI Music CD Identifier
MLL MPEG location lookup table
MLLT MPEG location lookup table
OWNE Ownership frame
PCNT Play counter
PIC Attached picture
POP Popularimeter
POPM Popularimeter
POSS Position synchronisation frame
PRIV Private frame
RBUF Recommended buffer size
REV Reverb
RVA Relative volume adjustment
RVA2 Relative volume adjustment (2)
RVAD Relative volume adjustment
RVRB Reverb
SEEK Seek frame
SIGN Signature frame
SLT Synchronised lyric/text
STC Synced tempo codes
SYLT Synchronised lyric/text
SYTC Synchronised tempo codes
TAL Album/Movie/Show title
TALB Album/Movie/Show title
TBP BPM (Beats Per Minute)
TBPM BPM (beats per minute)
TCM Composer
TCMP Part of a compilation
TCO Content type
TCOM Composer
TCON Content type
TCOP Copyright message
TCP Part of a compilation
TCR Copyright message
TDA Date
TDAT Date
TDEN Encoding time
TDLY Playlist delay
TDOR Original release time
TDRC Recording time
TDRL Release time
TDTG Tagging time
TDY Playlist delay
TEN Encoded by
TENC Encoded by
TEXT Lyricist/Text writer
TFLT File type
TFT File type
TIM Time
TIME Time
TIPL Involved people list
TIT1 Content group description
TIT2 Title/songname/content description
TIT3 Subtitle/Description refinement
TKE Initial key
TKEY Initial key
TLA Language(s)
TLAN Language(s)
TLE Length
TLEN Length
TMCL Musician credits list
TMED Media type
TMOO Mood
TMT Media type
TOA Original artist(s)/performer(s)
TOAL Original album/movie/show title
TOF Original filename
TOFN Original filename
TOL Original Lyricist(s)/text writer(s)
TOLY Original lyricist(s)/text writer(s)
TOPE Original artist(s)/performer(s)
TOR Original release year
TORY Original release year
TOT Original album/Movie/Show title
TOWN File owner/licensee
TP1 Lead artist(s)/Lead performer(s)/Soloist(s)/Performing group
TP2 Band/Orchestra/Accompaniment
TP3 Conductor/Performer refinement
TP4 Interpreted, remixed, or otherwise modified by
TPA Part of a set
TPB Publisher
TPE1 Lead performer(s)/Soloist(s)
TPE2 Band/orchestra/accompaniment
TPE3 Conductor/performer refinement
TPE4 Interpreted, remixed, or otherwise modified by
TPOS Part of a set
TPRO Produced notice
TPUB Publisher
TRC ISRC (International Standard Recording Code)
TRCK Track number/Position in set
TRD Recording dates
TRDA Recording dates
TRK Track number/Position in set
TRSN Internet radio station name
TRSO Internet radio station owner
TS2 Album-Artist sort order
TSA Album sort order
TSC Composer sort order
TSI Size
TSIZ Size
TSO2 Album-Artist sort order
TSOA Album sort order
TSOC Composer sort order
TSOP Performer sort order
TSOT Title sort order
TSP Performer sort order
TSRC ISRC (international standard recording code)
TSS Software/hardware and settings used for encoding
TSSE Software/Hardware and settings used for encoding
TSST Set subtitle
TST Title sort order
TT1 Content group description
TT2 Title/Songname/Content description
TT3 Subtitle/Description refinement
TXT Lyricist/text writer
TXX User defined text information frame
TXXX User defined text information frame
TYE Year
TYER Year
UFI Unique file identifier
UFID Unique file identifier
ULT Unsynchronised lyric/text transcription
USER Terms of use
USLT Unsynchronised lyric/text transcription
WAF Official audio file webpage
WAR Official artist/performer webpage
WAS Official audio source webpage
WCM Commercial information
WCOM Commercial information
WCOP Copyright/Legal information
WCP Copyright/Legal information
WOAF Official audio file webpage
WOAR Official artist/performer webpage
WOAS Official audio source webpage
WORS Official Internet radio station homepage
WPAY Payment
WPB Publishers official webpage
WPUB Publishers official webpage
WXX User defined URL link frame
WXXX User defined URL link frame
TFEA Featured Artist
TSTU Recording Studio
rgad Replay Gain Adjustment
*/
return getid3_lib::EmbeddedLookup($framename, $begin, __LINE__, __FILE__, 'id3v2-framename_long');
// Last three:
// from Helium2 [www.helium2.com]
// from http://privatewww.essex.ac.uk/~djmrob/replaygain/file_format_id3v2.html
}
/**
* @param string $framename
*
* @return string
*/
public static function FrameNameShortLookup($framename) {
$begin = __LINE__;
/** This is not a comment!
AENC audio_encryption
APIC attached_picture
ASPI audio_seek_point_index
BUF recommended_buffer_size
CNT play_counter
COM comment
COMM comment
COMR commercial_frame
CRA audio_encryption
CRM encrypted_meta_frame
ENCR encryption_method_registration
EQU equalisation
EQU2 equalisation
EQUA equalisation
ETC event_timing_codes
ETCO event_timing_codes
GEO general_encapsulated_object
GEOB general_encapsulated_object
GRID group_identification_registration
IPL involved_people_list
IPLS involved_people_list
LINK linked_information
LNK linked_information
MCDI music_cd_identifier
MCI music_cd_identifier
MLL mpeg_location_lookup_table
MLLT mpeg_location_lookup_table
OWNE ownership_frame
PCNT play_counter
PIC attached_picture
POP popularimeter
POPM popularimeter
POSS position_synchronisation_frame
PRIV private_frame
RBUF recommended_buffer_size
REV reverb
RVA relative_volume_adjustment
RVA2 relative_volume_adjustment
RVAD relative_volume_adjustment
RVRB reverb
SEEK seek_frame
SIGN signature_frame
SLT synchronised_lyric
STC synced_tempo_codes
SYLT synchronised_lyric
SYTC synchronised_tempo_codes
TAL album
TALB album
TBP bpm
TBPM bpm
TCM composer
TCMP part_of_a_compilation
TCO genre
TCOM composer
TCON genre
TCOP copyright_message
TCP part_of_a_compilation
TCR copyright_message
TDA date
TDAT date
TDEN encoding_time
TDLY playlist_delay
TDOR original_release_time
TDRC recording_time
TDRL release_time
TDTG tagging_time
TDY playlist_delay
TEN encoded_by
TENC encoded_by
TEXT lyricist
TFLT file_type
TFT file_type
TIM time
TIME time
TIPL involved_people_list
TIT1 content_group_description
TIT2 title
TIT3 subtitle
TKE initial_key
TKEY initial_key
TLA language
TLAN language
TLE length
TLEN length
TMCL musician_credits_list
TMED media_type
TMOO mood
TMT media_type
TOA original_artist
TOAL original_album
TOF original_filename
TOFN original_filename
TOL original_lyricist
TOLY original_lyricist
TOPE original_artist
TOR original_year
TORY original_year
TOT original_album
TOWN file_owner
TP1 artist
TP2 band
TP3 conductor
TP4 remixer
TPA part_of_a_set
TPB publisher
TPE1 artist
TPE2 band
TPE3 conductor
TPE4 remixer
TPOS part_of_a_set
TPRO produced_notice
TPUB publisher
TRC isrc
TRCK track_number
TRD recording_dates
TRDA recording_dates
TRK track_number
TRSN internet_radio_station_name
TRSO internet_radio_station_owner
TS2 album_artist_sort_order
TSA album_sort_order
TSC composer_sort_order
TSI size
TSIZ size
TSO2 album_artist_sort_order
TSOA album_sort_order
TSOC composer_sort_order
TSOP performer_sort_order
TSOT title_sort_order
TSP performer_sort_order
TSRC isrc
TSS encoder_settings
TSSE encoder_settings
TSST set_subtitle
TST title_sort_order
TT1 content_group_description
TT2 title
TT3 subtitle
TXT lyricist
TXX text
TXXX text
TYE year
TYER year
UFI unique_file_identifier
UFID unique_file_identifier
ULT unsynchronised_lyric
USER terms_of_use
USLT unsynchronised_lyric
WAF url_file
WAR url_artist
WAS url_source
WCM commercial_information
WCOM commercial_information
WCOP copyright
WCP copyright
WOAF url_file
WOAR url_artist
WOAS url_source
WORS url_station
WPAY url_payment
WPB url_publisher
WPUB url_publisher
WXX url_user
WXXX url_user
TFEA featured_artist
TSTU recording_studio
rgad replay_gain_adjustment
*/
return getid3_lib::EmbeddedLookup($framename, $begin, __LINE__, __FILE__, 'id3v2-framename_short');
}
/**
* @param string $encoding
*
* @return string
*/
public static function TextEncodingTerminatorLookup($encoding) {
// http://www.id3.org/id3v2.4.0-structure.txt
// Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings:
static $TextEncodingTerminatorLookup = array(
0 => "\x00", // $00 ISO-8859-1. Terminated with $00.
1 => "\x00\x00", // $01 UTF-16 encoded Unicode with BOM. All strings in the same frame SHALL have the same byteorder. Terminated with $00 00.
2 => "\x00\x00", // $02 UTF-16BE encoded Unicode without BOM. Terminated with $00 00.
3 => "\x00", // $03 UTF-8 encoded Unicode. Terminated with $00.
255 => "\x00\x00"
);
return (isset($TextEncodingTerminatorLookup[$encoding]) ? $TextEncodingTerminatorLookup[$encoding] : "\x00");
}
/**
* @param int $encoding
*
* @return string
*/
public static function TextEncodingNameLookup($encoding) {
// http://www.id3.org/id3v2.4.0-structure.txt
// Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings:
static $TextEncodingNameLookup = array(
0 => 'ISO-8859-1', // $00 ISO-8859-1. Terminated with $00.
1 => 'UTF-16', // $01 UTF-16 encoded Unicode with BOM. All strings in the same frame SHALL have the same byteorder. Terminated with $00 00.
2 => 'UTF-16BE', // $02 UTF-16BE encoded Unicode without BOM. Terminated with $00 00.
3 => 'UTF-8', // $03 UTF-8 encoded Unicode. Terminated with $00.
255 => 'UTF-16BE'
);
return (isset($TextEncodingNameLookup[$encoding]) ? $TextEncodingNameLookup[$encoding] : 'ISO-8859-1');
}
/**
* @param string $string
* @param string $terminator
*
* @return string
*/
public static function RemoveStringTerminator($string, $terminator) {
// Null terminator at end of comment string is somewhat ambiguous in the specification, may or may not be implemented by various taggers. Remove terminator only if present.
// https://github.com/JamesHeinrich/getID3/issues/121
// https://community.mp3tag.de/t/x-trailing-nulls-in-id3v2-comments/19227
if (substr($string, -strlen($terminator), strlen($terminator)) === $terminator) {
$string = substr($string, 0, -strlen($terminator));
}
return $string;
}
/**
* @param string $string
*
* @return string
*/
public static function MakeUTF16emptyStringEmpty($string) {
if (in_array($string, array("\x00", "\x00\x00", "\xFF\xFE", "\xFE\xFF"))) {
// if string only contains a BOM or terminator then make it actually an empty string
$string = '';
}
return $string;
}
/**
* @param string $framename
* @param int $id3v2majorversion
*
* @return bool|int
*/
public static function IsValidID3v2FrameName($framename, $id3v2majorversion) {
switch ($id3v2majorversion) {
case 2:
return preg_match('#[A-Z][A-Z0-9]{2}#', $framename);
case 3:
case 4:
return preg_match('#[A-Z][A-Z0-9]{3}#', $framename);
}
return false;
}
/**
* @param string $numberstring
* @param bool $allowdecimal
* @param bool $allownegative
*
* @return bool
*/
public static function IsANumber($numberstring, $allowdecimal=false, $allownegative=false) {
for ($i = 0; $i < strlen($numberstring); $i++) {
if ((chr($numberstring[$i]) < chr('0')) || (chr($numberstring[$i]) > chr('9'))) {
if (($numberstring[$i] == '.') && $allowdecimal) {
// allowed
} elseif (($numberstring[$i] == '-') && $allownegative && ($i == 0)) {
// allowed
} else {
return false;
}
}
}
return true;
}
/**
* @param string $datestamp
*
* @return bool
*/
public static function IsValidDateStampString($datestamp) {
if (strlen($datestamp) != 8) {
return false;
}
if (!self::IsANumber($datestamp, false)) {
return false;
}
$year = substr($datestamp, 0, 4);
$month = substr($datestamp, 4, 2);
$day = substr($datestamp, 6, 2);
if (($year == 0) || ($month == 0) || ($day == 0)) {
return false;
}
if ($month > 12) {
return false;
}
if ($day > 31) {
return false;
}
if (($day > 30) && (($month == 4) || ($month == 6) || ($month == 9) || ($month == 11))) {
return false;
}
if (($day > 29) && ($month == 2)) {
return false;
}
return true;
}
/**
* @param int $majorversion
*
* @return int
*/
public static function ID3v2HeaderLength($majorversion) {
return (($majorversion == 2) ? 6 : 10);
}
/**
* @param string $frame_name
*
* @return string|false
*/
public static function ID3v22iTunesBrokenFrameName($frame_name) {
// iTunes (multiple versions) has been known to write ID3v2.3 style frames
// but use ID3v2.2 frame names, right-padded using either [space] or [null]
// to make them fit in the 4-byte frame name space of the ID3v2.3 frame.
// This function will detect and translate the corrupt frame name into ID3v2.3 standard.
static $ID3v22_iTunes_BrokenFrames = array(
'BUF' => 'RBUF', // Recommended buffer size
'CNT' => 'PCNT', // Play counter
'COM' => 'COMM', // Comments
'CRA' => 'AENC', // Audio encryption
'EQU' => 'EQUA', // Equalisation
'ETC' => 'ETCO', // Event timing codes
'GEO' => 'GEOB', // General encapsulated object
'IPL' => 'IPLS', // Involved people list
'LNK' => 'LINK', // Linked information
'MCI' => 'MCDI', // Music CD identifier
'MLL' => 'MLLT', // MPEG location lookup table
'PIC' => 'APIC', // Attached picture
'POP' => 'POPM', // Popularimeter
'REV' => 'RVRB', // Reverb
'RVA' => 'RVAD', // Relative volume adjustment
'SLT' => 'SYLT', // Synchronised lyric/text
'STC' => 'SYTC', // Synchronised tempo codes
'TAL' => 'TALB', // Album/Movie/Show title
'TBP' => 'TBPM', // BPM (beats per minute)
'TCM' => 'TCOM', // Composer
'TCO' => 'TCON', // Content type
'TCP' => 'TCMP', // Part of a compilation
'TCR' => 'TCOP', // Copyright message
'TDA' => 'TDAT', // Date
'TDY' => 'TDLY', // Playlist delay
'TEN' => 'TENC', // Encoded by
'TFT' => 'TFLT', // File type
'TIM' => 'TIME', // Time
'TKE' => 'TKEY', // Initial key
'TLA' => 'TLAN', // Language(s)
'TLE' => 'TLEN', // Length
'TMT' => 'TMED', // Media type
'TOA' => 'TOPE', // Original artist(s)/performer(s)
'TOF' => 'TOFN', // Original filename
'TOL' => 'TOLY', // Original lyricist(s)/text writer(s)
'TOR' => 'TORY', // Original release year
'TOT' => 'TOAL', // Original album/movie/show title
'TP1' => 'TPE1', // Lead performer(s)/Soloist(s)
'TP2' => 'TPE2', // Band/orchestra/accompaniment
'TP3' => 'TPE3', // Conductor/performer refinement
'TP4' => 'TPE4', // Interpreted, remixed, or otherwise modified by
'TPA' => 'TPOS', // Part of a set
'TPB' => 'TPUB', // Publisher
'TRC' => 'TSRC', // ISRC (international standard recording code)
'TRD' => 'TRDA', // Recording dates
'TRK' => 'TRCK', // Track number/Position in set
'TS2' => 'TSO2', // Album-Artist sort order
'TSA' => 'TSOA', // Album sort order
'TSC' => 'TSOC', // Composer sort order
'TSI' => 'TSIZ', // Size
'TSP' => 'TSOP', // Performer sort order
'TSS' => 'TSSE', // Software/Hardware and settings used for encoding
'TST' => 'TSOT', // Title sort order
'TT1' => 'TIT1', // Content group description
'TT2' => 'TIT2', // Title/songname/content description
'TT3' => 'TIT3', // Subtitle/Description refinement
'TXT' => 'TEXT', // Lyricist/Text writer
'TXX' => 'TXXX', // User defined text information frame
'TYE' => 'TYER', // Year
'UFI' => 'UFID', // Unique file identifier
'ULT' => 'USLT', // Unsynchronised lyric/text transcription
'WAF' => 'WOAF', // Official audio file webpage
'WAR' => 'WOAR', // Official artist/performer webpage
'WAS' => 'WOAS', // Official audio source webpage
'WCM' => 'WCOM', // Commercial information
'WCP' => 'WCOP', // Copyright/Legal information
'WPB' => 'WPUB', // Publishers official webpage
'WXX' => 'WXXX', // User defined URL link frame
);
if (strlen($frame_name) == 4) {
if ((substr($frame_name, 3, 1) == ' ') || (substr($frame_name, 3, 1) == "\x00")) {
if (isset($ID3v22_iTunes_BrokenFrames[substr($frame_name, 0, 3)])) {
return $ID3v22_iTunes_BrokenFrames[substr($frame_name, 0, 3)];
}
}
}
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.tag.lyrics3.php //
// module for analyzing Lyrics3 tags //
// dependencies: module.tag.apetag.php (optional) //
// ///
/////////////////////////////////////////////////////////////////
if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
exit;
}
class getid3_lyrics3 extends getid3_handler
{
/**
* @return bool
*/
public function Analyze() {
$info = &$this->getid3->info;
// http://www.volweb.cz/str/tags.htm
if (!getid3_lib::intValueSupported($info['filesize'])) {
$this->warning('Unable to check for Lyrics3 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB');
return false;
}
$this->fseek((0 - 128 - 9 - 6), SEEK_END); // end - ID3v1 - "LYRICSEND" - [Lyrics3size]
$lyrics3_id3v1 = $this->fread(128 + 9 + 6);
$lyrics3lsz = (int) substr($lyrics3_id3v1, 0, 6); // Lyrics3size
$lyrics3end = substr($lyrics3_id3v1, 6, 9); // LYRICSEND or LYRICS200
$id3v1tag = substr($lyrics3_id3v1, 15, 128); // ID3v1
if ($lyrics3end == 'LYRICSEND') {
// Lyrics3v1, ID3v1, no APE
$lyrics3size = 5100;
$lyrics3offset = $info['filesize'] - 128 - $lyrics3size;
$lyrics3version = 1;
} elseif ($lyrics3end == 'LYRICS200') {
// Lyrics3v2, ID3v1, no APE
// LSZ = lyrics + 'LYRICSBEGIN'; add 6-byte size field; add 'LYRICS200'
$lyrics3size = $lyrics3lsz + 6 + strlen('LYRICS200');
$lyrics3offset = $info['filesize'] - 128 - $lyrics3size;
$lyrics3version = 2;
} elseif (substr(strrev($lyrics3_id3v1), 0, 9) == strrev('LYRICSEND')) {
// Lyrics3v1, no ID3v1, no APE
$lyrics3size = 5100;
$lyrics3offset = $info['filesize'] - $lyrics3size;
$lyrics3version = 1;
$lyrics3offset = $info['filesize'] - $lyrics3size;
} elseif (substr(strrev($lyrics3_id3v1), 0, 9) == strrev('LYRICS200')) {
// Lyrics3v2, no ID3v1, no APE
$lyrics3size = (int) strrev(substr(strrev($lyrics3_id3v1), 9, 6)) + 6 + strlen('LYRICS200'); // LSZ = lyrics + 'LYRICSBEGIN'; add 6-byte size field; add 'LYRICS200'
$lyrics3offset = $info['filesize'] - $lyrics3size;
$lyrics3version = 2;
} else {
if (isset($info['ape']['tag_offset_start']) && ($info['ape']['tag_offset_start'] > 15)) {
$this->fseek($info['ape']['tag_offset_start'] - 15);
$lyrics3lsz = $this->fread(6);
$lyrics3end = $this->fread(9);
if ($lyrics3end == 'LYRICSEND') {
// Lyrics3v1, APE, maybe ID3v1
$lyrics3size = 5100;
$lyrics3offset = $info['ape']['tag_offset_start'] - $lyrics3size;
$info['avdataend'] = $lyrics3offset;
$lyrics3version = 1;
$this->warning('APE tag located after Lyrics3, will probably break Lyrics3 compatability');
} elseif ($lyrics3end == 'LYRICS200') {
// Lyrics3v2, APE, maybe ID3v1
$lyrics3size = $lyrics3lsz + 6 + strlen('LYRICS200'); // LSZ = lyrics + 'LYRICSBEGIN'; add 6-byte size field; add 'LYRICS200'
$lyrics3offset = $info['ape']['tag_offset_start'] - $lyrics3size;
$lyrics3version = 2;
$this->warning('APE tag located after Lyrics3, will probably break Lyrics3 compatability');
}
}
}
if (isset($lyrics3offset) && isset($lyrics3version) && isset($lyrics3size)) {
$info['avdataend'] = $lyrics3offset;
$this->getLyrics3Data($lyrics3offset, $lyrics3version, $lyrics3size);
if (!isset($info['ape'])) {
if (isset($info['lyrics3']['tag_offset_start'])) {
$GETID3_ERRORARRAY = &$info['warning'];
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.apetag.php', __FILE__, true);
$getid3_temp = new getID3();
$getid3_temp->openfile($this->getid3->filename, $this->getid3->info['filesize'], $this->getid3->fp);
$getid3_apetag = new getid3_apetag($getid3_temp);
$getid3_apetag->overrideendoffset = $info['lyrics3']['tag_offset_start'];
$getid3_apetag->Analyze();
if (!empty($getid3_temp->info['ape'])) {
$info['ape'] = $getid3_temp->info['ape'];
}
if (!empty($getid3_temp->info['replay_gain'])) {
$info['replay_gain'] = $getid3_temp->info['replay_gain'];
}
unset($getid3_temp, $getid3_apetag);
} else {
$this->warning('Lyrics3 and APE tags appear to have become entangled (most likely due to updating the APE tags with a non-Lyrics3-aware tagger)');
}
}
}
return true;
}
/**
* @param int $endoffset
* @param int $version
* @param int $length
*
* @return bool
*/
public function getLyrics3Data($endoffset, $version, $length) {
// http://www.volweb.cz/str/tags.htm
$info = &$this->getid3->info;
if (!getid3_lib::intValueSupported($endoffset)) {
$this->warning('Unable to check for Lyrics3 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB');
return false;
}
$this->fseek($endoffset);
if ($length <= 0) {
return false;
}
$rawdata = $this->fread($length);
$ParsedLyrics3 = array();
$ParsedLyrics3['raw']['lyrics3version'] = $version;
$ParsedLyrics3['raw']['lyrics3tagsize'] = $length;
$ParsedLyrics3['tag_offset_start'] = $endoffset;
$ParsedLyrics3['tag_offset_end'] = $endoffset + $length - 1;
if (substr($rawdata, 0, 11) != 'LYRICSBEGIN') {
if (strpos($rawdata, 'LYRICSBEGIN') !== false) {
$this->warning('"LYRICSBEGIN" expected at '.$endoffset.' but actually found at '.($endoffset + strpos($rawdata, 'LYRICSBEGIN')).' - this is invalid for Lyrics3 v'.$version);
$info['avdataend'] = $endoffset + strpos($rawdata, 'LYRICSBEGIN');
$rawdata = substr($rawdata, strpos($rawdata, 'LYRICSBEGIN'));
$length = strlen($rawdata);
$ParsedLyrics3['tag_offset_start'] = $info['avdataend'];
$ParsedLyrics3['raw']['lyrics3tagsize'] = $length;
} else {
$this->error('"LYRICSBEGIN" expected at '.$endoffset.' but found "'.substr($rawdata, 0, 11).'" instead');
return false;
}
}
switch ($version) {
case 1:
if (substr($rawdata, strlen($rawdata) - 9, 9) == 'LYRICSEND') {
$ParsedLyrics3['raw']['LYR'] = trim(substr($rawdata, 11, strlen($rawdata) - 11 - 9));
$this->Lyrics3LyricsTimestampParse($ParsedLyrics3);
} else {
$this->error('"LYRICSEND" expected at '.($this->ftell() - 11 + $length - 9).' but found "'.substr($rawdata, strlen($rawdata) - 9, 9).'" instead');
return false;
}
break;
case 2:
if (substr($rawdata, strlen($rawdata) - 9, 9) == 'LYRICS200') {
$ParsedLyrics3['raw']['unparsed'] = substr($rawdata, 11, strlen($rawdata) - 11 - 9 - 6); // LYRICSBEGIN + LYRICS200 + LSZ
$rawdata = $ParsedLyrics3['raw']['unparsed'];
while (strlen($rawdata) > 0) {
$fieldname = substr($rawdata, 0, 3);
$fieldsize = (int) substr($rawdata, 3, 5);
$ParsedLyrics3['raw'][$fieldname] = substr($rawdata, 8, $fieldsize);
$rawdata = substr($rawdata, 3 + 5 + $fieldsize);
}
if (isset($ParsedLyrics3['raw']['IND'])) {
$i = 0;
$flagnames = array('lyrics', 'timestamps', 'inhibitrandom');
foreach ($flagnames as $flagname) {
if (strlen($ParsedLyrics3['raw']['IND']) > $i++) {
$ParsedLyrics3['flags'][$flagname] = $this->IntString2Bool(substr($ParsedLyrics3['raw']['IND'], $i, 1 - 1));
}
}
}
$fieldnametranslation = array('ETT'=>'title', 'EAR'=>'artist', 'EAL'=>'album', 'INF'=>'comment', 'AUT'=>'author');
foreach ($fieldnametranslation as $key => $value) {
if (isset($ParsedLyrics3['raw'][$key])) {
$ParsedLyrics3['comments'][$value][] = trim($ParsedLyrics3['raw'][$key]);
}
}
if (isset($ParsedLyrics3['raw']['IMG'])) {
$imagestrings = explode("\r\n", $ParsedLyrics3['raw']['IMG']);
foreach ($imagestrings as $key => $imagestring) {
if (strpos($imagestring, '||') !== false) {
$imagearray = explode('||', $imagestring);
$ParsedLyrics3['images'][$key]['filename'] = (isset($imagearray[0]) ? $imagearray[0] : '');
$ParsedLyrics3['images'][$key]['description'] = (isset($imagearray[1]) ? $imagearray[1] : '');
$ParsedLyrics3['images'][$key]['timestamp'] = $this->Lyrics3Timestamp2Seconds(isset($imagearray[2]) ? $imagearray[2] : '');
}
}
}
if (isset($ParsedLyrics3['raw']['LYR'])) {
$this->Lyrics3LyricsTimestampParse($ParsedLyrics3);
}
} else {
$this->error('"LYRICS200" expected at '.($this->ftell() - 11 + $length - 9).' but found "'.substr($rawdata, strlen($rawdata) - 9, 9).'" instead');
return false;
}
break;
default:
$this->error('Cannot process Lyrics3 version '.$version.' (only v1 and v2)');
return false;
}
if (isset($info['id3v1']['tag_offset_start']) && ($info['id3v1']['tag_offset_start'] <= $ParsedLyrics3['tag_offset_end'])) {
$this->warning('ID3v1 tag information ignored since it appears to be a false synch in Lyrics3 tag data');
unset($info['id3v1']);
foreach ($info['warning'] as $key => $value) {
if ($value == 'Some ID3v1 fields do not use NULL characters for padding') {
unset($info['warning'][$key]);
sort($info['warning']);
break;
}
}
}
$info['lyrics3'] = $ParsedLyrics3;
return true;
}
/**
* @param string $rawtimestamp
*
* @return int|false
*/
public function Lyrics3Timestamp2Seconds($rawtimestamp) {
if (preg_match('#^\\[([0-9]{2}):([0-9]{2})\\]$#', $rawtimestamp, $regs)) {
return (int) (($regs[1] * 60) + $regs[2]);
}
return false;
}
/**
* @param array $Lyrics3data
*
* @return bool
*/
public function Lyrics3LyricsTimestampParse(&$Lyrics3data) {
$lyricsarray = explode("\r\n", $Lyrics3data['raw']['LYR']);
$notimestamplyricsarray = array();
foreach ($lyricsarray as $key => $lyricline) {
$regs = array();
unset($thislinetimestamps);
while (preg_match('#^(\\[[0-9]{2}:[0-9]{2}\\])#', $lyricline, $regs)) {
$thislinetimestamps[] = $this->Lyrics3Timestamp2Seconds($regs[0]);
$lyricline = str_replace($regs[0], '', $lyricline);
}
$notimestamplyricsarray[$key] = $lyricline;
if (isset($thislinetimestamps) && is_array($thislinetimestamps)) {
sort($thislinetimestamps);
foreach ($thislinetimestamps as $timestampkey => $timestamp) {
if (isset($Lyrics3data['synchedlyrics'][$timestamp])) {
// timestamps only have a 1-second resolution, it's possible that multiple lines
// could have the same timestamp, if so, append
$Lyrics3data['synchedlyrics'][$timestamp] .= "\r\n".$lyricline;
} else {
$Lyrics3data['synchedlyrics'][$timestamp] = $lyricline;
}
}
}
}
$Lyrics3data['unsynchedlyrics'] = implode("\r\n", $notimestamplyricsarray);
if (isset($Lyrics3data['synchedlyrics']) && is_array($Lyrics3data['synchedlyrics'])) {
ksort($Lyrics3data['synchedlyrics']);
}
return true;
}
/**
* @param string $char
*
* @return bool|null
*/
public function IntString2Bool($char) {
if ($char == '1') {
return true;
} elseif ($char == '0') {
return false;
}
return 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.tag.xmp.php //
// module for analyzing XMP metadata (e.g. in JPEG files) //
// dependencies: NONE //
// //
/////////////////////////////////////////////////////////////////
// //
// Module originally written [2009-Mar-26] by //
// Nigel Barnes <ngbarnesØhotmail*com> //
// Bundled into getID3 with permission //
// called by getID3 in module.graphic.jpg.php //
// ///
/////////////////////////////////////////////////////////////////
/**************************************************************************************************
* SWISScenter Source Nigel Barnes
*
* Provides functions for reading information from the 'APP1' Extensible Metadata
* Platform (XMP) segment of JPEG format files.
* This XMP segment is XML based and contains the Resource Description Framework (RDF)
* data, which itself can contain the Dublin Core Metadata Initiative (DCMI) information.
*
* This code uses segments from the JPEG Metadata Toolkit project by Evan Hunter.
*************************************************************************************************/
class Image_XMP
{
/**
* @var string
* The name of the image file that contains the XMP fields to extract and modify.
* @see Image_XMP()
*/
public $_sFilename = null;
/**
* @var array
* The XMP fields that were extracted from the image or updated by this class.
* @see getAllTags()
*/
public $_aXMP = array();
/**
* @var boolean
* True if an APP1 segment was found to contain XMP metadata.
* @see isValid()
*/
public $_bXMPParse = false;
/**
* Returns the status of XMP parsing during instantiation
*
* You'll normally want to call this method before trying to get XMP fields.
*
* @return boolean
* Returns true if an APP1 segment was found to contain XMP metadata.
*/
public function isValid()
{
return $this->_bXMPParse;
}
/**
* Get a copy of all XMP tags extracted from the image
*
* @return array - An array of XMP fields as it extracted by the XMPparse() function
*/
public function getAllTags()
{
return $this->_aXMP;
}
/**
* Reads all the JPEG header segments from an JPEG image file into an array
*
* @param string $filename - the filename of the JPEG file to read
* @return array|boolean $headerdata - Array of JPEG header segments,
* FALSE - if headers could not be read
*/
public function _get_jpeg_header_data($filename)
{
// prevent refresh from aborting file operations and hosing file
ignore_user_abort(true);
// Attempt to open the jpeg file - the at symbol supresses the error message about
// not being able to open files. The file_exists would have been used, but it
// does not work with files fetched over http or ftp.
if (is_readable($filename) && is_file($filename) && ($filehnd = fopen($filename, 'rb'))) {
// great
} else {
return false;
}
// Read the first two characters
$data = fread($filehnd, 2);
// Check that the first two characters are 0xFF 0xD8 (SOI - Start of image)
if ($data != "\xFF\xD8")
{
// No SOI (FF D8) at start of file - This probably isn't a JPEG file - close file and return;
echo '<p>This probably is not a JPEG file</p>'."\n";
fclose($filehnd);
return false;
}
// Read the third character
$data = fread($filehnd, 2);
// Check that the third character is 0xFF (Start of first segment header)
if ($data[0] != "\xFF")
{
// NO FF found - close file and return - JPEG is probably corrupted
fclose($filehnd);
return false;
}
// Flag that we havent yet hit the compressed image data
$hit_compressed_image_data = false;
$headerdata = array();
// Cycle through the file until, one of: 1) an EOI (End of image) marker is hit,
// 2) we have hit the compressed image data (no more headers are allowed after data)
// 3) or end of file is hit
while (($data[1] != "\xD9") && (!$hit_compressed_image_data) && (!feof($filehnd)))
{
// Found a segment to look at.
// Check that the segment marker is not a Restart marker - restart markers don't have size or data after them
if ((ord($data[1]) < 0xD0) || (ord($data[1]) > 0xD7))
{
// Segment isn't a Restart marker
// Read the next two bytes (size)
$sizestr = fread($filehnd, 2);
// convert the size bytes to an integer
$decodedsize = unpack('nsize', $sizestr);
// Save the start position of the data
$segdatastart = ftell($filehnd);
// Read the segment data with length indicated by the previously read size
$segdata = fread($filehnd, $decodedsize['size'] - 2);
// Store the segment information in the output array
$headerdata[] = array(
'SegType' => ord($data[1]),
'SegName' => $GLOBALS['JPEG_Segment_Names'][ord($data[1])],
'SegDataStart' => $segdatastart,
'SegData' => $segdata,
);
}
// If this is a SOS (Start Of Scan) segment, then there is no more header data - the compressed image data follows
if ($data[1] == "\xDA")
{
// Flag that we have hit the compressed image data - exit loop as no more headers available.
$hit_compressed_image_data = true;
}
else
{
// Not an SOS - Read the next two bytes - should be the segment marker for the next segment
$data = fread($filehnd, 2);
// Check that the first byte of the two is 0xFF as it should be for a marker
if ($data[0] != "\xFF")
{
// NO FF found - close file and return - JPEG is probably corrupted
fclose($filehnd);
return false;
}
}
}
// Close File
fclose($filehnd);
// Alow the user to abort from now on
ignore_user_abort(false);
// Return the header data retrieved
return $headerdata;
}
/**
* Retrieves XMP information from an APP1 JPEG segment and returns the raw XML text as a string.
*
* @param string $filename - the filename of the JPEG file to read
* @return string|boolean $xmp_data - the string of raw XML text,
* FALSE - if an APP 1 XMP segment could not be found, or if an error occured
*/
public function _get_XMP_text($filename)
{
//Get JPEG header data
$jpeg_header_data = $this->_get_jpeg_header_data($filename);
//Cycle through the header segments
for ($i = 0; $i < count($jpeg_header_data); $i++)
{
// If we find an APP1 header,
if (strcmp($jpeg_header_data[$i]['SegName'], 'APP1') == 0)
{
// And if it has the Adobe XMP/RDF label (http://ns.adobe.com/xap/1.0/\x00) ,
if (strncmp($jpeg_header_data[$i]['SegData'], 'http://ns.adobe.com/xap/1.0/'."\x00", 29) == 0)
{
// Found a XMP/RDF block
// Return the XMP text
$xmp_data = substr($jpeg_header_data[$i]['SegData'], 29);
return trim($xmp_data); // trim() should not be neccesary, but some files found in the wild with null-terminated block (known samples from Apple Aperture) causes problems elsewhere (see https://www.getid3.org/phpBB3/viewtopic.php?f=4&t=1153)
}
}
}
return false;
}
/**
* Parses a string containing XMP data (XML), and returns an array
* which contains all the XMP (XML) information.
*
* @param string $xmltext - a string containing the XMP data (XML) to be parsed
* @return array|boolean $xmp_array - an array containing all xmp details retrieved,
* FALSE - couldn't parse the XMP data.
*/
public function read_XMP_array_from_text($xmltext)
{
// Check if there actually is any text to parse
if (trim($xmltext) == '')
{
return false;
}
// Create an instance of a xml parser to parse the XML text
$xml_parser = xml_parser_create('UTF-8');
// Change: Fixed problem that caused the whitespace (especially newlines) to be destroyed when converting xml text to an xml array, as of revision 1.10
// We would like to remove unneccessary white space, but this will also
// remove things like newlines (&#xA;) in the XML values, so white space
// will have to be removed later
if (xml_parser_set_option($xml_parser, XML_OPTION_SKIP_WHITE, 0) == false)
{
// Error setting case folding - destroy the parser and return
xml_parser_free($xml_parser);
return false;
}
// to use XML code correctly we have to turn case folding
// (uppercasing) off. XML is case sensitive and upper
// casing is in reality XML standards violation
if (xml_parser_set_option($xml_parser, XML_OPTION_CASE_FOLDING, 0) == false)
{
// Error setting case folding - destroy the parser and return
xml_parser_free($xml_parser);
return false;
}
// Parse the XML text into a array structure
if (xml_parse_into_struct($xml_parser, $xmltext, $values, $tags) == 0)
{
// Error Parsing XML - destroy the parser and return
xml_parser_free($xml_parser);
return false;
}
// Destroy the xml parser
xml_parser_free($xml_parser);
// Clear the output array
$xmp_array = array();
// The XMP data has now been parsed into an array ...
// Cycle through each of the array elements
$current_property = ''; // current property being processed
$container_index = -1; // -1 = no container open, otherwise index of container content
foreach ($values as $xml_elem)
{
// Syntax and Class names
switch ($xml_elem['tag'])
{
case 'x:xmpmeta':
// only defined attribute is x:xmptk written by Adobe XMP Toolkit; value is the version of the toolkit
break;
case 'rdf:RDF':
// required element immediately within x:xmpmeta; no data here
break;
case 'rdf:Description':
switch ($xml_elem['type'])
{
case 'open':
case 'complete':
if (array_key_exists('attributes', $xml_elem))
{
// rdf:Description may contain wanted attributes
foreach (array_keys($xml_elem['attributes']) as $key)
{
// Check whether we want this details from this attribute
// if (in_array($key, $GLOBALS['XMP_tag_captions']))
// if (true)
// {
// Attribute wanted
$xmp_array[$key] = $xml_elem['attributes'][$key];
// }
}
}
break;
case 'cdata':
case 'close':
break;
}
break;
case 'rdf:ID':
case 'rdf:nodeID':
// Attributes are ignored
break;
case 'rdf:li':
// Property member
if ($xml_elem['type'] == 'complete')
{
if (array_key_exists('attributes', $xml_elem))
{
// If Lang Alt (language alternatives) then ensure we take the default language
if (isset($xml_elem['attributes']['xml:lang']) && ($xml_elem['attributes']['xml:lang'] != 'x-default'))
{
break;
}
}
if ($current_property != '')
{
$xmp_array[$current_property][$container_index] = (isset($xml_elem['value']) ? $xml_elem['value'] : '');
$container_index += 1;
}
//else unidentified attribute!!
}
break;
case 'rdf:Seq':
case 'rdf:Bag':
case 'rdf:Alt':
// Container found
switch ($xml_elem['type'])
{
case 'open':
$container_index = 0;
break;
case 'close':
$container_index = -1;
break;
case 'cdata':
break;
}
break;
default:
// Check whether we want the details from this attribute
// if (in_array($xml_elem['tag'], $GLOBALS['XMP_tag_captions']))
// if (true)
// {
switch ($xml_elem['type'])
{
case 'open':
// open current element
$current_property = $xml_elem['tag'];
break;
case 'close':
// close current element
$current_property = '';
break;
case 'complete':
// store attribute value
$xmp_array[$xml_elem['tag']] = (isset($xml_elem['attributes']) ? $xml_elem['attributes'] : (isset($xml_elem['value']) ? $xml_elem['value'] : ''));
break;
case 'cdata':
// ignore
break;
}
// }
break;
}
}
return $xmp_array;
}
/**
* Constructor
*
* @param string $sFilename - Name of the image file to access and extract XMP information from.
*/
public function __construct($sFilename)
{
$this->_sFilename = $sFilename;
if (is_file($this->_sFilename))
{
// Get XMP data
$xmp_data = $this->_get_XMP_text($sFilename);
if ($xmp_data)
{
$aXMP = $this->read_XMP_array_from_text($xmp_data);
if ($aXMP !== false) {
$this->_aXMP = (array) $aXMP;
$this->_bXMPParse = true;
}
}
}
}
}
/**
* Global Variable: XMP_tag_captions
*
* The Property names of all known XMP fields.
* Note: this is a full list with unrequired properties commented out.
*/
/*
$GLOBALS['XMP_tag_captions'] = array(
// IPTC Core
'Iptc4xmpCore:CiAdrCity',
'Iptc4xmpCore:CiAdrCtry',
'Iptc4xmpCore:CiAdrExtadr',
'Iptc4xmpCore:CiAdrPcode',
'Iptc4xmpCore:CiAdrRegion',
'Iptc4xmpCore:CiEmailWork',
'Iptc4xmpCore:CiTelWork',
'Iptc4xmpCore:CiUrlWork',
'Iptc4xmpCore:CountryCode',
'Iptc4xmpCore:CreatorContactInfo',
'Iptc4xmpCore:IntellectualGenre',
'Iptc4xmpCore:Location',
'Iptc4xmpCore:Scene',
'Iptc4xmpCore:SubjectCode',
// Dublin Core Schema
'dc:contributor',
'dc:coverage',
'dc:creator',
'dc:date',
'dc:description',
'dc:format',
'dc:identifier',
'dc:language',
'dc:publisher',
'dc:relation',
'dc:rights',
'dc:source',
'dc:subject',
'dc:title',
'dc:type',
// XMP Basic Schema
'xmp:Advisory',
'xmp:BaseURL',
'xmp:CreateDate',
'xmp:CreatorTool',
'xmp:Identifier',
'xmp:Label',
'xmp:MetadataDate',
'xmp:ModifyDate',
'xmp:Nickname',
'xmp:Rating',
'xmp:Thumbnails',
'xmpidq:Scheme',
// XMP Rights Management Schema
'xmpRights:Certificate',
'xmpRights:Marked',
'xmpRights:Owner',
'xmpRights:UsageTerms',
'xmpRights:WebStatement',
// These are not in spec but Photoshop CS seems to use them
'xap:Advisory',
'xap:BaseURL',
'xap:CreateDate',
'xap:CreatorTool',
'xap:Identifier',
'xap:MetadataDate',
'xap:ModifyDate',
'xap:Nickname',
'xap:Rating',
'xap:Thumbnails',
'xapidq:Scheme',
'xapRights:Certificate',
'xapRights:Copyright',
'xapRights:Marked',
'xapRights:Owner',
'xapRights:UsageTerms',
'xapRights:WebStatement',
// XMP Media Management Schema
'xapMM:DerivedFrom',
'xapMM:DocumentID',
'xapMM:History',
'xapMM:InstanceID',
'xapMM:ManagedFrom',
'xapMM:Manager',
'xapMM:ManageTo',
'xapMM:ManageUI',
'xapMM:ManagerVariant',
'xapMM:RenditionClass',
'xapMM:RenditionParams',
'xapMM:VersionID',
'xapMM:Versions',
'xapMM:LastURL',
'xapMM:RenditionOf',
'xapMM:SaveID',
// XMP Basic Job Ticket Schema
'xapBJ:JobRef',
// XMP Paged-Text Schema
'xmpTPg:MaxPageSize',
'xmpTPg:NPages',
'xmpTPg:Fonts',
'xmpTPg:Colorants',
'xmpTPg:PlateNames',
// Adobe PDF Schema
'pdf:Keywords',
'pdf:PDFVersion',
'pdf:Producer',
// Photoshop Schema
'photoshop:AuthorsPosition',
'photoshop:CaptionWriter',
'photoshop:Category',
'photoshop:City',
'photoshop:Country',
'photoshop:Credit',
'photoshop:DateCreated',
'photoshop:Headline',
'photoshop:History',
// Not in XMP spec
'photoshop:Instructions',
'photoshop:Source',
'photoshop:State',
'photoshop:SupplementalCategories',
'photoshop:TransmissionReference',
'photoshop:Urgency',
// EXIF Schemas
'tiff:ImageWidth',
'tiff:ImageLength',
'tiff:BitsPerSample',
'tiff:Compression',
'tiff:PhotometricInterpretation',
'tiff:Orientation',
'tiff:SamplesPerPixel',
'tiff:PlanarConfiguration',
'tiff:YCbCrSubSampling',
'tiff:YCbCrPositioning',
'tiff:XResolution',
'tiff:YResolution',
'tiff:ResolutionUnit',
'tiff:TransferFunction',
'tiff:WhitePoint',
'tiff:PrimaryChromaticities',
'tiff:YCbCrCoefficients',
'tiff:ReferenceBlackWhite',
'tiff:DateTime',
'tiff:ImageDescription',
'tiff:Make',
'tiff:Model',
'tiff:Software',
'tiff:Artist',
'tiff:Copyright',
'exif:ExifVersion',
'exif:FlashpixVersion',
'exif:ColorSpace',
'exif:ComponentsConfiguration',
'exif:CompressedBitsPerPixel',
'exif:PixelXDimension',
'exif:PixelYDimension',
'exif:MakerNote',
'exif:UserComment',
'exif:RelatedSoundFile',
'exif:DateTimeOriginal',
'exif:DateTimeDigitized',
'exif:ExposureTime',
'exif:FNumber',
'exif:ExposureProgram',
'exif:SpectralSensitivity',
'exif:ISOSpeedRatings',
'exif:OECF',
'exif:ShutterSpeedValue',
'exif:ApertureValue',
'exif:BrightnessValue',
'exif:ExposureBiasValue',
'exif:MaxApertureValue',
'exif:SubjectDistance',
'exif:MeteringMode',
'exif:LightSource',
'exif:Flash',
'exif:FocalLength',
'exif:SubjectArea',
'exif:FlashEnergy',
'exif:SpatialFrequencyResponse',
'exif:FocalPlaneXResolution',
'exif:FocalPlaneYResolution',
'exif:FocalPlaneResolutionUnit',
'exif:SubjectLocation',
'exif:SensingMethod',
'exif:FileSource',
'exif:SceneType',
'exif:CFAPattern',
'exif:CustomRendered',
'exif:ExposureMode',
'exif:WhiteBalance',
'exif:DigitalZoomRatio',
'exif:FocalLengthIn35mmFilm',
'exif:SceneCaptureType',
'exif:GainControl',
'exif:Contrast',
'exif:Saturation',
'exif:Sharpness',
'exif:DeviceSettingDescription',
'exif:SubjectDistanceRange',
'exif:ImageUniqueID',
'exif:GPSVersionID',
'exif:GPSLatitude',
'exif:GPSLongitude',
'exif:GPSAltitudeRef',
'exif:GPSAltitude',
'exif:GPSTimeStamp',
'exif:GPSSatellites',
'exif:GPSStatus',
'exif:GPSMeasureMode',
'exif:GPSDOP',
'exif:GPSSpeedRef',
'exif:GPSSpeed',
'exif:GPSTrackRef',
'exif:GPSTrack',
'exif:GPSImgDirectionRef',
'exif:GPSImgDirection',
'exif:GPSMapDatum',
'exif:GPSDestLatitude',
'exif:GPSDestLongitude',
'exif:GPSDestBearingRef',
'exif:GPSDestBearing',
'exif:GPSDestDistanceRef',
'exif:GPSDestDistance',
'exif:GPSProcessingMethod',
'exif:GPSAreaInformation',
'exif:GPSDifferential',
'stDim:w',
'stDim:h',
'stDim:unit',
'xapGImg:height',
'xapGImg:width',
'xapGImg:format',
'xapGImg:image',
'stEvt:action',
'stEvt:instanceID',
'stEvt:parameters',
'stEvt:softwareAgent',
'stEvt:when',
'stRef:instanceID',
'stRef:documentID',
'stRef:versionID',
'stRef:renditionClass',
'stRef:renditionParams',
'stRef:manager',
'stRef:managerVariant',
'stRef:manageTo',
'stRef:manageUI',
'stVer:comments',
'stVer:event',
'stVer:modifyDate',
'stVer:modifier',
'stVer:version',
'stJob:name',
'stJob:id',
'stJob:url',
// Exif Flash
'exif:Fired',
'exif:Return',
'exif:Mode',
'exif:Function',
'exif:RedEyeMode',
// Exif OECF/SFR
'exif:Columns',
'exif:Rows',
'exif:Names',
'exif:Values',
// Exif CFAPattern
'exif:Columns',
'exif:Rows',
'exif:Values',
// Exif DeviceSettings
'exif:Columns',
'exif:Rows',
'exif:Settings',
);
*/
/**
* Global Variable: JPEG_Segment_Names
*
* The names of the JPEG segment markers, indexed by their marker number
*/
$GLOBALS['JPEG_Segment_Names'] = array(
0x01 => 'TEM',
0x02 => 'RES',
0xC0 => 'SOF0',
0xC1 => 'SOF1',
0xC2 => 'SOF2',
0xC3 => 'SOF4',
0xC4 => 'DHT',
0xC5 => 'SOF5',
0xC6 => 'SOF6',
0xC7 => 'SOF7',
0xC8 => 'JPG',
0xC9 => 'SOF9',
0xCA => 'SOF10',
0xCB => 'SOF11',
0xCC => 'DAC',
0xCD => 'SOF13',
0xCE => 'SOF14',
0xCF => 'SOF15',
0xD0 => 'RST0',
0xD1 => 'RST1',
0xD2 => 'RST2',
0xD3 => 'RST3',
0xD4 => 'RST4',
0xD5 => 'RST5',
0xD6 => 'RST6',
0xD7 => 'RST7',
0xD8 => 'SOI',
0xD9 => 'EOI',
0xDA => 'SOS',
0xDB => 'DQT',
0xDC => 'DNL',
0xDD => 'DRI',
0xDE => 'DHP',
0xDF => 'EXP',
0xE0 => 'APP0',
0xE1 => 'APP1',
0xE2 => 'APP2',
0xE3 => 'APP3',
0xE4 => 'APP4',
0xE5 => 'APP5',
0xE6 => 'APP6',
0xE7 => 'APP7',
0xE8 => 'APP8',
0xE9 => 'APP9',
0xEA => 'APP10',
0xEB => 'APP11',
0xEC => 'APP12',
0xED => 'APP13',
0xEE => 'APP14',
0xEF => 'APP15',
0xF0 => 'JPG0',
0xF1 => 'JPG1',
0xF2 => 'JPG2',
0xF3 => 'JPG3',
0xF4 => 'JPG4',
0xF5 => 'JPG5',
0xF6 => 'JPG6',
0xF7 => 'JPG7',
0xF8 => 'JPG8',
0xF9 => 'JPG9',
0xFA => 'JPG10',
0xFB => 'JPG11',
0xFC => 'JPG12',
0xFD => 'JPG13',
0xFE => 'COM',
);