getid3.php 79 KB

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