/wp-includes/ID3/getid3.php

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