getID3

The WordPress Core getID3 class.

Defined (1)

The class is defined in the following location(s).

/wp-includes/ID3/getid3.php  
  1. class getID3 
  2. // public: Settings 
  3. public $encoding = 'UTF-8'; // CASE SENSITIVE! - i.e. (must be supported by iconv()). Examples: ISO-8859-1 UTF-8 UTF-16 UTF-16BE 
  4. public $encoding_id3v1 = 'ISO-8859-1'; // Should always be 'ISO-8859-1', but some tags may be written in other encodings such as 'EUC-CN' or 'CP1252' 
  5.  
  6. // public: Optional tag checks - disable for speed. 
  7. public $option_tag_id3v1 = true; // Read and process ID3v1 tags 
  8. public $option_tag_id3v2 = true; // Read and process ID3v2 tags 
  9. public $option_tag_lyrics3 = true; // Read and process Lyrics3 tags 
  10. public $option_tag_apetag = true; // Read and process APE tags 
  11. public $option_tags_process = true; // Copy tags to root key 'tags' and encode to $this->encoding 
  12. public $option_tags_html = true; // Copy tags to root key 'tags_html' properly translated from various encodings to HTML entities 
  13.  
  14. // public: Optional tag/comment calucations 
  15. public $option_extra_info = true; // Calculate additional info such as bitrate, channelmode etc 
  16.  
  17. // public: Optional handling of embedded attachments (e.g. images) 
  18. public $option_save_attachments = true; // defaults to true (ATTACHMENTS_INLINE) for backward compatibility 
  19.  
  20. // public: Optional calculations 
  21. public $option_md5_data = false; // Get MD5 sum of data part - slow 
  22. public $option_md5_data_source = false; // Use MD5 of source file if availble - only FLAC and OptimFROG 
  23. public $option_sha1_data = false; // Get SHA1 sum of data part - slow 
  24. public $option_max_2gb_check = null; // Check whether file is larger than 2GB and thus not supported by 32-bit PHP (null: auto-detect based on PHP_INT_MAX) 
  25.  
  26. // public: Read buffer size in bytes 
  27. public $option_fread_buffer_size = 32768; 
  28.  
  29. // Public variables 
  30. public $filename; // Filename of file being analysed. 
  31. public $fp; // Filepointer to file being analysed. 
  32. public $info; // Result array. 
  33. public $tempdir = GETID3_TEMP_DIR; 
  34. public $memory_limit = 0; 
  35.  
  36. // Protected variables 
  37. protected $startup_error = ''; 
  38. protected $startup_warning = ''; 
  39.  
  40. const VERSION = '1.9.9-20141121'; 
  41. const FREAD_BUFFER_SIZE = 32768; 
  42.  
  43. const ATTACHMENTS_NONE = false; 
  44. const ATTACHMENTS_INLINE = true; 
  45.  
  46. // public: constructor 
  47. public function __construct() { 
  48.  
  49. // Check memory 
  50. $this->memory_limit = ini_get('memory_limit'); 
  51. if (preg_match('#([0-9]+)M#i', $this->memory_limit, $matches)) { 
  52. // could be stored as "16M" rather than 16777216 for example 
  53. $this->memory_limit = $matches[1] * 1048576; 
  54. } elseif (preg_match('#([0-9]+)G#i', $this->memory_limit, $matches)) { // The 'G' modifier is available since PHP 5.1.0 
  55. // could be stored as "2G" rather than 2147483648 for example 
  56. $this->memory_limit = $matches[1] * 1073741824; 
  57. if ($this->memory_limit <= 0) { 
  58. // memory limits probably disabled 
  59. } elseif ($this->memory_limit <= 4194304) { 
  60. $this->startup_error .= 'PHP has less than 4MB available memory and will very likely run out. Increase memory_limit in php.ini'; 
  61. } elseif ($this->memory_limit <= 12582912) { 
  62. $this->startup_warning .= 'PHP has less than 12MB available memory and might run out if all modules are loaded. Increase memory_limit in php.ini'; 
  63.  
  64. // Check safe_mode off 
  65. if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) { 
  66. $this->warning('WARNING: Safe mode is on, shorten support disabled, md5data/sha1data for ogg vorbis disabled, ogg vorbos/flac tag writing disabled.'); 
  67.  
  68. if (intval(ini_get('mbstring.func_overload')) > 0) { 
  69. $this->warning('WARNING: php.ini contains "mbstring.func_overload = '.ini_get('mbstring.func_overload').'", this may break things.'); 
  70.  
  71. // Check for magic_quotes_runtime 
  72. if (function_exists('get_magic_quotes_runtime')) { 
  73. if (get_magic_quotes_runtime()) { 
  74. return $this->startup_error('magic_quotes_runtime must be disabled before running getID3(). Surround getid3 block by set_magic_quotes_runtime(0) and set_magic_quotes_runtime(1).'); 
  75.  
  76. // Check for magic_quotes_gpc 
  77. if (function_exists('magic_quotes_gpc')) { 
  78. if (get_magic_quotes_gpc()) { 
  79. return $this->startup_error('magic_quotes_gpc must be disabled before running getID3(). Surround getid3 block by set_magic_quotes_gpc(0) and set_magic_quotes_gpc(1).'); 
  80.  
  81. // Load support library 
  82. if (!include_once(GETID3_INCLUDEPATH.'getid3.lib.php')) { 
  83. $this->startup_error .= 'getid3.lib.php is missing or corrupt'; 
  84.  
  85. if ($this->option_max_2gb_check === null) { 
  86. $this->option_max_2gb_check = (PHP_INT_MAX <= 2147483647); 
  87.  
  88.  
  89. // Needed for Windows only: 
  90. // Define locations of helper applications for Shorten, VorbisComment, MetaFLAC 
  91. // as well as other helper functions such as head, tail, md5sum, etc 
  92. // This path cannot contain spaces, but the below code will attempt to get the 
  93. // 8.3-equivalent path automatically 
  94. // IMPORTANT: This path must include the trailing slash 
  95. if (GETID3_OS_ISWINDOWS && !defined('GETID3_HELPERAPPSDIR')) { 
  96.  
  97. $helperappsdir = GETID3_INCLUDEPATH.'..'.DIRECTORY_SEPARATOR.'helperapps'; // must not have any space in this path 
  98.  
  99. if (!is_dir($helperappsdir)) { 
  100. $this->startup_warning .= '"'.$helperappsdir.'" cannot be defined as GETID3_HELPERAPPSDIR because it does not exist'; 
  101. } elseif (strpos(realpath($helperappsdir), ' ') !== false) { 
  102. $DirPieces = explode(DIRECTORY_SEPARATOR, realpath($helperappsdir)); 
  103. $path_so_far = array(); 
  104. foreach ($DirPieces as $key => $value) { 
  105. if (strpos($value, ' ') !== false) { 
  106. if (!empty($path_so_far)) { 
  107. $commandline = 'dir /x '.escapeshellarg(implode(DIRECTORY_SEPARATOR, $path_so_far)); 
  108. $dir_listing = `$commandline`; 
  109. $lines = explode("\n", $dir_listing); 
  110. foreach ($lines as $line) { 
  111. $line = trim($line); 
  112. if (preg_match('#^([0-9/]{10}) +([0-9:]{4, 5}( [AP]M)?) +(<DIR>|[0-9, ]+) +([^ ]{0, 11}) +(.+)$#', $line, $matches)) { 
  113. list($dummy, $date, $time, $ampm, $filesize, $shortname, $filename) = $matches; 
  114. if ((strtoupper($filesize) == '<DIR>') && (strtolower($filename) == strtolower($value))) { 
  115. $value = $shortname; 
  116. } else { 
  117. $this->startup_warning .= 'GETID3_HELPERAPPSDIR must not have any spaces in it - use 8dot3 naming convention if neccesary. You can run "dir /x" from the commandline to see the correct 8.3-style names.'; 
  118. $path_so_far[] = $value; 
  119. $helperappsdir = implode(DIRECTORY_SEPARATOR, $path_so_far); 
  120. define('GETID3_HELPERAPPSDIR', $helperappsdir.DIRECTORY_SEPARATOR); 
  121.  
  122. return true; 
  123.  
  124. public function version() { 
  125. return self::VERSION; 
  126.  
  127. public function fread_buffer_size() { 
  128. return $this->option_fread_buffer_size; 
  129.  
  130.  
  131. // public: setOption 
  132. public function setOption($optArray) { 
  133. if (!is_array($optArray) || empty($optArray)) { 
  134. return false; 
  135. foreach ($optArray as $opt => $val) { 
  136. if (isset($this->$opt) === false) { 
  137. continue; 
  138. $this->$opt = $val; 
  139. return true; 
  140.  
  141.  
  142. public function openfile($filename) { 
  143. try { 
  144. if (!empty($this->startup_error)) { 
  145. throw new getid3_exception($this->startup_error); 
  146. if (!empty($this->startup_warning)) { 
  147. $this->warning($this->startup_warning); 
  148.  
  149. // init result array and set parameters 
  150. $this->filename = $filename; 
  151. $this->info = array(); 
  152. $this->info['GETID3_VERSION'] = $this->version(); 
  153. $this->info['php_memory_limit'] = (($this->memory_limit > 0) ? $this->memory_limit : false); 
  154.  
  155. // remote files not supported 
  156. if (preg_match('/^(ht|f)tp:\/\//', $filename)) { 
  157. throw new getid3_exception('Remote files are not supported - please copy the file locally first'); 
  158.  
  159. $filename = str_replace('/', DIRECTORY_SEPARATOR, $filename); 
  160. $filename = preg_replace('#(.+)'.preg_quote(DIRECTORY_SEPARATOR).'{2, }#U', '\1'.DIRECTORY_SEPARATOR, $filename); 
  161.  
  162. // open local file 
  163. //if (is_readable($filename) && is_file($filename) && ($this->fp = fopen($filename, 'rb'))) { // see http://www.getid3.org/phpBB3/viewtopic.php?t=1720 
  164. if ((is_readable($filename) || file_exists($filename)) && is_file($filename) && ($this->fp = fopen($filename, 'rb'))) { 
  165. // great 
  166. } else { 
  167. $errormessagelist = array(); 
  168. if (!is_readable($filename)) { 
  169. $errormessagelist[] = '!is_readable'; 
  170. if (!is_file($filename)) { 
  171. $errormessagelist[] = '!is_file'; 
  172. if (!file_exists($filename)) { 
  173. $errormessagelist[] = '!file_exists'; 
  174. if (empty($errormessagelist)) { 
  175. $errormessagelist[] = 'fopen failed'; 
  176. throw new getid3_exception('Could not open "'.$filename.'" ('.implode('; ', $errormessagelist).')'); 
  177.  
  178. $this->info['filesize'] = filesize($filename); 
  179. // set redundant parameters - might be needed in some include file 
  180. // filenames / filepaths in getID3 are always expressed with forward slashes (unix-style) for both Windows and other to try and minimize confusion 
  181. $filename = str_replace('\\', '/', $filename); 
  182. $this->info['filepath'] = str_replace('\\', '/', realpath(dirname($filename))); 
  183. $this->info['filename'] = getid3_lib::mb_basename($filename); 
  184. $this->info['filenamepath'] = $this->info['filepath'].'/'.$this->info['filename']; 
  185.  
  186.  
  187. // option_max_2gb_check 
  188. if ($this->option_max_2gb_check) { 
  189. // PHP (32-bit all, and 64-bit Windows) doesn't support integers larger than 2^31 (~2GB) 
  190. // filesize() simply returns (filesize % (pow(2, 32)), no matter the actual filesize 
  191. // ftell() returns 0 if seeking to the end is beyond the range of unsigned integer 
  192. $fseek = fseek($this->fp, 0, SEEK_END); 
  193. if (($fseek < 0) || (($this->info['filesize'] != 0) && (ftell($this->fp) == 0)) || 
  194. ($this->info['filesize'] < 0) || 
  195. (ftell($this->fp) < 0)) { 
  196. $real_filesize = getid3_lib::getFileSizeSyscall($this->info['filenamepath']); 
  197.  
  198. if ($real_filesize === false) { 
  199. unset($this->info['filesize']); 
  200. fclose($this->fp); 
  201. throw new getid3_exception('Unable to determine actual filesize. File is most likely larger than '.round(PHP_INT_MAX / 1073741824).'GB and is not supported by PHP.'); 
  202. } elseif (getid3_lib::intValueSupported($real_filesize)) { 
  203. unset($this->info['filesize']); 
  204. fclose($this->fp); 
  205. throw new getid3_exception('PHP seems to think the file is larger than '.round(PHP_INT_MAX / 1073741824).'GB, but filesystem reports it as '.number_format($real_filesize, 3).'GB, please report to info@getid3.org'); 
  206. $this->info['filesize'] = $real_filesize; 
  207. $this->warning('File is larger than '.round(PHP_INT_MAX / 1073741824).'GB (filesystem reports it as '.number_format($real_filesize, 3).'GB) and is not properly supported by PHP.'); 
  208.  
  209. // set more parameters 
  210. $this->info['avdataoffset'] = 0; 
  211. $this->info['avdataend'] = $this->info['filesize']; 
  212. $this->info['fileformat'] = ''; // filled in later 
  213. $this->info['audio']['dataformat'] = ''; // filled in later, unset if not used 
  214. $this->info['video']['dataformat'] = ''; // filled in later, unset if not used 
  215. $this->info['tags'] = array(); // filled in later, unset if not used 
  216. $this->info['error'] = array(); // filled in later, unset if not used 
  217. $this->info['warning'] = array(); // filled in later, unset if not used 
  218. $this->info['comments'] = array(); // filled in later, unset if not used 
  219. $this->info['encoding'] = $this->encoding; // required by id3v2 and iso modules - can be unset at the end if desired 
  220.  
  221. return true; 
  222.  
  223. } catch (Exception $e) { 
  224. $this->error($e->getMessage()); 
  225. return false; 
  226.  
  227. // public: analyze file 
  228. public function analyze($filename) { 
  229. try { 
  230. if (!$this->openfile($filename)) { 
  231. return $this->info; 
  232.  
  233. // Handle tags 
  234. foreach (array('id3v2'=>'id3v2', 'id3v1'=>'id3v1', 'apetag'=>'ape', 'lyrics3'=>'lyrics3') as $tag_name => $tag_key) { 
  235. $option_tag = 'option_tag_'.$tag_name; 
  236. if ($this->$option_tag) { 
  237. $this->include_module('tag.'.$tag_name); 
  238. try { 
  239. $tag_class = 'getid3_'.$tag_name; 
  240. $tag = new $tag_class($this); 
  241. $tag->Analyze(); 
  242. catch (getid3_exception $e) { 
  243. throw $e; 
  244. if (isset($this->info['id3v2']['tag_offset_start'])) { 
  245. $this->info['avdataoffset'] = max($this->info['avdataoffset'], $this->info['id3v2']['tag_offset_end']); 
  246. foreach (array('id3v1'=>'id3v1', 'apetag'=>'ape', 'lyrics3'=>'lyrics3') as $tag_name => $tag_key) { 
  247. if (isset($this->info[$tag_key]['tag_offset_start'])) { 
  248. $this->info['avdataend'] = min($this->info['avdataend'], $this->info[$tag_key]['tag_offset_start']); 
  249.  
  250. // ID3v2 detection (NOT parsing), even if ($this->option_tag_id3v2 == false) done to make fileformat easier 
  251. if (!$this->option_tag_id3v2) { 
  252. fseek($this->fp, 0); 
  253. $header = fread($this->fp, 10); 
  254. if ((substr($header, 0, 3) == 'ID3') && (strlen($header) == 10)) { 
  255. $this->info['id3v2']['header'] = true; 
  256. $this->info['id3v2']['majorversion'] = ord($header{3}); 
  257. $this->info['id3v2']['minorversion'] = ord($header{4}); 
  258. $this->info['avdataoffset'] += getid3_lib::BigEndian2Int(substr($header, 6, 4), 1) + 10; // length of ID3v2 tag in 10-byte header doesn't include 10-byte header length 
  259.  
  260. // read 32 kb file data 
  261. fseek($this->fp, $this->info['avdataoffset']); 
  262. $formattest = fread($this->fp, 32774); 
  263.  
  264. // determine format 
  265. $determined_format = $this->GetFileFormat($formattest, $filename); 
  266.  
  267. // unable to determine file format 
  268. if (!$determined_format) { 
  269. fclose($this->fp); 
  270. return $this->error('unable to determine file format'); 
  271.  
  272. // check for illegal ID3 tags 
  273. if (isset($determined_format['fail_id3']) && (in_array('id3v1', $this->info['tags']) || in_array('id3v2', $this->info['tags']))) { 
  274. if ($determined_format['fail_id3'] === 'ERROR') { 
  275. fclose($this->fp); 
  276. return $this->error('ID3 tags not allowed on this file type.'); 
  277. } elseif ($determined_format['fail_id3'] === 'WARNING') { 
  278. $this->warning('ID3 tags not allowed on this file type.'); 
  279.  
  280. // check for illegal APE tags 
  281. if (isset($determined_format['fail_ape']) && in_array('ape', $this->info['tags'])) { 
  282. if ($determined_format['fail_ape'] === 'ERROR') { 
  283. fclose($this->fp); 
  284. return $this->error('APE tags not allowed on this file type.'); 
  285. } elseif ($determined_format['fail_ape'] === 'WARNING') { 
  286. $this->warning('APE tags not allowed on this file type.'); 
  287.  
  288. // set mime type 
  289. $this->info['mime_type'] = $determined_format['mime_type']; 
  290.  
  291. // supported format signature pattern detected, but module deleted 
  292. if (!file_exists(GETID3_INCLUDEPATH.$determined_format['include'])) { 
  293. fclose($this->fp); 
  294. return $this->error('Format not supported, module "'.$determined_format['include'].'" was removed.'); 
  295.  
  296. // module requires iconv support 
  297. // Check encoding/iconv support 
  298. if (!empty($determined_format['iconv_req']) && !function_exists('iconv') && !in_array($this->encoding, array('ISO-8859-1', 'UTF-8', 'UTF-16LE', 'UTF-16BE', 'UTF-16'))) { 
  299. $errormessage = 'iconv() support is required for this module ('.$determined_format['include'].') for encodings other than ISO-8859-1, UTF-8, UTF-16LE, UTF16-BE, UTF-16. '; 
  300. if (GETID3_OS_ISWINDOWS) { 
  301. $errormessage .= 'PHP does not have iconv() support. Please enable php_iconv.dll in php.ini, and copy iconv.dll from c:/php/dlls to c:/windows/system32'; 
  302. } else { 
  303. $errormessage .= 'PHP is not compiled with iconv() support. Please recompile with the --with-iconv switch'; 
  304. return $this->error($errormessage); 
  305.  
  306. // include module 
  307. include_once(GETID3_INCLUDEPATH.$determined_format['include']); 
  308.  
  309. // instantiate module class 
  310. $class_name = 'getid3_'.$determined_format['module']; 
  311. if (!class_exists($class_name)) { 
  312. return $this->error('Format not supported, module "'.$determined_format['include'].'" is corrupt.'); 
  313. $class = new $class_name($this); 
  314. $class->Analyze(); 
  315. unset($class); 
  316.  
  317. // close file 
  318. fclose($this->fp); 
  319.  
  320. // process all tags - copy to 'tags' and convert charsets 
  321. if ($this->option_tags_process) { 
  322. $this->HandleAllTags(); 
  323.  
  324. // perform more calculations 
  325. if ($this->option_extra_info) { 
  326. $this->ChannelsBitratePlaytimeCalculations(); 
  327. $this->CalculateCompressionRatioVideo(); 
  328. $this->CalculateCompressionRatioAudio(); 
  329. $this->CalculateReplayGain(); 
  330. $this->ProcessAudioStreams(); 
  331.  
  332. // get the MD5 sum of the audio/video portion of the file - without ID3/APE/Lyrics3/etc header/footer tags 
  333. if ($this->option_md5_data) { 
  334. // do not calc md5_data if md5_data_source is present - set by flac only - future MPC/SV8 too 
  335. if (!$this->option_md5_data_source || empty($this->info['md5_data_source'])) { 
  336. $this->getHashdata('md5'); 
  337.  
  338. // get the SHA1 sum of the audio/video portion of the file - without ID3/APE/Lyrics3/etc header/footer tags 
  339. if ($this->option_sha1_data) { 
  340. $this->getHashdata('sha1'); 
  341.  
  342. // remove undesired keys 
  343. $this->CleanUp(); 
  344.  
  345. } catch (Exception $e) { 
  346. $this->error('Caught exception: '.$e->getMessage()); 
  347.  
  348. // return info array 
  349. return $this->info; 
  350.  
  351.  
  352. // private: error handling 
  353. public function error($message) { 
  354. $this->CleanUp(); 
  355. if (!isset($this->info['error'])) { 
  356. $this->info['error'] = array(); 
  357. $this->info['error'][] = $message; 
  358. return $this->info; 
  359.  
  360.  
  361. // private: warning handling 
  362. public function warning($message) { 
  363. $this->info['warning'][] = $message; 
  364. return true; 
  365.  
  366.  
  367. // private: CleanUp 
  368. private function CleanUp() { 
  369.  
  370. // remove possible empty keys 
  371. $AVpossibleEmptyKeys = array('dataformat', 'bits_per_sample', 'encoder_options', 'streams', 'bitrate'); 
  372. foreach ($AVpossibleEmptyKeys as $dummy => $key) { 
  373. if (empty($this->info['audio'][$key]) && isset($this->info['audio'][$key])) { 
  374. unset($this->info['audio'][$key]); 
  375. if (empty($this->info['video'][$key]) && isset($this->info['video'][$key])) { 
  376. unset($this->info['video'][$key]); 
  377.  
  378. // remove empty root keys 
  379. if (!empty($this->info)) { 
  380. foreach ($this->info as $key => $value) { 
  381. if (empty($this->info[$key]) && ($this->info[$key] !== 0) && ($this->info[$key] !== '0')) { 
  382. unset($this->info[$key]); 
  383.  
  384. // remove meaningless entries from unknown-format files 
  385. if (empty($this->info['fileformat'])) { 
  386. if (isset($this->info['avdataoffset'])) { 
  387. unset($this->info['avdataoffset']); 
  388. if (isset($this->info['avdataend'])) { 
  389. unset($this->info['avdataend']); 
  390.  
  391. // remove possible duplicated identical entries 
  392. if (!empty($this->info['error'])) { 
  393. $this->info['error'] = array_values(array_unique($this->info['error'])); 
  394. if (!empty($this->info['warning'])) { 
  395. $this->info['warning'] = array_values(array_unique($this->info['warning'])); 
  396.  
  397. // remove "global variable" type keys 
  398. unset($this->info['php_memory_limit']); 
  399.  
  400. return true; 
  401.  
  402.  
  403. // return array containing information about all supported formats 
  404. public function GetFileFormatArray() { 
  405. static $format_info = array(); 
  406. if (empty($format_info)) { 
  407. $format_info = array( 
  408.  
  409. // Audio formats 
  410.  
  411. // AC-3 - audio - Dolby AC-3 / Dolby Digital 
  412. 'ac3' => array( 
  413. 'pattern' => '^\x0B\x77',  
  414. 'group' => 'audio',  
  415. 'module' => 'ac3',  
  416. 'mime_type' => 'audio/ac3',  
  417. ),  
  418.  
  419. // AAC - audio - Advanced Audio Coding (AAC) - ADIF format 
  420. 'adif' => array( 
  421. 'pattern' => '^ADIF',  
  422. 'group' => 'audio',  
  423. 'module' => 'aac',  
  424. 'mime_type' => 'application/octet-stream',  
  425. 'fail_ape' => 'WARNING',  
  426. ),  
  427.  
  428. /** 
  429. // AA - audio - Audible Audiobook 
  430. 'aa' => array( 
  431. 'pattern' => '^.{4}\x57\x90\x75\x36',  
  432. 'group' => 'audio',  
  433. 'module' => 'aa',  
  434. 'mime_type' => 'audio/audible',  
  435. ),  
  436. */ 
  437. // AAC - audio - Advanced Audio Coding (AAC) - ADTS format (very similar to MP3) 
  438. 'adts' => array( 
  439. 'pattern' => '^\xFF[\xF0-\xF1\xF8-\xF9]',  
  440. 'group' => 'audio',  
  441. 'module' => 'aac',  
  442. 'mime_type' => 'application/octet-stream',  
  443. 'fail_ape' => 'WARNING',  
  444. ),  
  445.  
  446.  
  447. // AU - audio - NeXT/Sun AUdio (AU) 
  448. 'au' => array( 
  449. 'pattern' => '^\.snd',  
  450. 'group' => 'audio',  
  451. 'module' => 'au',  
  452. 'mime_type' => 'audio/basic',  
  453. ),  
  454.  
  455. // AMR - audio - Adaptive Multi Rate 
  456. 'amr' => array( 
  457. 'pattern' => '^\x23\x21AMR\x0A', // #!AMR[0A] 
  458. 'group' => 'audio',  
  459. 'module' => 'amr',  
  460. 'mime_type' => 'audio/amr',  
  461. ),  
  462.  
  463. // AVR - audio - Audio Visual Research 
  464. 'avr' => array( 
  465. 'pattern' => '^2BIT',  
  466. 'group' => 'audio',  
  467. 'module' => 'avr',  
  468. 'mime_type' => 'application/octet-stream',  
  469. ),  
  470.  
  471. // BONK - audio - Bonk v0.9+ 
  472. 'bonk' => array( 
  473. 'pattern' => '^\x00(BONK|INFO|META| ID3)',  
  474. 'group' => 'audio',  
  475. 'module' => 'bonk',  
  476. 'mime_type' => 'audio/xmms-bonk',  
  477. ),  
  478.  
  479. // DSS - audio - Digital Speech Standard 
  480. 'dss' => array( 
  481. 'pattern' => '^[\x02-\x03]ds[s2]',  
  482. 'group' => 'audio',  
  483. 'module' => 'dss',  
  484. 'mime_type' => 'application/octet-stream',  
  485. ),  
  486.  
  487. // DTS - audio - Dolby Theatre System 
  488. 'dts' => array( 
  489. 'pattern' => '^\x7F\xFE\x80\x01',  
  490. 'group' => 'audio',  
  491. 'module' => 'dts',  
  492. 'mime_type' => 'audio/dts',  
  493. ),  
  494.  
  495. // FLAC - audio - Free Lossless Audio Codec 
  496. 'flac' => array( 
  497. 'pattern' => '^fLaC',  
  498. 'group' => 'audio',  
  499. 'module' => 'flac',  
  500. 'mime_type' => 'audio/x-flac',  
  501. ),  
  502.  
  503. // LA - audio - Lossless Audio (LA) 
  504. 'la' => array( 
  505. 'pattern' => '^LA0[2-4]',  
  506. 'group' => 'audio',  
  507. 'module' => 'la',  
  508. 'mime_type' => 'application/octet-stream',  
  509. ),  
  510.  
  511. // LPAC - audio - Lossless Predictive Audio Compression (LPAC) 
  512. 'lpac' => array( 
  513. 'pattern' => '^LPAC',  
  514. 'group' => 'audio',  
  515. 'module' => 'lpac',  
  516. 'mime_type' => 'application/octet-stream',  
  517. ),  
  518.  
  519. // MIDI - audio - MIDI (Musical Instrument Digital Interface) 
  520. 'midi' => array( 
  521. 'pattern' => '^MThd',  
  522. 'group' => 'audio',  
  523. 'module' => 'midi',  
  524. 'mime_type' => 'audio/midi',  
  525. ),  
  526.  
  527. // MAC - audio - Monkey's Audio Compressor 
  528. 'mac' => array( 
  529. 'pattern' => '^MAC ',  
  530. 'group' => 'audio',  
  531. 'module' => 'monkey',  
  532. 'mime_type' => 'application/octet-stream',  
  533. ),  
  534.  
  535. // has been known to produce false matches in random files (e.g. JPEGs), leave out until more precise matching available 
  536. // // MOD - audio - MODule (assorted sub-formats) 
  537. // 'mod' => array( 
  538. // 'pattern' => '^.{1080}(M\\.K\\.|M!K!|FLT4|FLT8|[5-9]CHN|[1-3][0-9]CH)',  
  539. // 'group' => 'audio',  
  540. // 'module' => 'mod',  
  541. // 'option' => 'mod',  
  542. // 'mime_type' => 'audio/mod',  
  543. // ),  
  544.  
  545. // MOD - audio - MODule (Impulse Tracker) 
  546. 'it' => array( 
  547. 'pattern' => '^IMPM',  
  548. 'group' => 'audio',  
  549. 'module' => 'mod',  
  550. //'option' => 'it',  
  551. 'mime_type' => 'audio/it',  
  552. ),  
  553.  
  554. // MOD - audio - MODule (eXtended Module, various sub-formats) 
  555. 'xm' => array( 
  556. 'pattern' => '^Extended Module',  
  557. 'group' => 'audio',  
  558. 'module' => 'mod',  
  559. //'option' => 'xm',  
  560. 'mime_type' => 'audio/xm',  
  561. ),  
  562.  
  563. // MOD - audio - MODule (ScreamTracker) 
  564. 's3m' => array( 
  565. 'pattern' => '^.{44}SCRM',  
  566. 'group' => 'audio',  
  567. 'module' => 'mod',  
  568. //'option' => 's3m',  
  569. 'mime_type' => 'audio/s3m',  
  570. ),  
  571.  
  572. // MPC - audio - Musepack / MPEGplus 
  573. 'mpc' => array( 
  574. 'pattern' => '^(MPCK|MP\+|[\x00\x01\x10\x11\x40\x41\x50\x51\x80\x81\x90\x91\xC0\xC1\xD0\xD1][\x20-37][\x00\x20\x40\x60\x80\xA0\xC0\xE0])',  
  575. 'group' => 'audio',  
  576. 'module' => 'mpc',  
  577. 'mime_type' => 'audio/x-musepack',  
  578. ),  
  579.  
  580. // MP3 - audio - MPEG-audio Layer 3 (very similar to AAC-ADTS) 
  581. 'mp3' => array( 
  582. 'pattern' => '^\xFF[\xE2-\xE7\xF2-\xF7\xFA-\xFF][\x00-\x0B\x10-\x1B\x20-\x2B\x30-\x3B\x40-\x4B\x50-\x5B\x60-\x6B\x70-\x7B\x80-\x8B\x90-\x9B\xA0-\xAB\xB0-\xBB\xC0-\xCB\xD0-\xDB\xE0-\xEB\xF0-\xFB]',  
  583. 'group' => 'audio',  
  584. 'module' => 'mp3',  
  585. 'mime_type' => 'audio/mpeg',  
  586. ),  
  587.  
  588. // OFR - audio - OptimFROG 
  589. 'ofr' => array( 
  590. 'pattern' => '^(\*RIFF|OFR)',  
  591. 'group' => 'audio',  
  592. 'module' => 'optimfrog',  
  593. 'mime_type' => 'application/octet-stream',  
  594. ),  
  595.  
  596. // RKAU - audio - RKive AUdio compressor 
  597. 'rkau' => array( 
  598. 'pattern' => '^RKA',  
  599. 'group' => 'audio',  
  600. 'module' => 'rkau',  
  601. 'mime_type' => 'application/octet-stream',  
  602. ),  
  603.  
  604. // SHN - audio - Shorten 
  605. 'shn' => array( 
  606. 'pattern' => '^ajkg',  
  607. 'group' => 'audio',  
  608. 'module' => 'shorten',  
  609. 'mime_type' => 'audio/xmms-shn',  
  610. 'fail_id3' => 'ERROR',  
  611. 'fail_ape' => 'ERROR',  
  612. ),  
  613.  
  614. // TTA - audio - TTA Lossless Audio Compressor (http://tta.corecodec.org) 
  615. 'tta' => array( 
  616. 'pattern' => '^TTA', // could also be '^TTA(\x01|\x02|\x03|2|1)' 
  617. 'group' => 'audio',  
  618. 'module' => 'tta',  
  619. 'mime_type' => 'application/octet-stream',  
  620. ),  
  621.  
  622. // VOC - audio - Creative Voice (VOC) 
  623. 'voc' => array( 
  624. 'pattern' => '^Creative Voice File',  
  625. 'group' => 'audio',  
  626. 'module' => 'voc',  
  627. 'mime_type' => 'audio/voc',  
  628. ),  
  629.  
  630. // VQF - audio - transform-domain weighted interleave Vector Quantization Format (VQF) 
  631. 'vqf' => array( 
  632. 'pattern' => '^TWIN',  
  633. 'group' => 'audio',  
  634. 'module' => 'vqf',  
  635. 'mime_type' => 'application/octet-stream',  
  636. ),  
  637.  
  638. // WV - audio - WavPack (v4.0+) 
  639. 'wv' => array( 
  640. 'pattern' => '^wvpk',  
  641. 'group' => 'audio',  
  642. 'module' => 'wavpack',  
  643. 'mime_type' => 'application/octet-stream',  
  644. ),  
  645.  
  646.  
  647. // Audio-Video formats 
  648.  
  649. // ASF - audio/video - Advanced Streaming Format, Windows Media Video, Windows Media Audio 
  650. 'asf' => array( 
  651. 'pattern' => '^\x30\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C',  
  652. 'group' => 'audio-video',  
  653. 'module' => 'asf',  
  654. 'mime_type' => 'video/x-ms-asf',  
  655. 'iconv_req' => false,  
  656. ),  
  657.  
  658. // BINK - audio/video - Bink / Smacker 
  659. 'bink' => array( 
  660. 'pattern' => '^(BIK|SMK)',  
  661. 'group' => 'audio-video',  
  662. 'module' => 'bink',  
  663. 'mime_type' => 'application/octet-stream',  
  664. ),  
  665.  
  666. // FLV - audio/video - FLash Video 
  667. 'flv' => array( 
  668. 'pattern' => '^FLV\x01',  
  669. 'group' => 'audio-video',  
  670. 'module' => 'flv',  
  671. 'mime_type' => 'video/x-flv',  
  672. ),  
  673.  
  674. // MKAV - audio/video - Mastroka 
  675. 'matroska' => array( 
  676. 'pattern' => '^\x1A\x45\xDF\xA3',  
  677. 'group' => 'audio-video',  
  678. 'module' => 'matroska',  
  679. 'mime_type' => 'video/x-matroska', // may also be audio/x-matroska 
  680. ),  
  681.  
  682. // MPEG - audio/video - MPEG (Moving Pictures Experts Group) 
  683. 'mpeg' => array( 
  684. 'pattern' => '^\x00\x00\x01(\xBA|\xB3)',  
  685. 'group' => 'audio-video',  
  686. 'module' => 'mpeg',  
  687. 'mime_type' => 'video/mpeg',  
  688. ),  
  689.  
  690. // NSV - audio/video - Nullsoft Streaming Video (NSV) 
  691. 'nsv' => array( 
  692. 'pattern' => '^NSV[sf]',  
  693. 'group' => 'audio-video',  
  694. 'module' => 'nsv',  
  695. 'mime_type' => 'application/octet-stream',  
  696. ),  
  697.  
  698. // Ogg - audio/video - Ogg (Ogg-Vorbis, Ogg-FLAC, Speex, Ogg-Theora(*), Ogg-Tarkin(*)) 
  699. 'ogg' => array( 
  700. 'pattern' => '^OggS',  
  701. 'group' => 'audio',  
  702. 'module' => 'ogg',  
  703. 'mime_type' => 'application/ogg',  
  704. 'fail_id3' => 'WARNING',  
  705. 'fail_ape' => 'WARNING',  
  706. ),  
  707.  
  708. // QT - audio/video - Quicktime 
  709. 'quicktime' => array( 
  710. 'pattern' => '^.{4}(cmov|free|ftyp|mdat|moov|pnot|skip|wide)',  
  711. 'group' => 'audio-video',  
  712. 'module' => 'quicktime',  
  713. 'mime_type' => 'video/quicktime',  
  714. ),  
  715.  
  716. // RIFF - audio/video - Resource Interchange File Format (RIFF) / WAV / AVI / CD-audio / SDSS = renamed variant used by SmartSound QuickTracks (www.smartsound.com) / FORM = Audio Interchange File Format (AIFF) 
  717. 'riff' => array( 
  718. 'pattern' => '^(RIFF|SDSS|FORM)',  
  719. 'group' => 'audio-video',  
  720. 'module' => 'riff',  
  721. 'mime_type' => 'audio/x-wave',  
  722. 'fail_ape' => 'WARNING',  
  723. ),  
  724.  
  725. // Real - audio/video - RealAudio, RealVideo 
  726. 'real' => array( 
  727. 'pattern' => '^(\\.RMF|\\.ra)',  
  728. 'group' => 'audio-video',  
  729. 'module' => 'real',  
  730. 'mime_type' => 'audio/x-realaudio',  
  731. ),  
  732.  
  733. // SWF - audio/video - ShockWave Flash 
  734. 'swf' => array( 
  735. 'pattern' => '^(F|C)WS',  
  736. 'group' => 'audio-video',  
  737. 'module' => 'swf',  
  738. 'mime_type' => 'application/x-shockwave-flash',  
  739. ),  
  740.  
  741. // TS - audio/video - MPEG-2 Transport Stream 
  742. 'ts' => array( 
  743. 'pattern' => '^(\x47.{187}) {10, }', // packets are 188 bytes long and start with 0x47 "G". Check for at least 10 packets matching this pattern 
  744. 'group' => 'audio-video',  
  745. 'module' => 'ts',  
  746. 'mime_type' => 'video/MP2T',  
  747. ),  
  748.  
  749.  
  750. // Still-Image formats 
  751.  
  752. // BMP - still image - Bitmap (Windows, OS/2; uncompressed, RLE8, RLE4) 
  753. 'bmp' => array( 
  754. 'pattern' => '^BM',  
  755. 'group' => 'graphic',  
  756. 'module' => 'bmp',  
  757. 'mime_type' => 'image/bmp',  
  758. 'fail_id3' => 'ERROR',  
  759. 'fail_ape' => 'ERROR',  
  760. ),  
  761.  
  762. // GIF - still image - Graphics Interchange Format 
  763. 'gif' => array( 
  764. 'pattern' => '^GIF',  
  765. 'group' => 'graphic',  
  766. 'module' => 'gif',  
  767. 'mime_type' => 'image/gif',  
  768. 'fail_id3' => 'ERROR',  
  769. 'fail_ape' => 'ERROR',  
  770. ),  
  771.  
  772. // JPEG - still image - Joint Photographic Experts Group (JPEG) 
  773. 'jpg' => array( 
  774. 'pattern' => '^\xFF\xD8\xFF',  
  775. 'group' => 'graphic',  
  776. 'module' => 'jpg',  
  777. 'mime_type' => 'image/jpeg',  
  778. 'fail_id3' => 'ERROR',  
  779. 'fail_ape' => 'ERROR',  
  780. ),  
  781.  
  782. // PCD - still image - Kodak Photo CD 
  783. 'pcd' => array( 
  784. 'pattern' => '^.{2048}PCD_IPI\x00',  
  785. 'group' => 'graphic',  
  786. 'module' => 'pcd',  
  787. 'mime_type' => 'image/x-photo-cd',  
  788. 'fail_id3' => 'ERROR',  
  789. 'fail_ape' => 'ERROR',  
  790. ),  
  791.  
  792.  
  793. // PNG - still image - Portable Network Graphics (PNG) 
  794. 'png' => array( 
  795. 'pattern' => '^\x89\x50\x4E\x47\x0D\x0A\x1A\x0A',  
  796. 'group' => 'graphic',  
  797. 'module' => 'png',  
  798. 'mime_type' => 'image/png',  
  799. 'fail_id3' => 'ERROR',  
  800. 'fail_ape' => 'ERROR',  
  801. ),  
  802.  
  803.  
  804. // SVG - still image - Scalable Vector Graphics (SVG) 
  805. 'svg' => array( 
  806. 'pattern' => '(<!DOCTYPE svg PUBLIC |xmlns="http:\/\/www\.w3\.org\/2000\/svg")',  
  807. 'group' => 'graphic',  
  808. 'module' => 'svg',  
  809. 'mime_type' => 'image/svg+xml',  
  810. 'fail_id3' => 'ERROR',  
  811. 'fail_ape' => 'ERROR',  
  812. ),  
  813.  
  814.  
  815. // TIFF - still image - Tagged Information File Format (TIFF) 
  816. 'tiff' => array( 
  817. 'pattern' => '^(II\x2A\x00|MM\x00\x2A)',  
  818. 'group' => 'graphic',  
  819. 'module' => 'tiff',  
  820. 'mime_type' => 'image/tiff',  
  821. 'fail_id3' => 'ERROR',  
  822. 'fail_ape' => 'ERROR',  
  823. ),  
  824.  
  825.  
  826. // EFAX - still image - eFax (TIFF derivative) 
  827. 'efax' => array( 
  828. 'pattern' => '^\xDC\xFE',  
  829. 'group' => 'graphic',  
  830. 'module' => 'efax',  
  831. 'mime_type' => 'image/efax',  
  832. 'fail_id3' => 'ERROR',  
  833. 'fail_ape' => 'ERROR',  
  834. ),  
  835.  
  836.  
  837. // Data formats 
  838.  
  839. // ISO - data - International Standards Organization (ISO) CD-ROM Image 
  840. 'iso' => array( 
  841. 'pattern' => '^.{32769}CD001',  
  842. 'group' => 'misc',  
  843. 'module' => 'iso',  
  844. 'mime_type' => 'application/octet-stream',  
  845. 'fail_id3' => 'ERROR',  
  846. 'fail_ape' => 'ERROR',  
  847. 'iconv_req' => false,  
  848. ),  
  849.  
  850. // RAR - data - RAR compressed data 
  851. 'rar' => array( 
  852. 'pattern' => '^Rar\!',  
  853. 'group' => 'archive',  
  854. 'module' => 'rar',  
  855. 'mime_type' => 'application/octet-stream',  
  856. 'fail_id3' => 'ERROR',  
  857. 'fail_ape' => 'ERROR',  
  858. ),  
  859.  
  860. // SZIP - audio/data - SZIP compressed data 
  861. 'szip' => array( 
  862. 'pattern' => '^SZ\x0A\x04',  
  863. 'group' => 'archive',  
  864. 'module' => 'szip',  
  865. 'mime_type' => 'application/octet-stream',  
  866. 'fail_id3' => 'ERROR',  
  867. 'fail_ape' => 'ERROR',  
  868. ),  
  869.  
  870. // TAR - data - TAR compressed data 
  871. 'tar' => array( 
  872. 'pattern' => '^.{100}[0-9\x20]{7}\x00[0-9\x20]{7}\x00[0-9\x20]{7}\x00[0-9\x20\x00]{12}[0-9\x20\x00]{12}',  
  873. 'group' => 'archive',  
  874. 'module' => 'tar',  
  875. 'mime_type' => 'application/x-tar',  
  876. 'fail_id3' => 'ERROR',  
  877. 'fail_ape' => 'ERROR',  
  878. ),  
  879.  
  880. // GZIP - data - GZIP compressed data 
  881. 'gz' => array( 
  882. 'pattern' => '^\x1F\x8B\x08',  
  883. 'group' => 'archive',  
  884. 'module' => 'gzip',  
  885. 'mime_type' => 'application/x-gzip',  
  886. 'fail_id3' => 'ERROR',  
  887. 'fail_ape' => 'ERROR',  
  888. ),  
  889.  
  890. // ZIP - data - ZIP compressed data 
  891. 'zip' => array( 
  892. 'pattern' => '^PK\x03\x04',  
  893. 'group' => 'archive',  
  894. 'module' => 'zip',  
  895. 'mime_type' => 'application/zip',  
  896. 'fail_id3' => 'ERROR',  
  897. 'fail_ape' => 'ERROR',  
  898. ),  
  899.  
  900.  
  901. // Misc other formats 
  902.  
  903. // PAR2 - data - Parity Volume Set Specification 2.0 
  904. 'par2' => array ( 
  905. 'pattern' => '^PAR2\x00PKT',  
  906. 'group' => 'misc',  
  907. 'module' => 'par2',  
  908. 'mime_type' => 'application/octet-stream',  
  909. 'fail_id3' => 'ERROR',  
  910. 'fail_ape' => 'ERROR',  
  911. ),  
  912.  
  913. // PDF - data - Portable Document Format 
  914. 'pdf' => array( 
  915. 'pattern' => '^\x25PDF',  
  916. 'group' => 'misc',  
  917. 'module' => 'pdf',  
  918. 'mime_type' => 'application/pdf',  
  919. 'fail_id3' => 'ERROR',  
  920. 'fail_ape' => 'ERROR',  
  921. ),  
  922.  
  923. // MSOFFICE - data - ZIP compressed data 
  924. 'msoffice' => array( 
  925. 'pattern' => '^\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1', // D0CF11E == DOCFILE == Microsoft Office Document 
  926. 'group' => 'misc',  
  927. 'module' => 'msoffice',  
  928. 'mime_type' => 'application/octet-stream',  
  929. 'fail_id3' => 'ERROR',  
  930. 'fail_ape' => 'ERROR',  
  931. ),  
  932.  
  933. // CUE - data - CUEsheet (index to single-file disc images) 
  934. 'cue' => array( 
  935. 'pattern' => '', // empty pattern means cannot be automatically detected, will fall through all other formats and match based on filename and very basic file contents 
  936. 'group' => 'misc',  
  937. 'module' => 'cue',  
  938. 'mime_type' => 'application/octet-stream',  
  939. ),  
  940.  
  941. ); 
  942.  
  943. return $format_info; 
  944.  
  945.  
  946.  
  947. public function GetFileFormat(&$filedata, $filename='') { 
  948. // this function will determine the format of a file based on usually 
  949. // the first 2-4 bytes of the file (8 bytes for PNG, 16 bytes for JPG,  
  950. // and in the case of ISO CD image, 6 bytes offset 32kb from the start 
  951. // of the file). 
  952.  
  953. // Identify file format - loop through $format_info and detect with reg expr 
  954. foreach ($this->GetFileFormatArray() as $format_name => $info) { 
  955. // The /s switch on preg_match() forces preg_match() NOT to treat 
  956. // newline (0x0A) characters as special chars but do a binary match 
  957. if (!empty($info['pattern']) && preg_match('#'.$info['pattern'].'#s', $filedata)) { 
  958. $info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php'; 
  959. return $info; 
  960.  
  961.  
  962. if (preg_match('#\.mp[123a]$#i', $filename)) { 
  963. // Too many mp3 encoders on the market put gabage in front of mpeg files 
  964. // use assume format on these if format detection failed 
  965. $GetFileFormatArray = $this->GetFileFormatArray(); 
  966. $info = $GetFileFormatArray['mp3']; 
  967. $info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php'; 
  968. return $info; 
  969. } elseif (preg_match('/\.cue$/i', $filename) && preg_match('#FILE "[^"]+" (BINARY|MOTOROLA|AIFF|WAVE|MP3)#', $filedata)) { 
  970. // there's not really a useful consistent "magic" at the beginning of .cue files to identify them 
  971. // so until I think of something better, just go by filename if all other format checks fail 
  972. // and verify there's at least one instance of "TRACK xx AUDIO" in the file 
  973. $GetFileFormatArray = $this->GetFileFormatArray(); 
  974. $info = $GetFileFormatArray['cue']; 
  975. $info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php'; 
  976. return $info; 
  977.  
  978. return false; 
  979.  
  980.  
  981. // converts array to $encoding charset from $this->encoding 
  982. public function CharConvert(&$array, $encoding) { 
  983.  
  984. // identical encoding - end here 
  985. if ($encoding == $this->encoding) { 
  986. return; 
  987.  
  988. // loop thru array 
  989. foreach ($array as $key => $value) { 
  990.  
  991. // go recursive 
  992. if (is_array($value)) { 
  993. $this->CharConvert($array[$key], $encoding); 
  994.  
  995. // convert string 
  996. elseif (is_string($value)) { 
  997. $array[$key] = trim(getid3_lib::iconv_fallback($encoding, $this->encoding, $value)); 
  998.  
  999.  
  1000. public function HandleAllTags() { 
  1001.  
  1002. // key name => array (tag name, character encoding) 
  1003. static $tags; 
  1004. if (empty($tags)) { 
  1005. $tags = array( 
  1006. 'asf' => array('asf' , 'UTF-16LE'),  
  1007. 'midi' => array('midi' , 'ISO-8859-1'),  
  1008. 'nsv' => array('nsv' , 'ISO-8859-1'),  
  1009. 'ogg' => array('vorbiscomment' , 'UTF-8'),  
  1010. 'png' => array('png' , 'UTF-8'),  
  1011. 'tiff' => array('tiff' , 'ISO-8859-1'),  
  1012. 'quicktime' => array('quicktime' , 'UTF-8'),  
  1013. 'real' => array('real' , 'ISO-8859-1'),  
  1014. 'vqf' => array('vqf' , 'ISO-8859-1'),  
  1015. 'zip' => array('zip' , 'ISO-8859-1'),  
  1016. 'riff' => array('riff' , 'ISO-8859-1'),  
  1017. 'lyrics3' => array('lyrics3' , 'ISO-8859-1'),  
  1018. 'id3v1' => array('id3v1' , $this->encoding_id3v1),  
  1019. 'id3v2' => array('id3v2' , 'UTF-8'), // not according to the specs (every frame can have a different encoding), but getID3() force-converts all encodings to UTF-8 
  1020. 'ape' => array('ape' , 'UTF-8'),  
  1021. 'cue' => array('cue' , 'ISO-8859-1'),  
  1022. 'matroska' => array('matroska' , 'UTF-8'),  
  1023. 'flac' => array('vorbiscomment' , 'UTF-8'),  
  1024. 'divxtag' => array('divx' , 'ISO-8859-1'),  
  1025. 'iptc' => array('iptc' , 'ISO-8859-1'),  
  1026. ); 
  1027.  
  1028. // loop through comments array 
  1029. foreach ($tags as $comment_name => $tagname_encoding_array) { 
  1030. list($tag_name, $encoding) = $tagname_encoding_array; 
  1031.  
  1032. // fill in default encoding type if not already present 
  1033. if (isset($this->info[$comment_name]) && !isset($this->info[$comment_name]['encoding'])) { 
  1034. $this->info[$comment_name]['encoding'] = $encoding; 
  1035.  
  1036. // copy comments if key name set 
  1037. if (!empty($this->info[$comment_name]['comments'])) { 
  1038. foreach ($this->info[$comment_name]['comments'] as $tag_key => $valuearray) { 
  1039. foreach ($valuearray as $key => $value) { 
  1040. if (is_string($value)) { 
  1041. $value = trim($value, " \r\n\t"); // do not trim nulls from $value!! Unicode characters will get mangled if trailing nulls are removed! 
  1042. if ($value) { 
  1043. if (!is_numeric($key)) { 
  1044. $this->info['tags'][trim($tag_name)][trim($tag_key)][$key] = $value; 
  1045. } else { 
  1046. $this->info['tags'][trim($tag_name)][trim($tag_key)][] = $value; 
  1047. if ($tag_key == 'picture') { 
  1048. unset($this->info[$comment_name]['comments'][$tag_key]); 
  1049.  
  1050. if (!isset($this->info['tags'][$tag_name])) { 
  1051. // comments are set but contain nothing but empty strings, so skip 
  1052. continue; 
  1053.  
  1054. if ($this->option_tags_html) { 
  1055. foreach ($this->info['tags'][$tag_name] as $tag_key => $valuearray) { 
  1056. $this->info['tags_html'][$tag_name][$tag_key] = getid3_lib::recursiveMultiByteCharString2HTML($valuearray, $encoding); 
  1057.  
  1058. $this->CharConvert($this->info['tags'][$tag_name], $encoding); // only copy gets converted! 
  1059.  
  1060.  
  1061. // pictures can take up a lot of space, and we don't need multiple copies of them 
  1062. // let there be a single copy in [comments][picture], and not elsewhere 
  1063. if (!empty($this->info['tags'])) { 
  1064. $unset_keys = array('tags', 'tags_html'); 
  1065. foreach ($this->info['tags'] as $tagtype => $tagarray) { 
  1066. foreach ($tagarray as $tagname => $tagdata) { 
  1067. if ($tagname == 'picture') { 
  1068. foreach ($tagdata as $key => $tagarray) { 
  1069. $this->info['comments']['picture'][] = $tagarray; 
  1070. if (isset($tagarray['data']) && isset($tagarray['image_mime'])) { 
  1071. if (isset($this->info['tags'][$tagtype][$tagname][$key])) { 
  1072. unset($this->info['tags'][$tagtype][$tagname][$key]); 
  1073. if (isset($this->info['tags_html'][$tagtype][$tagname][$key])) { 
  1074. unset($this->info['tags_html'][$tagtype][$tagname][$key]); 
  1075. foreach ($unset_keys as $unset_key) { 
  1076. // remove possible empty keys from (e.g. [tags][id3v2][picture]) 
  1077. if (empty($this->info[$unset_key][$tagtype]['picture'])) { 
  1078. unset($this->info[$unset_key][$tagtype]['picture']); 
  1079. if (empty($this->info[$unset_key][$tagtype])) { 
  1080. unset($this->info[$unset_key][$tagtype]); 
  1081. if (empty($this->info[$unset_key])) { 
  1082. unset($this->info[$unset_key]); 
  1083. // remove duplicate copy of picture data from (e.g. [id3v2][comments][picture]) 
  1084. if (isset($this->info[$tagtype]['comments']['picture'])) { 
  1085. unset($this->info[$tagtype]['comments']['picture']); 
  1086. if (empty($this->info[$tagtype]['comments'])) { 
  1087. unset($this->info[$tagtype]['comments']); 
  1088. if (empty($this->info[$tagtype])) { 
  1089. unset($this->info[$tagtype]); 
  1090. return true; 
  1091.  
  1092. public function getHashdata($algorithm) { 
  1093. switch ($algorithm) { 
  1094. case 'md5': 
  1095. case 'sha1': 
  1096. break; 
  1097.  
  1098. default: 
  1099. return $this->error('bad algorithm "'.$algorithm.'" in getHashdata()'); 
  1100. break; 
  1101.  
  1102. if (!empty($this->info['fileformat']) && !empty($this->info['dataformat']) && ($this->info['fileformat'] == 'ogg') && ($this->info['audio']['dataformat'] == 'vorbis')) { 
  1103.  
  1104. // We cannot get an identical md5_data value for Ogg files where the comments 
  1105. // span more than 1 Ogg page (compared to the same audio data with smaller 
  1106. // comments) using the normal getID3() method of MD5'ing the data between the 
  1107. // end of the comments and the end of the file (minus any trailing tags),  
  1108. // because the page sequence numbers of the pages that the audio data is on 
  1109. // do not match. Under normal circumstances, where comments are smaller than 
  1110. // the nominal 4-8kB page size, then this is not a problem, but if there are 
  1111. // very large comments, the only way around it is to strip off the comment 
  1112. // tags with vorbiscomment and MD5 that file. 
  1113. // This procedure must be applied to ALL Ogg files, not just the ones with 
  1114. // comments larger than 1 page, because the below method simply MD5's the 
  1115. // whole file with the comments stripped, not just the portion after the 
  1116. // comments block (which is the standard getID3() method. 
  1117.  
  1118. // The above-mentioned problem of comments spanning multiple pages and changing 
  1119. // page sequence numbers likely happens for OggSpeex and OggFLAC as well, but 
  1120. // currently vorbiscomment only works on OggVorbis files. 
  1121.  
  1122. if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) { 
  1123.  
  1124. $this->warning('Failed making system call to vorbiscomment.exe - '.$algorithm.'_data is incorrect - error returned: PHP running in Safe Mode (backtick operator not available)'); 
  1125. $this->info[$algorithm.'_data'] = false; 
  1126.  
  1127. } else { 
  1128.  
  1129. // Prevent user from aborting script 
  1130. $old_abort = ignore_user_abort(true); 
  1131.  
  1132. // Create empty file 
  1133. $empty = tempnam(GETID3_TEMP_DIR, 'getID3'); 
  1134. touch($empty); 
  1135.  
  1136. // Use vorbiscomment to make temp file without comments 
  1137. $temp = tempnam(GETID3_TEMP_DIR, 'getID3'); 
  1138. $file = $this->info['filenamepath']; 
  1139.  
  1140. if (GETID3_OS_ISWINDOWS) { 
  1141.  
  1142. if (file_exists(GETID3_HELPERAPPSDIR.'vorbiscomment.exe')) { 
  1143.  
  1144. $commandline = '"'.GETID3_HELPERAPPSDIR.'vorbiscomment.exe" -w -c "'.$empty.'" "'.$file.'" "'.$temp.'"'; 
  1145. $VorbisCommentError = `$commandline`; 
  1146.  
  1147. } else { 
  1148.  
  1149. $VorbisCommentError = 'vorbiscomment.exe not found in '.GETID3_HELPERAPPSDIR; 
  1150.  
  1151.  
  1152. } else { 
  1153.  
  1154. $commandline = 'vorbiscomment -w -c "'.$empty.'" "'.$file.'" "'.$temp.'" 2>&1'; 
  1155. $commandline = 'vorbiscomment -w -c '.escapeshellarg($empty).' '.escapeshellarg($file).' '.escapeshellarg($temp).' 2>&1'; 
  1156. $VorbisCommentError = `$commandline`; 
  1157.  
  1158.  
  1159. if (!empty($VorbisCommentError)) { 
  1160.  
  1161. $this->info['warning'][] = 'Failed making system call to vorbiscomment(.exe) - '.$algorithm.'_data will be incorrect. If vorbiscomment is unavailable, please download from http://www.vorbis.com/download.psp and put in the getID3() directory. Error returned: '.$VorbisCommentError; 
  1162. $this->info[$algorithm.'_data'] = false; 
  1163.  
  1164. } else { 
  1165.  
  1166. // Get hash of newly created file 
  1167. switch ($algorithm) { 
  1168. case 'md5': 
  1169. $this->info[$algorithm.'_data'] = md5_file($temp); 
  1170. break; 
  1171.  
  1172. case 'sha1': 
  1173. $this->info[$algorithm.'_data'] = sha1_file($temp); 
  1174. break; 
  1175.  
  1176. // Clean up 
  1177. unlink($empty); 
  1178. unlink($temp); 
  1179.  
  1180. // Reset abort setting 
  1181. ignore_user_abort($old_abort); 
  1182.  
  1183.  
  1184. } else { 
  1185.  
  1186. if (!empty($this->info['avdataoffset']) || (isset($this->info['avdataend']) && ($this->info['avdataend'] < $this->info['filesize']))) { 
  1187.  
  1188. // get hash from part of file 
  1189. $this->info[$algorithm.'_data'] = getid3_lib::hash_data($this->info['filenamepath'], $this->info['avdataoffset'], $this->info['avdataend'], $algorithm); 
  1190.  
  1191. } else { 
  1192.  
  1193. // get hash from whole file 
  1194. switch ($algorithm) { 
  1195. case 'md5': 
  1196. $this->info[$algorithm.'_data'] = md5_file($this->info['filenamepath']); 
  1197. break; 
  1198.  
  1199. case 'sha1': 
  1200. $this->info[$algorithm.'_data'] = sha1_file($this->info['filenamepath']); 
  1201. break; 
  1202.  
  1203. return true; 
  1204.  
  1205.  
  1206. public function ChannelsBitratePlaytimeCalculations() { 
  1207.  
  1208. // set channelmode on audio 
  1209. if (!empty($this->info['audio']['channelmode']) || !isset($this->info['audio']['channels'])) { 
  1210. // ignore 
  1211. } elseif ($this->info['audio']['channels'] == 1) { 
  1212. $this->info['audio']['channelmode'] = 'mono'; 
  1213. } elseif ($this->info['audio']['channels'] == 2) { 
  1214. $this->info['audio']['channelmode'] = 'stereo'; 
  1215.  
  1216. // Calculate combined bitrate - audio + video 
  1217. $CombinedBitrate = 0; 
  1218. $CombinedBitrate += (isset($this->info['audio']['bitrate']) ? $this->info['audio']['bitrate'] : 0); 
  1219. $CombinedBitrate += (isset($this->info['video']['bitrate']) ? $this->info['video']['bitrate'] : 0); 
  1220. if (($CombinedBitrate > 0) && empty($this->info['bitrate'])) { 
  1221. $this->info['bitrate'] = $CombinedBitrate; 
  1222. //if ((isset($this->info['video']) && !isset($this->info['video']['bitrate'])) || (isset($this->info['audio']) && !isset($this->info['audio']['bitrate']))) { 
  1223. // // for example, VBR MPEG video files cannot determine video bitrate: 
  1224. // // should not set overall bitrate and playtime from audio bitrate only 
  1225. // unset($this->info['bitrate']); 
  1226. //} 
  1227.  
  1228. // video bitrate undetermined, but calculable 
  1229. if (isset($this->info['video']['dataformat']) && $this->info['video']['dataformat'] && (!isset($this->info['video']['bitrate']) || ($this->info['video']['bitrate'] == 0))) { 
  1230. // if video bitrate not set 
  1231. if (isset($this->info['audio']['bitrate']) && ($this->info['audio']['bitrate'] > 0) && ($this->info['audio']['bitrate'] == $this->info['bitrate'])) { 
  1232. // AND if audio bitrate is set to same as overall bitrate 
  1233. if (isset($this->info['playtime_seconds']) && ($this->info['playtime_seconds'] > 0)) { 
  1234. // AND if playtime is set 
  1235. if (isset($this->info['avdataend']) && isset($this->info['avdataoffset'])) { 
  1236. // AND if AV data offset start/end is known 
  1237. // THEN we can calculate the video bitrate 
  1238. $this->info['bitrate'] = round((($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['playtime_seconds']); 
  1239. $this->info['video']['bitrate'] = $this->info['bitrate'] - $this->info['audio']['bitrate']; 
  1240.  
  1241. if ((!isset($this->info['playtime_seconds']) || ($this->info['playtime_seconds'] <= 0)) && !empty($this->info['bitrate'])) { 
  1242. $this->info['playtime_seconds'] = (($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['bitrate']; 
  1243.  
  1244. if (!isset($this->info['bitrate']) && !empty($this->info['playtime_seconds'])) { 
  1245. $this->info['bitrate'] = (($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['playtime_seconds']; 
  1246. if (isset($this->info['bitrate']) && empty($this->info['audio']['bitrate']) && empty($this->info['video']['bitrate'])) { 
  1247. if (isset($this->info['audio']['dataformat']) && empty($this->info['video']['resolution_x'])) { 
  1248. // audio only 
  1249. $this->info['audio']['bitrate'] = $this->info['bitrate']; 
  1250. } elseif (isset($this->info['video']['resolution_x']) && empty($this->info['audio']['dataformat'])) { 
  1251. // video only 
  1252. $this->info['video']['bitrate'] = $this->info['bitrate']; 
  1253.  
  1254. // Set playtime string 
  1255. if (!empty($this->info['playtime_seconds']) && empty($this->info['playtime_string'])) { 
  1256. $this->info['playtime_string'] = getid3_lib::PlaytimeString($this->info['playtime_seconds']); 
  1257.  
  1258.  
  1259. public function CalculateCompressionRatioVideo() { 
  1260. if (empty($this->info['video'])) { 
  1261. return false; 
  1262. if (empty($this->info['video']['resolution_x']) || empty($this->info['video']['resolution_y'])) { 
  1263. return false; 
  1264. if (empty($this->info['video']['bits_per_sample'])) { 
  1265. return false; 
  1266.  
  1267. switch ($this->info['video']['dataformat']) { 
  1268. case 'bmp': 
  1269. case 'gif': 
  1270. case 'jpeg': 
  1271. case 'jpg': 
  1272. case 'png': 
  1273. case 'tiff': 
  1274. $FrameRate = 1; 
  1275. $PlaytimeSeconds = 1; 
  1276. $BitrateCompressed = $this->info['filesize'] * 8; 
  1277. break; 
  1278.  
  1279. default: 
  1280. if (!empty($this->info['video']['frame_rate'])) { 
  1281. $FrameRate = $this->info['video']['frame_rate']; 
  1282. } else { 
  1283. return false; 
  1284. if (!empty($this->info['playtime_seconds'])) { 
  1285. $PlaytimeSeconds = $this->info['playtime_seconds']; 
  1286. } else { 
  1287. return false; 
  1288. if (!empty($this->info['video']['bitrate'])) { 
  1289. $BitrateCompressed = $this->info['video']['bitrate']; 
  1290. } else { 
  1291. return false; 
  1292. break; 
  1293. $BitrateUncompressed = $this->info['video']['resolution_x'] * $this->info['video']['resolution_y'] * $this->info['video']['bits_per_sample'] * $FrameRate; 
  1294.  
  1295. $this->info['video']['compression_ratio'] = $BitrateCompressed / $BitrateUncompressed; 
  1296. return true; 
  1297.  
  1298.  
  1299. public function CalculateCompressionRatioAudio() { 
  1300. if (empty($this->info['audio']['bitrate']) || empty($this->info['audio']['channels']) || empty($this->info['audio']['sample_rate']) || !is_numeric($this->info['audio']['sample_rate'])) { 
  1301. return false; 
  1302. $this->info['audio']['compression_ratio'] = $this->info['audio']['bitrate'] / ($this->info['audio']['channels'] * $this->info['audio']['sample_rate'] * (!empty($this->info['audio']['bits_per_sample']) ? $this->info['audio']['bits_per_sample'] : 16)); 
  1303.  
  1304. if (!empty($this->info['audio']['streams'])) { 
  1305. foreach ($this->info['audio']['streams'] as $streamnumber => $streamdata) { 
  1306. if (!empty($streamdata['bitrate']) && !empty($streamdata['channels']) && !empty($streamdata['sample_rate'])) { 
  1307. $this->info['audio']['streams'][$streamnumber]['compression_ratio'] = $streamdata['bitrate'] / ($streamdata['channels'] * $streamdata['sample_rate'] * (!empty($streamdata['bits_per_sample']) ? $streamdata['bits_per_sample'] : 16)); 
  1308. return true; 
  1309.  
  1310.  
  1311. public function CalculateReplayGain() { 
  1312. if (isset($this->info['replay_gain'])) { 
  1313. if (!isset($this->info['replay_gain']['reference_volume'])) { 
  1314. $this->info['replay_gain']['reference_volume'] = (double) 89.0; 
  1315. if (isset($this->info['replay_gain']['track']['adjustment'])) { 
  1316. $this->info['replay_gain']['track']['volume'] = $this->info['replay_gain']['reference_volume'] - $this->info['replay_gain']['track']['adjustment']; 
  1317. if (isset($this->info['replay_gain']['album']['adjustment'])) { 
  1318. $this->info['replay_gain']['album']['volume'] = $this->info['replay_gain']['reference_volume'] - $this->info['replay_gain']['album']['adjustment']; 
  1319.  
  1320. if (isset($this->info['replay_gain']['track']['peak'])) { 
  1321. $this->info['replay_gain']['track']['max_noclip_gain'] = 0 - getid3_lib::RGADamplitude2dB($this->info['replay_gain']['track']['peak']); 
  1322. if (isset($this->info['replay_gain']['album']['peak'])) { 
  1323. $this->info['replay_gain']['album']['max_noclip_gain'] = 0 - getid3_lib::RGADamplitude2dB($this->info['replay_gain']['album']['peak']); 
  1324. return true; 
  1325.  
  1326. public function ProcessAudioStreams() { 
  1327. if (!empty($this->info['audio']['bitrate']) || !empty($this->info['audio']['channels']) || !empty($this->info['audio']['sample_rate'])) { 
  1328. if (!isset($this->info['audio']['streams'])) { 
  1329. foreach ($this->info['audio'] as $key => $value) { 
  1330. if ($key != 'streams') { 
  1331. $this->info['audio']['streams'][0][$key] = $value; 
  1332. return true; 
  1333.  
  1334. public function getid3_tempnam() { 
  1335. return tempnam($this->tempdir, 'gI3'); 
  1336.  
  1337. public function include_module($name) { 
  1338. //if (!file_exists($this->include_path.'module.'.$name.'.php')) { 
  1339. if (!file_exists(GETID3_INCLUDEPATH.'module.'.$name.'.php')) { 
  1340. throw new getid3_exception('Required module.'.$name.'.php is missing.'); 
  1341. include_once(GETID3_INCLUDEPATH.'module.'.$name.'.php'); 
  1342. return true; 
  1343.