getid3.php 69 KB

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