getid3::__construct()   B
last analyzed

Complexity

Conditions 10
Paths 35

Size

Total Lines 53
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 10
eloc 26
c 1
b 0
f 0
nc 35
nop 0
dl 0
loc 53
rs 7.6666

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
// +----------------------------------------------------------------------+
3
// | PHP version 5                                                        |
4
// +----------------------------------------------------------------------+
5
// | Copyright (c) 2002-2006 James Heinrich, Allan Hansen                 |
6
// +----------------------------------------------------------------------+
7
// | This source file is subject to version 2 of the GPL license,         |
8
// | that is bundled with this package in the file license.txt and is     |
9
// | available through the world-wide-web at the following url:           |
10
// | http://www.gnu.org/copyleft/gpl.html                                 |
11
// +----------------------------------------------------------------------+
12
// | getID3() - http://getid3.sourceforge.net or http://www.getid3.org    |
13
// +----------------------------------------------------------------------+
14
// | Authors: James Heinrich <info�getid3*org>                            |
15
// |          Allan Hansen <ah�artemis*dk>                                |
16
// +----------------------------------------------------------------------+
17
// | getid3.php                                                           |
18
// | Main getID3() file.                                                  |
19
// | dependencies: modules.                                               |
20
// +----------------------------------------------------------------------+
21
//
22
// $Id: getid3.php,v 1.26 2006/12/25 23:44:23 ah Exp $
23
24
class getid3
25
{
26
    //// Settings Section - do NOT modify this file - change setting after newing getid3!
27
28
    // Encoding
29
    public $encoding       = 'ISO-8859-1';      // CASE SENSITIVE! - i.e. (must be supported by iconv() - see http://www.gnu.org/software/libiconv/).  Examples:  ISO-8859-1  UTF-8  UTF-16  UTF-16BE.
30
    public $encoding_id3v1 = 'ISO-8859-1';      // Override SPECIFICATION encoding for broken ID3v1 tags caused by bad tag programs. Examples: 'EUC-CN' for "Chinese MP3s" and 'CP1251' for "Cyrillic".
31
    public $encoding_id3v2 = 'ISO-8859-1';      // Override ISO-8859-1 encoding for broken ID3v2 tags caused by BRAINDEAD tag programs that writes system codepage as 'ISO-8859-1' instead of UTF-8.
32
33
    // Tags - disable for speed
34
    public $option_tag_id3v1   = true;              // Read and process ID3v1 tags.
35
    public $option_tag_id3v2   = true;              // Read and process ID3v2 tags.
36
    public $option_tag_lyrics3 = true;              // Read and process Lyrics3 tags.
37
    public $option_tag_apetag  = true;              // Read and process APE tags.
38
39
    // Misc calucations - disable for speed
40
    public $option_analyze          = true;              // Analyze file - disable if you only need to detect file format.
41
    public $option_accurate_results = true;              // Disable to greatly speed up parsing of some file formats at the cost of accuracy.
42
    public $option_tags_process     = true;              // Copy tags to root key 'tags' and 'comments' and encode to $this->encoding.
43
    public $option_tags_images      = false;             // Scan tags for binary image data - ID3v2 and vorbiscomments only.
44
    public $option_extra_info       = true;              // Calculate/return additional info such as bitrate, channelmode etc.
45
    public $option_max_2gb_check    = false;             // Check whether file is larger than 2 Gb and thus not supported by PHP.
46
47
    // Misc data hashes - slow - require hash module
48
    public $option_md5_data        = false;             // Get MD5 sum of data part - slow.
49
    public $option_md5_data_source = false;             // Use MD5 of source file if available - only FLAC, MAC, OptimFROG and Wavpack4.
50
    public $option_sha1_data       = false;             // Get SHA1 sum of data part - slow.
51
52
    // Public variables
53
    public $filename;                                     // Filename of file being analysed.
54
    public $fp;                                           // Filepointer to file being analysed.
55
    public $info;                                         // Result array.
56
57
    // Protected variables
58
    protected $include_path;                              // getid3 include path.
59
    protected $warnings = [];
60
    protected $iconv_present;
61
62
    // Class constants
63
    const VERSION = '2.0.0b4';
64
    const FREAD_BUFFER_SIZE = 16384;                      // Read buffer size in bytes.
65
    const ICONV_TEST_STRING = ' !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~�������������������������������� �����������������������������������������������������������������������������������������������';
66
67
    // Constructor - check PHP enviroment and load library.
68
    public function __construct()
69
    {
70
        // Static varibles - no need to recalc every time we new getid3.
71
        static $include_path;
72
        static $iconv_present;
73
74
        static $initialized;
75
        if ($initialized) {
76
            // Import static variables
77
            $this->include_path  = $include_path;
78
            $this->iconv_present = $iconv_present;
79
80
            // Run init checks only on first instance.
81
            return;
82
        }
83
84
        // Get include_path
85
        $this->include_path = $include_path = dirname(__FILE__) . '/';
86
87
        // Check for presence of iconv() and make sure it works (simpel test only).
88
        if (function_exists('iconv') && @iconv('UTF-16LE', 'ISO-8859-1', @iconv('ISO-8859-1', 'UTF-16LE', getid3::ICONV_TEST_STRING)) == getid3::ICONV_TEST_STRING) {
0 ignored issues
show
Bug introduced by
It seems like @iconv('ISO-8859-1', 'UT...id3::ICONV_TEST_STRING) can also be of type false; however, parameter $string of iconv() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

88
        if (function_exists('iconv') && @iconv('UTF-16LE', 'ISO-8859-1', /** @scrutinizer ignore-type */ @iconv('ISO-8859-1', 'UTF-16LE', getid3::ICONV_TEST_STRING)) == getid3::ICONV_TEST_STRING) {
Loading history...
89
            $this->iconv_present = $iconv_present = true;
90
        } // iconv() not present - load replacement module.
91
        else {
92
            $this->include_module('lib.iconv_replacement');
93
            $this->iconv_present = $iconv_present = false;
94
        }
95
96
        // Require magic_quotes_runtime off
97
        if (get_magic_quotes_runtime()) {
98
            throw new getid3_exception('magic_quotes_runtime must be disabled before running getID3(). Surround getid3 block by set_magic_quotes_runtime(0) and set_magic_quotes_runtime(1).');
99
        }
100
101
        // Check memory limit.
102
        $memory_limit = ini_get('memory_limit');
103
        if (eregi('([0-9]+)M', $memory_limit, $matches)) {
104
            // could be stored as "16M" rather than 16777216 for example
105
            $memory_limit = $matches[1] * 1048576;
106
        }
107
        if ($memory_limit <= 0) {
108
            // Should not happen.
109
        } elseif ($memory_limit <= 4194304) {
110
            $this->warning('[SERIOUS] PHP has less than 4 Mb available memory and will very likely run out. Increase memory_limit in php.ini.');
111
        } elseif ($memory_limit <= 12582912) {
112
            $this->warning('PHP has less than 12 Mb available memory and might run out if all modules are loaded. Increase memory_limit in php.ini if needed.');
113
        }
114
115
        // Check safe_mode off
116
        if ((bool)ini_get('safe_mode')) {
117
            $this->warning('Safe mode is on, shorten support disabled, md5data/sha1data for ogg vorbis disabled, ogg vorbis/flac tag writing disabled.');
118
        }
119
120
        $initialized = true;
121
    }
122
123
    // Analyze file by name
124
    public function Analyze($filename)
125
    {
126
        // Init and save values
127
        $this->filename = $filename;
128
        $this->warnings = [];
129
130
        // Init result array and set parameters
131
        $this->info                   = [];
132
        $this->info['GETID3_VERSION'] = getid3::VERSION;
133
134
        // Remote files not supported
135
        if (preg_match('/^(ht|f)tp:\/\//', $filename)) {
136
            throw new getid3_exception('Remote files are not supported - please copy the file locally first.');
137
        }
138
139
        // Open local file
140
        if (!$this->fp = @fopen($filename, 'rb')) {
141
            throw new getid3_exception('Could not open file "' . $filename . '"');
142
        }
143
144
        // Set filesize related parameters
145
        $this->info['filesize']     = filesize($filename);
146
        $this->info['avdataoffset'] = 0;
147
        $this->info['avdataend']    = $this->info['filesize'];
148
149
        // Option_max_2gb_check
150
        if ($this->option_max_2gb_check) {
151
            // PHP doesn't support integers larger than 31-bit (~2GB)
152
            // filesize() simply returns (filesize % (pow(2, 32)), no matter the actual filesize
153
            // ftell() returns 0 if seeking to the end is beyond the range of unsigned integer
154
            fseek($this->fp, 0, SEEK_END);
155
            if (((0 != $this->info['filesize']) && (0 == ftell($this->fp)))
156
                || ($this->info['filesize'] < 0)
157
                || (ftell($this->fp) < 0)) {
158
                unset($this->info['filesize']);
159
                fclose($this->fp);
160
                throw new getid3_exception('File is most likely larger than 2GB and is not supported by PHP.');
161
            }
162
        }
163
164
        // ID3v2 detection (NOT parsing) done to make fileformat easier.
165
        if (!$this->option_tag_id3v2) {
166
            fseek($this->fp, 0, SEEK_SET);
167
            $header = fread($this->fp, 10);
168
            if ('ID3' == substr($header, 0, 3) && 10 == strlen($header)) {
169
                $this->info['id3v2']['header']       = true;
170
                $this->info['id3v2']['majorversion'] = ord($header{3});
171
                $this->info['id3v2']['minorversion'] = ord($header{4});
172
                $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
173
            }
174
        }
175
176
        // Handle tags
177
        foreach (['id3v2', 'id3v1', 'apetag', 'lyrics3'] as $tag_name) {
178
            $option_tag = 'option_tag_' . $tag_name;
179
            if ($this->$option_tag) {
180
                $this->include_module('tag.' . $tag_name);
181
                try {
182
                    $tag_class = 'getid3_' . $tag_name;
183
                    $tag       = new $tag_class($this);
184
                    $tag->Analyze();
185
                } catch (getid3_exception $e) {
186
                    throw $e;
187
                }
188
            }
189
        }
190
191
        //// Determine file format by magic bytes in file header.
192
193
        // Read 32 kb file data
194
        fseek($this->fp, $this->info['avdataoffset'], SEEK_SET);
195
        $filedata = fread($this->fp, 32774);
196
197
        // Get huge FileFormatArray
198
        $file_format_array = getid3::GetFileFormatArray();
199
200
        // Identify file format - loop through $format_info and detect with reg expr
201
        foreach ($file_format_array as $name => $info) {
202
            if (preg_match('/' . $info['pattern'] . '/s', $filedata)) {                         // The /s switch on preg_match() forces preg_match() NOT to treat newline (0x0A) characters as special chars but do a binary match
203
204
                // Format detected but not supported
205
                if (!@$info['module'] || !@$info['group']) {
206
                    fclose($this->fp);
207
                    $this->info['fileformat'] = $name;
208
                    $this->info['mime_type']  = $info['mime_type'];
209
                    $this->warning('Format only detected. Parsing not available yet.');
210
                    $this->info['warning'] = $this->warnings;
211
                    return $this->info;
212
                }
213
214
                $determined_format = $info;  // copy $info deleted by foreach()
215
                continue;
216
            }
217
        }
218
219
        // Unable to determine file format
220
        if (!@$determined_format) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $determined_format does not seem to be defined for all execution paths leading up to this point.
Loading history...
221
            // Too many mp3 encoders on the market put gabage in front of mpeg files
222
            // use assume format on these if format detection failed
223
            if (preg_match('/\.mp[123a]$/i', $filename)) {
224
                $determined_format = $file_format_array['mp3'];
225
            } else {
226
                fclose($this->fp);
227
                throw new getid3_exception('Unable to determine file format');
228
            }
229
        }
230
231
        // Free memory
232
        unset($file_format_array);
233
234
        // Check for illegal ID3 tags
235
        if (@$determined_format['fail_id3'] && (@$this->info['id3v1'] || @$this->info['id3v2'])) {
236
            if ('ERROR' === $determined_format['fail_id3']) {
237
                fclose($this->fp);
238
                throw new getid3_exception('ID3 tags not allowed on this file type.');
239
            } elseif ('WARNING' === $determined_format['fail_id3']) {
240
                @$this->info['id3v1'] and $this->warning('ID3v1 tags not allowed on this file type.');
241
                @$this->info['id3v2'] and $this->warning('ID3v2 tags not allowed on this file type.');
242
            }
243
        }
244
245
        // Check for illegal APE tags
246
        if (@$determined_format['fail_ape'] && @$this->info['tags']['ape']) {
247
            if ('ERROR' === $determined_format['fail_ape']) {
248
                fclose($this->fp);
249
                throw new getid3_exception('APE tags not allowed on this file type.');
250
            } elseif ('WARNING' === $determined_format['fail_ape']) {
251
                $this->warning('APE tags not allowed on this file type.');
252
            }
253
        }
254
255
        // Set mime type
256
        $this->info['mime_type'] = $determined_format['mime_type'];
257
258
        // Calc module file name
259
        $determined_format['include'] = 'module.' . $determined_format['group'] . '.' . $determined_format['module'] . '.php';
260
261
        // Supported format signature pattern detected, but module deleted.
262
        if (!file_exists($this->include_path . $determined_format['include'])) {
263
            fclose($this->fp);
264
            throw new getid3_exception('Format not supported, module, ' . $determined_format['include'] . ', was removed.');
265
        }
266
267
        // Include module
268
        $this->include_module($determined_format['group'] . '.' . $determined_format['module']);
269
270
        // Instantiate module class and analyze
271
        $class_name = 'getid3_' . $determined_format['module'];
272
        if (!class_exists($class_name)) {
273
            throw new getid3_exception('Format not supported, module, ' . $determined_format['include'] . ', is corrupt.');
274
        }
275
        $class = new $class_name($this);
276
277
        try {
278
            $this->option_analyze and $class->Analyze();
279
        } catch (getid3_exception $e) {
280
            throw $e;
281
        } catch (Exception $e) {
282
            throw new getid3_exception('Corrupt file.');
283
        }
284
285
        // Close file
286
        fclose($this->fp);
287
288
        // Optional - Process all tags - copy to 'tags' and convert charsets
289
        if ($this->option_tags_process) {
290
            $this->HandleAllTags();
291
        }
292
293
        //// Optional - perform more calculations
294
        if ($this->option_extra_info) {
295
            // Set channelmode on audio
296
            if ('1' == @$this->info['audio']['channels']) {
297
                $this->info['audio']['channelmode'] = 'mono';
298
            } elseif ('2' == @$this->info['audio']['channels']) {
299
                $this->info['audio']['channelmode'] = 'stereo';
300
            }
301
302
            // Calculate combined bitrate - audio + video
303
            $combined_bitrate = 0;
304
            $combined_bitrate += (isset($this->info['audio']['bitrate']) ? $this->info['audio']['bitrate'] : 0);
305
            $combined_bitrate += (isset($this->info['video']['bitrate']) ? $this->info['video']['bitrate'] : 0);
306
            if (($combined_bitrate > 0) && empty($this->info['bitrate'])) {
307
                $this->info['bitrate'] = $combined_bitrate;
308
            }
309
            if (!isset($this->info['playtime_seconds']) && !empty($this->info['bitrate'])) {
310
                $this->info['playtime_seconds'] = (($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['bitrate'];
311
            }
312
313
            // Set playtime string
314
            if (!empty($this->info['playtime_seconds']) && empty($this->info['playtime_string'])) {
315
                $this->info['playtime_string'] = floor(round($this->info['playtime_seconds']) / 60) . ':' . str_pad(floor(round($this->info['playtime_seconds']) % 60), 2, 0, STR_PAD_LEFT);;
316
            }
317
318
            // CalculateCompressionRatioVideo() {
319
            if (@$this->info['video'] && @$this->info['video']['resolution_x'] && @$this->info['video']['resolution_y'] && @$this->info['video']['bits_per_sample']) {
320
                // From static image formats
321
                if (in_array($this->info['video']['dataformat'], ['bmp', 'gif', 'jpeg', 'jpg', 'png', 'tiff'])) {
322
                    $frame_rate         = 1;
323
                    $bitrate_compressed = $this->info['filesize'] * 8;
324
                } // From video formats
325
                else {
326
                    $frame_rate         = @$this->info['video']['frame_rate'];
327
                    $bitrate_compressed = @$this->info['video']['bitrate'];
328
                }
329
330
                if ($frame_rate && $bitrate_compressed) {
331
                    $this->info['video']['compression_ratio'] = $bitrate_compressed / ($this->info['video']['resolution_x'] * $this->info['video']['resolution_y'] * $this->info['video']['bits_per_sample'] * $frame_rate);
332
                }
333
            }
334
335
            // CalculateCompressionRatioAudio() {
336
            if (@$this->info['audio']['bitrate'] && @$this->info['audio']['channels'] && @$this->info['audio']['sample_rate']) {
337
                $this->info['audio']['compression_ratio'] = $this->info['audio']['bitrate'] / ($this->info['audio']['channels'] * $this->info['audio']['sample_rate'] * (@$this->info['audio']['bits_per_sample'] ? $this->info['audio']['bits_per_sample'] : 16));
338
            }
339
340
            if (@$this->info['audio']['streams']) {
341
                foreach ($this->info['audio']['streams'] as $stream_number => $stream_data) {
342
                    if (@$stream_data['bitrate'] && @$stream_data['channels'] && @$stream_data['sample_rate']) {
343
                        $this->info['audio']['streams'][$stream_number]['compression_ratio'] = $stream_data['bitrate'] / ($stream_data['channels'] * $stream_data['sample_rate'] * (@$stream_data['bits_per_sample'] ? $stream_data['bits_per_sample'] : 16));
344
                    }
345
                }
346
            }
347
348
            // CalculateReplayGain() {
349
            if (@$this->info['replay_gain']) {
350
                if (!@$this->info['replay_gain']['reference_volume']) {
351
                    $this->info['replay_gain']['reference_volume'] = 89;
352
                }
353
                if (isset($this->info['replay_gain']['track']['adjustment'])) {
354
                    $this->info['replay_gain']['track']['volume'] = $this->info['replay_gain']['reference_volume'] - $this->info['replay_gain']['track']['adjustment'];
355
                }
356
                if (isset($this->info['replay_gain']['album']['adjustment'])) {
357
                    $this->info['replay_gain']['album']['volume'] = $this->info['replay_gain']['reference_volume'] - $this->info['replay_gain']['album']['adjustment'];
358
                }
359
360
                if (isset($this->info['replay_gain']['track']['peak'])) {
361
                    $this->info['replay_gain']['track']['max_noclip_gain'] = 0 - 20 * log10($this->info['replay_gain']['track']['peak']);
362
                }
363
                if (isset($this->info['replay_gain']['album']['peak'])) {
364
                    $this->info['replay_gain']['album']['max_noclip_gain'] = 0 - 20 * log10($this->info['replay_gain']['album']['peak']);
365
                }
366
            }
367
368
            // ProcessAudioStreams() {
369
            if (@!$this->info['audio']['streams'] && (@$this->info['audio']['bitrate'] || @$this->info['audio']['channels'] || @$this->info['audio']['sample_rate'])) {
370
                foreach ($this->info['audio'] as $key => $value) {
371
                    if ('streams' != $key) {
372
                        $this->info['audio']['streams'][0][$key] = $value;
373
                    }
374
                }
375
            }
376
        }
377
378
        // Get the md5/sha1sum of the audio/video portion of the file - without ID3/APE/Lyrics3/etc header/footer tags.
379
        if ($this->option_md5_data || $this->option_sha1_data) {
380
            // Load data-hash library if needed
381
            $this->include_module('lib.data_hash');
382
383
            if ($this->option_sha1_data) {
384
                new getid3_lib_data_hash($this, 'sha1');
385
            }
386
387
            if ($this->option_md5_data) {
388
                // no md5_data_source or option disabled -- md5_data_source supported by FLAC, MAC, OptimFROG, Wavpack4
389
                if (!$this->option_md5_data_source || !@$this->info['md5_data_source']) {
390
                    new getid3_lib_data_hash($this, 'md5');
391
                } // copy md5_data_source to md5_data if option set to true
392
                elseif ($this->option_md5_data_source && @$this->info['md5_data_source']) {
393
                    $this->info['md5_data'] = $this->info['md5_data_source'];
394
                }
395
            }
396
        }
397
398
        // Set warnings
399
        if ($this->warnings) {
400
            $this->info['warning'] = $this->warnings;
401
        }
402
403
        // Return result
404
        return $this->info;
405
    }
406
407
    // Return array of warnings
408
    public function warnings()
409
    {
410
        return $this->warnings;
411
    }
412
413
    // Add warning(s) to $this->warnings[]
414
    public function warning($message)
415
    {
416
        if (is_array($message)) {
417
            $this->warnings = array_merge($this->warnings, $message);
418
        } else {
419
            $this->warnings[] = $message;
420
        }
421
    }
422
423
    //  Clear all warnings when cloning
424
    public function __clone()
425
    {
426
        $this->warnings = [];
427
428
        // Copy info array, otherwise it will be a reference.
429
        $temp = $this->info;
430
        unset($this->info);
431
        $this->info = $temp;
432
    }
433
434
    // Convert string between charsets -- iconv() wrapper
435
    public function iconv($in_charset, $out_charset, $string, $drop01 = false)
436
    {
437
        if ($drop01 && ("\x00" === $string || "\x01" === $string)) {
438
            return '';
439
        }
440
441
        if (!$this->iconv_present) {
442
            return getid3_iconv_replacement::iconv($in_charset, $out_charset, $string);
443
        }
444
445
        // iconv() present
446
        if ($result = @iconv($in_charset, $out_charset . '//TRANSLIT', $string)) {
447
            if ('ISO-8859-1' == $out_charset) {
448
                return rtrim($result, "\x00");
449
            }
450
            return $result;
451
        }
452
453
        $this->warning('iconv() was unable to convert the string: "' . $string . '" from ' . $in_charset . ' to ' . $out_charset);
454
        return $string;
455
    }
456
457
    public function include_module($name)
458
    {
459
        if (!file_exists($this->include_path . 'module.' . $name . '.php')) {
460
            throw new getid3_exception('Required module.' . $name . '.php is missing.');
461
        }
462
463
        include_once($this->include_path . 'module.' . $name . '.php');
464
    }
465
466
    public function include_module_optional($name)
467
    {
468
        if (!file_exists($this->include_path . 'module.' . $name . '.php')) {
469
            return;
470
        }
471
472
        include_once($this->include_path . 'module.' . $name . '.php');
473
        return true;
474
    }
475
476
    // Return array containing information about all supported formats
477
    public static function GetFileFormatArray()
478
    {
479
        static $format_info = [
480
481
            // Audio formats
482
483
            // AC-3   - audio      - Dolby AC-3 / Dolby Digital
484
            'ac3'       => [
485
                'pattern'   => '^\x0B\x77',
486
                'group'     => 'audio',
487
                'module'    => 'ac3',
488
                'mime_type' => 'audio/ac3',
489
            ],
490
491
            // AAC  - audio       - Advanced Audio Coding (AAC) - ADIF format
492
            'adif'      => [
493
                'pattern'   => '^ADIF',
494
                'group'     => 'audio',
495
                'module'    => 'aac_adif',
496
                'mime_type' => 'application/octet-stream',
497
                'fail_ape'  => 'WARNING',
498
            ],
499
500
            // AAC  - audio       - Advanced Audio Coding (AAC) - ADTS format (very similar to MP3)
501
            'adts'      => [
502
                'pattern'   => '^\xFF[\xF0-\xF1\xF8-\xF9]',
503
                'group'     => 'audio',
504
                'module'    => 'aac_adts',
505
                'mime_type' => 'application/octet-stream',
506
                'fail_ape'  => 'WARNING',
507
            ],
508
509
            // AU   - audio       - NeXT/Sun AUdio (AU)
510
            'au'        => [
511
                'pattern'   => '^\.snd',
512
                'group'     => 'audio',
513
                'module'    => 'au',
514
                'mime_type' => 'audio/basic',
515
            ],
516
517
            // AVR  - audio       - Audio Visual Research
518
            'avr'       => [
519
                'pattern'   => '^2BIT',
520
                'group'     => 'audio',
521
                'module'    => 'avr',
522
                'mime_type' => 'application/octet-stream',
523
            ],
524
525
            // BONK - audio       - Bonk v0.9+
526
            'bonk'      => [
527
                'pattern'   => '^\x00(BONK|INFO|META| ID3)',
528
                'group'     => 'audio',
529
                'module'    => 'bonk',
530
                'mime_type' => 'audio/xmms-bonk',
531
            ],
532
533
            // DTS  - audio       - Dolby Theatre System
534
            'dts'       => [
535
                'pattern'   => '^\x7F\xFE\x80\x01',
536
                'group'     => 'audio',
537
                'module'    => 'dts',
538
                'mime_type' => 'audio/dts',
539
            ],
540
541
            // FLAC - audio       - Free Lossless Audio Codec
542
            'flac'      => [
543
                'pattern'   => '^fLaC',
544
                'group'     => 'audio',
545
                'module'    => 'xiph',
546
                'mime_type' => 'audio/x-flac',
547
            ],
548
549
            // LA   - audio       - Lossless Audio (LA)
550
            'la'        => [
551
                'pattern'   => '^LA0[2-4]',
552
                'group'     => 'audio',
553
                'module'    => 'la',
554
                'mime_type' => 'application/octet-stream',
555
            ],
556
557
            // LPAC - audio       - Lossless Predictive Audio Compression (LPAC)
558
            'lpac'      => [
559
                'pattern'   => '^LPAC',
560
                'group'     => 'audio',
561
                'module'    => 'lpac',
562
                'mime_type' => 'application/octet-stream',
563
            ],
564
565
            // MIDI - audio       - MIDI (Musical Instrument Digital Interface)
566
            'midi'      => [
567
                'pattern'   => '^MThd',
568
                'group'     => 'audio',
569
                'module'    => 'midi',
570
                'mime_type' => 'audio/midi',
571
            ],
572
573
            // MAC  - audio       - Monkey's Audio Compressor
574
            'mac'       => [
575
                'pattern'   => '^MAC ',
576
                'group'     => 'audio',
577
                'module'    => 'monkey',
578
                'mime_type' => 'application/octet-stream',
579
            ],
580
581
            // MOD  - audio       - MODule (assorted sub-formats)
582
            'mod'       => [
583
                'pattern'   => '^.{1080}(M.K.|[5-9]CHN|[1-3][0-9]CH)',
584
                'mime_type' => 'audio/mod',
585
            ],
586
587
            // MOD  - audio       - MODule (Impulse Tracker)
588
            'it'        => [
589
                'pattern'   => '^IMPM',
590
                'mime_type' => 'audio/it',
591
            ],
592
593
            // MOD  - audio       - MODule (eXtended Module, various sub-formats)
594
            'xm'        => [
595
                'pattern'   => '^Extended Module',
596
                'mime_type' => 'audio/xm',
597
            ],
598
599
            // MOD  - audio       - MODule (ScreamTracker)
600
            's3m'       => [
601
                'pattern'   => '^.{44}SCRM',
602
                'mime_type' => 'audio/s3m',
603
            ],
604
605
            // MPC  - audio       - Musepack / MPEGplus SV7+
606
            'mpc'       => [
607
                'pattern'   => '^(MP\+)',
608
                'group'     => 'audio',
609
                'module'    => 'mpc',
610
                'mime_type' => 'audio/x-musepack',
611
            ],
612
613
            // MPC  - audio       - Musepack / MPEGplus SV4-6
614
            'mpc_old'   => [
615
                'pattern'   => '^([\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])',
616
                'group'     => 'audio',
617
                'module'    => 'mpc_old',
618
                'mime_type' => 'application/octet-stream',
619
            ],
620
621
            // MP3  - audio       - MPEG-audio Layer 3 (very similar to AAC-ADTS)
622
            'mp3'       => [
623
                'pattern'   => '^\xFF[\xE2-\xE7\xF2-\xF7\xFA-\xFF][\x00-\xEB]',
624
                'group'     => 'audio',
625
                'module'    => 'mp3',
626
                'mime_type' => 'audio/mpeg',
627
            ],
628
629
            // OFR  - audio       - OptimFROG
630
            'ofr'       => [
631
                'pattern'   => '^(\*RIFF|OFR)',
632
                'group'     => 'audio',
633
                'module'    => 'optimfrog',
634
                'mime_type' => 'application/octet-stream',
635
            ],
636
637
            // RKAU - audio       - RKive AUdio compressor
638
            'rkau'      => [
639
                'pattern'   => '^RKA',
640
                'group'     => 'audio',
641
                'module'    => 'rkau',
642
                'mime_type' => 'application/octet-stream',
643
            ],
644
645
            // SHN  - audio       - Shorten
646
            'shn'       => [
647
                'pattern'   => '^ajkg',
648
                'group'     => 'audio',
649
                'module'    => 'shorten',
650
                'mime_type' => 'audio/xmms-shn',
651
                'fail_id3'  => 'ERROR',
652
                'fail_ape'  => 'ERROR',
653
            ],
654
655
            // TTA  - audio       - TTA Lossless Audio Compressor (http://tta.corecodec.org)
656
            'tta'       => [
657
                'pattern'   => '^TTA',  // could also be '^TTA(\x01|\x02|\x03|2|1)'
658
                'group'     => 'audio',
659
                'module'    => 'tta',
660
                'mime_type' => 'application/octet-stream',
661
            ],
662
663
            // VOC  - audio       - Creative Voice (VOC)
664
            'voc'       => [
665
                'pattern'   => '^Creative Voice File',
666
                'group'     => 'audio',
667
                'module'    => 'voc',
668
                'mime_type' => 'audio/voc',
669
            ],
670
671
            // VQF  - audio       - transform-domain weighted interleave Vector Quantization Format (VQF)
672
            'vqf'       => [
673
                'pattern'   => '^TWIN',
674
                'group'     => 'audio',
675
                'module'    => 'vqf',
676
                'mime_type' => 'application/octet-stream',
677
            ],
678
679
            // WV  - audio        - WavPack (v4.0+)
680
            'vw'        => [
681
                'pattern'   => '^wvpk',
682
                'group'     => 'audio',
683
                'module'    => 'wavpack',
684
                'mime_type' => 'application/octet-stream',
685
            ],
686
687
            // Audio-Video formats
688
689
            // ASF  - audio/video - Advanced Streaming Format, Windows Media Video, Windows Media Audio
690
            'asf'       => [
691
                'pattern'   => '^\x30\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C',
692
                'group'     => 'audio-video',
693
                'module'    => 'asf',
694
                'mime_type' => 'video/x-ms-asf',
695
            ],
696
697
            // BINK  - audio/video - Bink / Smacker
698
            'bink'      => [
699
                'pattern'   => '^(BIK|SMK)',
700
                'mime_type' => 'application/octet-stream',
701
            ],
702
703
            // FLV  - audio/video - FLash Video
704
            'flv'       => [
705
                'pattern'   => '^FLV\x01',
706
                'group'     => 'audio-video',
707
                'module'    => 'flv',
708
                'mime_type' => 'video/x-flv',
709
            ],
710
711
            // MKAV - audio/video - Mastroka
712
            'matroska'  => [
713
                'pattern'   => '^\x1A\x45\xDF\xA3',
714
                'mime_type' => 'application/octet-stream',
715
            ],
716
717
            // MPEG - audio/video - MPEG (Moving Pictures Experts Group)
718
            'mpeg'      => [
719
                'pattern'   => '^\x00\x00\x01(\xBA|\xB3)',
720
                'group'     => 'audio-video',
721
                'module'    => 'mpeg',
722
                'mime_type' => 'video/mpeg',
723
            ],
724
725
            // NSV  - audio/video - Nullsoft Streaming Video (NSV)
726
            'nsv'       => [
727
                'pattern'   => '^NSV[sf]',
728
                'group'     => 'audio-video',
729
                'module'    => 'nsv',
730
                'mime_type' => 'application/octet-stream',
731
            ],
732
733
            // Ogg  - audio/video - Ogg (Ogg Vorbis, OggFLAC, Speex, Ogg Theora(*), Ogg Tarkin(*))
734
            'ogg'       => [
735
                'pattern'   => '^OggS',
736
                'group'     => 'audio',
737
                'module'    => 'xiph',
738
                'mime_type' => 'application/ogg',
739
                'fail_id3'  => 'WARNING',
740
                'fail_ape'  => 'WARNING',
741
            ],
742
743
            // QT   - audio/video - Quicktime
744
            'quicktime' => [
745
                'pattern'   => '^.{4}(cmov|free|ftyp|mdat|moov|pnot|skip|wide)',
746
                'group'     => 'audio-video',
747
                'module'    => 'quicktime',
748
                'mime_type' => 'video/quicktime',
749
            ],
750
751
            // 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)
752
            'riff'      => [
753
                'pattern'   => '^(RIFF|SDSS|FORM)',
754
                'group'     => 'audio-video',
755
                'module'    => 'riff',
756
                'mime_type' => 'audio/x-wave',
757
                'fail_ape'  => 'WARNING',
758
            ],
759
760
            // Real - audio/video - RealAudio, RealVideo
761
            'real'      => [
762
                'pattern'   => '^(\.RMF|.ra)',
763
                'group'     => 'audio-video',
764
                'module'    => 'real',
765
                'mime_type' => 'audio/x-realaudio',
766
            ],
767
768
            // SWF - audio/video - ShockWave Flash
769
            'swf'       => [
770
                'pattern'   => '^(F|C)WS',
771
                'group'     => 'audio-video',
772
                'module'    => 'swf',
773
                'mime_type' => 'application/x-shockwave-flash',
774
            ],
775
776
            // Still-Image formats
777
778
            // BMP  - still image - Bitmap (Windows, OS/2; uncompressed, RLE8, RLE4)
779
            'bmp'       => [
780
                'pattern'   => '^BM',
781
                'group'     => 'graphic',
782
                'module'    => 'bmp',
783
                'mime_type' => 'image/bmp',
784
                'fail_id3'  => 'ERROR',
785
                'fail_ape'  => 'ERROR',
786
            ],
787
788
            // GIF  - still image - Graphics Interchange Format
789
            'gif'       => [
790
                'pattern'   => '^GIF',
791
                'group'     => 'graphic',
792
                'module'    => 'gif',
793
                'mime_type' => 'image/gif',
794
                'fail_id3'  => 'ERROR',
795
                'fail_ape'  => 'ERROR',
796
            ],
797
798
            // JPEG - still image - Joint Photographic Experts Group (JPEG)
799
            'jpeg'      => [
800
                'pattern'   => '^\xFF\xD8\xFF',
801
                'group'     => 'graphic',
802
                'module'    => 'jpeg',
803
                'mime_type' => 'image/jpeg',
804
                'fail_id3'  => 'ERROR',
805
                'fail_ape'  => 'ERROR',
806
            ],
807
808
            // PCD  - still image - Kodak Photo CD
809
            'pcd'       => [
810
                'pattern'   => '^.{2048}PCD_IPI\x00',
811
                'group'     => 'graphic',
812
                'module'    => 'pcd',
813
                'mime_type' => 'image/x-photo-cd',
814
                'fail_id3'  => 'ERROR',
815
                'fail_ape'  => 'ERROR',
816
            ],
817
818
            // PNG  - still image - Portable Network Graphics (PNG)
819
            'png'       => [
820
                'pattern'   => '^\x89\x50\x4E\x47\x0D\x0A\x1A\x0A',
821
                'group'     => 'graphic',
822
                'module'    => 'png',
823
                'mime_type' => 'image/png',
824
                'fail_id3'  => 'ERROR',
825
                'fail_ape'  => 'ERROR',
826
            ],
827
828
            // SVG  - still image - Scalable Vector Graphics (SVG)
829
            'svg'       => [
830
                'pattern'   => '<!DOCTYPE svg PUBLIC ',
831
                'mime_type' => 'image/svg+xml',
832
                'fail_id3'  => 'ERROR',
833
                'fail_ape'  => 'ERROR',
834
            ],
835
836
            // TIFF  - still image - Tagged Information File Format (TIFF)
837
            'tiff'      => [
838
                'pattern'   => '^(II\x2A\x00|MM\x00\x2A)',
839
                'group'     => 'graphic',
840
                'module'    => 'tiff',
841
                'mime_type' => 'image/tiff',
842
                'fail_id3'  => 'ERROR',
843
                'fail_ape'  => 'ERROR',
844
            ],
845
846
            // Data formats
847
848
            'exe'      => [
849
                'pattern'   => '^MZ',
850
                'mime_type' => 'application/octet-stream',
851
                'fail_id3'  => 'ERROR',
852
                'fail_ape'  => 'ERROR',
853
            ],
854
855
            // ISO  - data        - International Standards Organization (ISO) CD-ROM Image
856
            'iso'      => [
857
                'pattern'   => '^.{32769}CD001',
858
                'group'     => 'misc',
859
                'module'    => 'iso',
860
                'mime_type' => 'application/octet-stream',
861
                'fail_id3'  => 'ERROR',
862
                'fail_ape'  => 'ERROR',
863
            ],
864
865
            // RAR  - data        - RAR compressed data
866
            'rar'      => [
867
                'pattern'   => '^Rar\!',
868
                'mime_type' => 'application/octet-stream',
869
                'fail_id3'  => 'ERROR',
870
                'fail_ape'  => 'ERROR',
871
            ],
872
873
            // SZIP - audio       - SZIP compressed data
874
            'szip'     => [
875
                'pattern'   => '^SZ\x0A\x04',
876
                'group'     => 'archive',
877
                'module'    => 'szip',
878
                'mime_type' => 'application/octet-stream',
879
                'fail_id3'  => 'ERROR',
880
                'fail_ape'  => 'ERROR',
881
            ],
882
883
            // TAR  - data        - TAR compressed data
884
            'tar'      => [
885
                '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}',
886
                'group'     => 'archive',
887
                'module'    => 'tar',
888
                'mime_type' => 'application/x-tar',
889
                'fail_id3'  => 'ERROR',
890
                'fail_ape'  => 'ERROR',
891
            ],
892
893
            // GZIP  - data        - GZIP compressed data
894
            'gz'       => [
895
                'pattern'   => '^\x1F\x8B\x08',
896
                'group'     => 'archive',
897
                'module'    => 'gzip',
898
                'mime_type' => 'application/x-gzip',
899
                'fail_id3'  => 'ERROR',
900
                'fail_ape'  => 'ERROR',
901
            ],
902
903
            // ZIP  - data        - ZIP compressed data
904
            'zip'      => [
905
                'pattern'   => '^PK\x03\x04',
906
                'group'     => 'archive',
907
                'module'    => 'zip',
908
                'mime_type' => 'application/zip',
909
                'fail_id3'  => 'ERROR',
910
                'fail_ape'  => 'ERROR',
911
            ],
912
913
            // PAR2 - data        - Parity Volume Set Specification 2.0
914
            'par2'     => [
915
                'pattern'   => '^PAR2\x00PKT',
916
                'mime_type' => 'application/octet-stream',
917
                'fail_id3'  => 'ERROR',
918
                'fail_ape'  => 'ERROR',
919
            ],
920
921
            // PDF  - data       - Portable Document Format
922
            'pdf'      => [
923
                'pattern'   => '^\x25PDF',
924
                'mime_type' => 'application/pdf',
925
                'fail_id3'  => 'ERROR',
926
                'fail_ape'  => 'ERROR',
927
            ],
928
929
            // DOC  - data       - Microsoft Word
930
            'msoffice' => [
931
                'pattern'   => '^\xD0\xCF\x11\xE0', // D0CF11E == DOCFILE == Microsoft Office Document
932
                'mime_type' => 'application/octet-stream',
933
                'fail_id3'  => 'ERROR',
934
                'fail_ape'  => 'ERROR',
935
            ],
936
        ];
937
938
        return $format_info;
939
    }
940
941
    // Recursive over array - converts array to $encoding charset from $this->encoding
942
    public function CharConvert(&$array, $encoding)
943
    {
944
        // Identical encoding - end here
945
        if ($encoding == $this->encoding) {
946
            return;
947
        }
948
949
        // Loop thru array
950
        foreach ($array as $key => $value) {
951
            // Go recursive
952
            if (is_array($value)) {
953
                $this->CharConvert($array[$key], $encoding);
954
            } // Convert string
955
            elseif (is_string($value)) {
956
                $array[$key] = $this->iconv($encoding, $this->encoding, $value);
957
            }
958
        }
959
    }
960
961
    // Convert and copy tags
962
    protected function HandleAllTags()
963
    {
964
        // Key name => array (tag name, character encoding)
965
        static $tags = [
966
            'asf'       => ['asf', 'UTF-16LE'],
967
            'midi'      => ['midi', 'ISO-8859-1'],
968
            'nsv'       => ['nsv', 'ISO-8859-1'],
969
            'ogg'       => ['vorbiscomment', 'UTF-8'],
970
            'png'       => ['png', 'UTF-8'],
971
            'tiff'      => ['tiff', 'ISO-8859-1'],
972
            'quicktime' => ['quicktime', 'ISO-8859-1'],
973
            'real'      => ['real', 'ISO-8859-1'],
974
            'vqf'       => ['vqf', 'ISO-8859-1'],
975
            'zip'       => ['zip', 'ISO-8859-1'],
976
            'riff'      => ['riff', 'ISO-8859-1'],
977
            'lyrics3'   => ['lyrics3', 'ISO-8859-1'],
978
            'id3v1'     => ['id3v1', ''],            // change below - cannot assign variable to static array
979
            'id3v2'     => ['id3v2', 'UTF-8'],       // module converts all frames to UTF-8
980
            'ape'       => ['ape', 'UTF-8']
981
        ];
982
        $tags['id3v1'][1] = $this->encoding_id3v1;
983
984
        // Loop thru tags array
985
        foreach ($tags as $comment_name => $tag_name_encoding_array) {
986
            list($tag_name, $encoding) = $tag_name_encoding_array;
987
988
            // Fill in default encoding type if not already present
989
            @$this->info[$comment_name] and $this->info[$comment_name]['encoding'] = $encoding;
990
991
            // Copy comments if key name set
992
            if (@$this->info[$comment_name]['comments']) {
993
                foreach ($this->info[$comment_name]['comments'] as $tag_key => $value_array) {
994
                    foreach ($value_array as $key => $value) {
995
                        if (strlen(trim($value)) > 0) {
996
                            $this->info['tags'][$tag_name][trim($tag_key)][] = $value; // do not trim!! Unicode characters will get mangled if trailing nulls are removed!
997
                        }
998
                    }
999
                }
1000
1001
                if (!@$this->info['tags'][$tag_name]) {
1002
                    // comments are set but contain nothing but empty strings, so skip
1003
                    continue;
1004
                }
1005
1006
                $this->CharConvert($this->info['tags'][$tag_name], $encoding);
1007
            }
1008
        }
1009
1010
        // Merge comments from ['tags'] into common ['comments']
1011
        if (@$this->info['tags']) {
1012
            foreach ($this->info['tags'] as $tag_type => $tag_array) {
1013
                foreach ($tag_array as $tag_name => $tagdata) {
1014
                    foreach ($tagdata as $key => $value) {
1015
                        if (!empty($value)) {
1016
                            if (empty($this->info['comments'][$tag_name])) {
1017
                                // fall through and append value
1018
                            } elseif ('id3v1' == $tag_type) {
1019
                                $new_value_length = strlen(trim($value));
1020
                                foreach ($this->info['comments'][$tag_name] as $existing_key => $existing_value) {
1021
                                    $old_value_length = strlen(trim($existing_value));
1022
                                    if (($new_value_length <= $old_value_length) && (substr($existing_value, 0, $new_value_length) == trim($value))) {
1023
                                        // new value is identical but shorter-than (or equal-length to) one already in comments - skip
1024
                                        break 2;
1025
                                    }
1026
                                }
1027
                            } else {
1028
                                $new_value_length = strlen(trim($value));
1029
                                foreach ($this->info['comments'][$tag_name] as $existing_key => $existing_value) {
1030
                                    $old_value_length = strlen(trim($existing_value));
1031
                                    if (($new_value_length > $old_value_length) && (substr(trim($value), 0, strlen($existing_value)) == $existing_value)) {
1032
                                        $this->info['comments'][$tag_name][$existing_key] = trim($value);
1033
                                        break 2;
1034
                                    }
1035
                                }
1036
                            }
1037
1038
                            if (empty($this->info['comments'][$tag_name]) || !in_array(trim($value), $this->info['comments'][$tag_name])) {
1039
                                $this->info['comments'][$tag_name][] = trim($value);
1040
                            }
1041
                        }
1042
                    }
1043
                }
1044
            }
1045
        }
1046
1047
        return true;
1048
    }
1049
}
1050
1051
abstract class getid3_handler
1052
{
1053
1054
    protected $getid3;                          // pointer
1055
1056
    protected $data_string_flag     = false;        // analyzing filepointer or string
1057
    protected $data_string;                     // string to analyze
1058
    protected $data_string_position = 0;        // seek position in string
1059
1060
    public function __construct(getID3 $getid3)
1061
    {
1062
        $this->getid3 = $getid3;
1063
    }
1064
1065
    // Analyze from file pointer
1066
    abstract public function Analyze();
1067
1068
    // Analyze from string instead
1069
    public function AnalyzeString(&$string)
1070
    {
1071
        // Enter string mode
1072
        $this->data_string_flag = true;
1073
        $this->data_string      = $string;
1074
1075
        // Save info
1076
        $saved_avdataoffset = $this->getid3->info['avdataoffset'];
1077
        $saved_avdataend    = $this->getid3->info['avdataend'];
1078
        $saved_filesize     = $this->getid3->info['filesize'];
1079
1080
        // Reset some info
1081
        $this->getid3->info['avdataoffset'] = 0;
1082
        $this->getid3->info['avdataend']    = $this->getid3->info['filesize'] = strlen($string);
1083
1084
        // Analyze
1085
        $this->Analyze();
1086
1087
        // Restore some info
1088
        $this->getid3->info['avdataoffset'] = $saved_avdataoffset;
1089
        $this->getid3->info['avdataend']    = $saved_avdataend;
1090
        $this->getid3->info['filesize']     = $saved_filesize;
1091
1092
        // Exit string mode
1093
        $this->data_string_flag = false;
1094
    }
1095
1096
    protected function ftell()
1097
    {
1098
        if ($this->data_string_flag) {
1099
            return $this->data_string_position;
1100
        }
1101
        return ftell($this->getid3->fp);
1102
    }
1103
1104
    protected function fread($bytes)
1105
    {
1106
        if ($this->data_string_flag) {
1107
            $this->data_string_position += $bytes;
1108
            return substr($this->data_string, $this->data_string_position - $bytes, $bytes);
1109
        }
1110
        return fread($this->getid3->fp, $bytes);
1111
    }
1112
1113
    protected function fseek($bytes, $whence = SEEK_SET)
1114
    {
1115
        if ($this->data_string_flag) {
1116
            switch ($whence) {
1117
                case SEEK_SET:
1118
                    $this->data_string_position = $bytes;
1119
                    return;
1120
1121
                case SEEK_CUR:
1122
                    $this->data_string_position += $bytes;
1123
                    return;
1124
1125
                case SEEK_END:
1126
                    $this->data_string_position = strlen($this->data_string) + $bytes;
1127
                    return;
1128
            }
1129
        }
1130
        return fseek($this->getid3->fp, $bytes, $whence);
1131
    }
1132
1133
}
1134
1135
abstract class getid3_handler_write
1136
{
1137
    protected $filename;
1138
    protected $user_abort;
1139
1140
    private $fp_lock;
1141
    private $owner;
1142
    private $group;
1143
    private $perms;
1144
1145
    public function __construct($filename)
1146
    {
1147
        if (!file_exists($filename)) {
1148
            throw new getid3_exception('File does not exist: "' . $filename . '"');
1149
        }
1150
1151
        if (!is_writeable($filename)) {
1152
            throw new getid3_exception('File is not writeable: "' . $filename . '"');
1153
        }
1154
1155
        if (!is_writeable(dirname($filename))) {
1156
            throw new getid3_exception('Directory is not writeable: ' . dirname($filename) . ' (need to write lock file).');
1157
        }
1158
1159
        $this->user_abort = ignore_user_abort(true);
1160
1161
        $this->fp_lock = fopen($filename . '.getid3.lock', 'w');
1162
        flock($this->fp_lock, LOCK_EX);
1163
1164
        $this->filename = $filename;
1165
    }
1166
1167
    public function __destruct()
1168
    {
1169
        flock($this->fp_lock, LOCK_UN);
1170
        fclose($this->fp_lock);
1171
        unlink($this->filename . '.getid3.lock');
1172
1173
        ignore_user_abort($this->user_abort);
1174
    }
1175
1176
    protected function save_permissions()
1177
    {
1178
        $this->owner = fileowner($this->filename);
1179
        $this->group = filegroup($this->filename);
1180
        $this->perms = fileperms($this->filename);
1181
    }
1182
1183
    protected function restore_permissions()
1184
    {
1185
        @chown($this->filename, $this->owner);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for chown(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

1185
        /** @scrutinizer ignore-unhandled */ @chown($this->filename, $this->owner);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1186
        @chgrp($this->filename, $this->group);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for chgrp(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

1186
        /** @scrutinizer ignore-unhandled */ @chgrp($this->filename, $this->group);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1187
        @chmod($this->filename, $this->perms);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for chmod(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

1187
        /** @scrutinizer ignore-unhandled */ @chmod($this->filename, $this->perms);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1188
    }
1189
1190
    abstract public function read();
1191
1192
    abstract public function write();
1193
1194
    abstract public function remove();
1195
1196
}
1197
1198
class getid3_exception extends Exception
1199
{
1200
    public $message;
1201
1202
}
1203
1204
class getid3_lib
1205
{
1206
1207
    // Convert Little Endian byte string to int - max 32 bits
1208
    public static function LittleEndian2Int($byte_word, $signed = false)
1209
    {
1210
        return getid3_lib::BigEndian2Int(strrev($byte_word), $signed);
1211
    }
1212
1213
    // Convert number to Little Endian byte string
1214
    public static function LittleEndian2String($number, $minbytes = 1, $synchsafe = false)
1215
    {
1216
        $intstring = '';
1217
        while ($number > 0) {
1218
            if ($synchsafe) {
1219
                $intstring = $intstring . chr($number & 127);
1220
                $number    >>= 7;
1221
            } else {
1222
                $intstring = $intstring . chr($number & 255);
1223
                $number    >>= 8;
1224
            }
1225
        }
1226
        return str_pad($intstring, $minbytes, "\x00", STR_PAD_RIGHT);
1227
    }
1228
1229
    // Convert Big Endian byte string to int - max 32 bits
1230
    public static function BigEndian2Int($byte_word, $signed = false)
1231
    {
1232
        $int_value    = 0;
1233
        $byte_wordlen = strlen($byte_word);
1234
1235
        for ($i = 0; $i < $byte_wordlen; $i++) {
1236
            $int_value += ord($byte_word{$i}) * pow(256, ($byte_wordlen - 1 - $i));
1237
        }
1238
1239
        if ($signed) {
1240
            $sign_mask_bit = 0x80 << (8 * ($byte_wordlen - 1));
1241
            if ($int_value & $sign_mask_bit) {
1242
                $int_value = 0 - ($int_value & ($sign_mask_bit - 1));
1243
            }
1244
        }
1245
1246
        return $int_value;
1247
    }
1248
1249
    // Convert Big Endian byte sybc safe string to int - max 32 bits
1250
    public static function BigEndianSyncSafe2Int($byte_word)
1251
    {
1252
        $int_value    = 0;
1253
        $byte_wordlen = strlen($byte_word);
1254
1255
        // disregard MSB, effectively 7-bit bytes
1256
        for ($i = 0; $i < $byte_wordlen; $i++) {
1257
            $int_value = $int_value | (ord($byte_word{$i}) & 0x7F) << (($byte_wordlen - 1 - $i) * 7);
1258
        }
1259
        return $int_value;
1260
    }
1261
1262
    // Convert Big Endian byte string to bit string
1263
    public static function BigEndian2Bin($byte_word)
1264
    {
1265
        $bin_value    = '';
1266
        $byte_wordlen = strlen($byte_word);
1267
        for ($i = 0; $i < $byte_wordlen; $i++) {
1268
            $bin_value .= str_pad(decbin(ord($byte_word{$i})), 8, '0', STR_PAD_LEFT);
1269
        }
1270
        return $bin_value;
1271
    }
1272
1273
    public static function BigEndian2Float($byte_word)
1274
    {
1275
        // ANSI/IEEE Standard 754-1985, Standard for Binary Floating Point Arithmetic
1276
        // http://www.psc.edu/general/software/packages/ieee/ieee.html
1277
        // http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/ieee.html
1278
1279
        $bit_word = getid3_lib::BigEndian2Bin($byte_word);
1280
        if (!$bit_word) {
1281
            return 0;
1282
        }
1283
        $sign_bit = $bit_word{0};
1284
1285
        switch (strlen($byte_word) * 8) {
1286
            case 32:
1287
                $exponent_bits = 8;
1288
                $fraction_bits = 23;
1289
                break;
1290
1291
            case 64:
1292
                $exponent_bits = 11;
1293
                $fraction_bits = 52;
1294
                break;
1295
1296
            case 80:
1297
                // 80-bit Apple SANE format
1298
                // http://www.mactech.com/articles/mactech/Vol.06/06.01/SANENormalized/
1299
                $exponent_string = substr($bit_word, 1, 15);
1300
                $is_normalized   = intval($bit_word{16});
1301
                $fraction_string = substr($bit_word, 17, 63);
1302
                $exponent        = pow(2, getid3_lib::Bin2Dec($exponent_string) - 16383);
0 ignored issues
show
Bug introduced by
The method Bin2Dec() does not exist on getid3_lib. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1302
                $exponent        = pow(2, getid3_lib::/** @scrutinizer ignore-call */ Bin2Dec($exponent_string) - 16383);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1303
                $fraction        = $is_normalized + getid3_lib::DecimalBinary2Float($fraction_string);
1304
                $float_value     = $exponent * $fraction;
1305
                if ('1' == $sign_bit) {
1306
                    $float_value *= -1;
1307
                }
1308
                return $float_value;
1309
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
1310
1311
            default:
1312
                return false;
1313
                break;
1314
        }
1315
        $exponent_string = substr($bit_word, 1, $exponent_bits);
1316
        $fraction_string = substr($bit_word, $exponent_bits + 1, $fraction_bits);
1317
        $exponent        = bindec($exponent_string);
1318
        $fraction        = bindec($fraction_string);
1319
1320
        if (($exponent == (pow(2, $exponent_bits) - 1)) && (0 != $fraction)) {
1321
            // Not a Number
1322
            $float_value = false;
1323
        } elseif (($exponent == (pow(2, $exponent_bits) - 1)) && (0 == $fraction)) {
1324
            if ('1' == $sign_bit) {
1325
                $float_value = '-infinity';
1326
            } else {
1327
                $float_value = '+infinity';
1328
            }
1329
        } elseif ((0 == $exponent) && (0 == $fraction)) {
1330
            if ('1' == $sign_bit) {
1331
                $float_value = -0;
0 ignored issues
show
Unused Code introduced by
The assignment to $float_value is dead and can be removed.
Loading history...
1332
            } else {
1333
                $float_value = 0;
1334
            }
1335
            $float_value = ($sign_bit ? 0 : -0);
1336
        } elseif ((0 == $exponent) && (0 != $fraction)) {
1337
            // These are 'unnormalized' values
1338
            $float_value = pow(2, (-1 * (pow(2, $exponent_bits - 1) - 2))) * getid3_lib::DecimalBinary2Float($fraction_string);
1339
            if ('1' == $sign_bit) {
1340
                $float_value *= -1;
1341
            }
1342
        } elseif (0 != $exponent) {
1343
            $float_value = pow(2, ($exponent - (pow(2, $exponent_bits - 1) - 1))) * (1 + getid3_lib::DecimalBinary2Float($fraction_string));
1344
            if ('1' == $sign_bit) {
1345
                $float_value *= -1;
1346
            }
1347
        }
1348
        return (float)$float_value;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $float_value does not seem to be defined for all execution paths leading up to this point.
Loading history...
1349
    }
1350
1351
    public static function LittleEndian2Float($byte_word)
1352
    {
1353
        return getid3_lib::BigEndian2Float(strrev($byte_word));
1354
    }
1355
1356
    public static function DecimalBinary2Float($binary_numerator)
1357
    {
1358
        $numerator   = bindec($binary_numerator);
1359
        $denominator = bindec('1' . str_repeat('0', strlen($binary_numerator)));
1360
        return ($numerator / $denominator);
1361
    }
1362
1363
    public static function PrintHexBytes($string, $hex = true, $spaces = true, $html_safe = true)
1364
    {
1365
        $return_string = '';
1366
        for ($i = 0; $i < strlen($string); $i++) {
1367
            if ($hex) {
1368
                $return_string .= str_pad(dechex(ord($string{$i})), 2, '0', STR_PAD_LEFT);
1369
            } else {
1370
                $return_string .= ' ' . (ereg("[\x20-\x7E]", $string{$i}) ? $string{$i} : '�');
1371
            }
1372
            if ($spaces) {
1373
                $return_string .= ' ';
1374
            }
1375
        }
1376
        if ($html_safe) {
1377
            $return_string = htmlentities($return_string);
1378
        }
1379
        return $return_string;
1380
    }
1381
1382
1383
1384
    // Process header data string - read several values with algorithm and add to target
1385
    //   algorithm is one one the getid3_lib::Something2Something() function names
1386
    //   parts_array is  index => length    -  $target[index] = algorithm(substring(data))
1387
    //   - OR just substring(data) if length is negative!
1388
    //  indexes == 'IGNORE**' are ignored
1389
1390
    public static function ReadSequence($algorithm, &$target, &$data, $offset, $parts_array)
1391
    {
1392
        // Loop thru $parts_array
1393
        foreach ($parts_array as $target_string => $length) {
1394
            // Add to target
1395
            if (!strstr($target_string, 'IGNORE')) {
1396
                // substr(....length)
1397
                if ($length < 0) {
1398
                    $target[$target_string] = substr($data, $offset, -$length);
1399
                } // algorithm(substr(...length))
1400
                else {
1401
                    $target[$target_string] = getid3_lib::$algorithm(substr($data, $offset, $length));
1402
                }
1403
            }
1404
1405
            // Move pointer
1406
            $offset += abs($length);
1407
        }
1408
    }
1409
1410
}
1411
1412
class getid3_lib_replaygain
1413
{
1414
1415
    public static function NameLookup($name_code)
1416
    {
1417
        static $lookup = [
1418
            0 => 'not set',
1419
            1 => 'Track Gain Adjustment',
1420
            2 => 'Album Gain Adjustment'
1421
        ];
1422
1423
        return @$lookup[$name_code];
1424
    }
1425
1426
    public static function OriginatorLookup($originator_code)
1427
    {
1428
        static $lookup = [
1429
            0 => 'unspecified',
1430
            1 => 'pre-set by artist/producer/mastering engineer',
1431
            2 => 'set by user',
1432
            3 => 'determined automatically'
1433
        ];
1434
1435
        return @$lookup[$originator_code];
1436
    }
1437
1438
    public static function AdjustmentLookup($raw_adjustment, $sign_bit)
1439
    {
1440
        return (float)$raw_adjustment / 10 * (1 == $sign_bit ? -1 : 1);
1441
    }
1442
1443
    public static function GainString($name_code, $originator_code, $replaygain)
1444
    {
1445
        $sign_bit = $replaygain < 0 ? 1 : 0;
1446
1447
        $stored_replaygain = intval(round($replaygain * 10));
1448
        $gain_string       = str_pad(decbin($name_code), 3, '0', STR_PAD_LEFT);
1449
        $gain_string       .= str_pad(decbin($originator_code), 3, '0', STR_PAD_LEFT);
1450
        $gain_string       .= $sign_bit;
1451
        $gain_string       .= str_pad(decbin($stored_replaygain), 9, '0', STR_PAD_LEFT);
1452
1453
        return $gain_string;
1454
    }
1455
1456
}
1457
1458
1459