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