ID3v23::MCDI()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 2
rs 10
1
<?php
2
/**
3
 * Class ID3v23
4
 *
5
 * @created      22.09.2018
6
 * @author       smiley <[email protected]>
7
 * @copyright    2018 smiley
8
 * @license      MIT
9
 *
10
 * @noinspection PhpUnusedParameterInspection
11
 */
12
13
namespace chillerlan\ID3Tag;
14
15
use function count, explode, implode, ord, sha1, strlen, strpos, strtolower, substr, trim, unpack;
16
17
/**
18
 * @link http://id3.org/id3v2.3.0
19
 */
20
class ID3v23 extends ID3v22{
21
22
	/**
23
	 * 4. Declared ID3v2 frames
24
	 *
25
	 * @link http://id3.org/id3v2.3.0#Declared_ID3v2_frames
26
	 */
27
	protected array $declaredFrames = [
28
		'AENC' => 'Audio encryption',
29
		'APIC' => 'Attached picture',
30
		'COMM' => 'Comments',
31
		'COMR' => 'Commercial frame',
32
		'ENCR' => 'Encryption method registration',
33
		'EQUA' => 'Equalization',
34
		'ETCO' => 'Event timing codes',
35
		'GEOB' => 'General encapsulated object',
36
		'GRID' => 'Group identification registration',
37
		'IPLS' => 'Involved people list',
38
		'LINK' => 'Linked information',
39
		'MCDI' => 'Music CD identifier',
40
		'MLLT' => 'MPEG location lookup table',
41
		'OWNE' => 'Ownership frame',
42
		'PRIV' => 'Private frame',
43
		'PCNT' => 'Play counter',
44
		'POPM' => 'Popularimeter',
45
		'POSS' => 'Position synchronisation frame',
46
		'RBUF' => 'Recommended buffer size',
47
		'RVAD' => 'Relative volume adjustment',
48
		'RVRB' => 'Reverb',
49
		'SYLT' => 'Synchronized lyric/text',
50
		'SYTC' => 'Synchronized tempo codes',
51
		'TALB' => 'Album/Movie/Show title',
52
		'TBPM' => 'BPM (beats per minute)',
53
		'TCOM' => 'Composer',
54
		'TCON' => 'Content type',
55
		'TCOP' => 'Copyright message',
56
		'TDAT' => 'Date',
57
		'TDLY' => 'Playlist delay',
58
		'TENC' => 'Encoded by',
59
		'TEXT' => 'Lyricist/Text writer',
60
		'TFLT' => 'File type',
61
		'TIME' => 'Time',
62
		'TIT1' => 'Content group description',
63
		'TIT2' => 'Title/songname/content description',
64
		'TIT3' => 'Subtitle/Description refinement',
65
		'TKEY' => 'Initial key',
66
		'TLAN' => 'Language(s)',
67
		'TLEN' => 'Length',
68
		'TMED' => 'Media type',
69
		'TOAL' => 'Original album/movie/show title',
70
		'TOFN' => 'Original filename',
71
		'TOLY' => 'Original lyricist(s)/text writer(s)',
72
		'TOPE' => 'Original artist(s)/performer(s)',
73
		'TORY' => 'Original release year',
74
		'TOWN' => 'File owner/licensee',
75
		'TPE1' => 'Lead performer(s)/Soloist(s)',
76
		'TPE2' => 'Band/orchestra/accompaniment',
77
		'TPE3' => 'Conductor/performer refinement',
78
		'TPE4' => 'Interpreted, remixed, or otherwise modified by',
79
		'TPOS' => 'Part of a set',
80
		'TPUB' => 'Publisher',
81
		'TRCK' => 'Track number/Position in set',
82
		'TRDA' => 'Recording dates',
83
		'TRSN' => 'Internet radio station name',
84
		'TRSO' => 'Internet radio station owner',
85
		'TSIZ' => 'Size',
86
		'TSRC' => 'ISRC (international standard recording code)',
87
		'TSSE' => 'Software/Hardware and settings used for encoding',
88
		'TYER' => 'Year',
89
		'TXXX' => 'User defined text information frame',
90
		'UFID' => 'Unique file identifier',
91
		'USER' => 'Terms of use',
92
		'USLT' => 'Unsychronized lyric/text transcription',
93
		'WCOM' => 'Commercial information',
94
		'WCOP' => 'Copyright/Legal information',
95
		'WOAF' => 'Official audio file webpage',
96
		'WOAR' => 'Official artist/performer webpage',
97
		'WOAS' => 'Official audio source webpage',
98
		'WORS' => 'Official internet radio station homepage',
99
		'WPAY' => 'Payment',
100
		'WPUB' => 'Publishers official webpage',
101
		'WXXX' => 'User defined URL link frame',
102
103
		'GRP1' => 'ITunes Grouping',
104
		'TCMP' => 'ITunes compilation field',
105
		'TDEN' => 'Encoding time',
106
		'TSST' => 'Set subtitle',
107
		'TIPL' => 'Involved people list',
108
		'TMOO' => 'Mood',
109
		'TDOR' => 'Original release time',
110
		'TDRL' => 'Release time',
111
		'TDTG' => 'Tagging time',
112
		'TDRC' => 'Recording time',
113
		'TSOA' => 'Album sort order',
114
		'TSOP' => 'Performer sort order',
115
		'TSOT' => 'Title sort order',
116
		'TSO2' => 'Album-Artist sort order',
117
		'TSOC' => 'Composer sort order',
118
		'EQU2' => 'Equalisation',
119
		'RVA2' => 'Relative volume adjustment',
120
		'SIGN' => 'Signature',
121
		'ASPI' => 'Audio seek point index',
122
		'RGAD' => 'Replay Gain Adjustment',
123
		'CHAP' => 'Chapters',
124
		'CTOC' => 'Chapters Table Of Contents',
125
126
		'NCON' => '???',
127
		'TDES' => '???PODCASTDESC',
128
		'TCAT' => '???PODCASTCATEGORY',
129
		'TGID' => '???PODCASTID',
130
		'TKWD' => '???PODCASTKEYWORDS',
131
		'WFED' => '???PODCASTURL',
132
	];
133
134
135
	/**
136
	 * 3.3. ID3v2.3 frame overview
137
	 *
138
	 * @link http://id3.org/id3v2.3.0#ID3v2_frame_overview
139
	 *
140
	 * Frame ID   $xx xx xx xx  (four characters)
141
	 * Size       $xx xx xx xx
142
	 * Flags      $xx xx
143
	 *
144
	 * @inheritDoc
145
	 */
146
	public function parse(string $rawdata):array{
147
		$this->parsedFrames = [];
148
		$index              = 0;
149
		$rawlength          = strlen($rawdata);
150
151
		while($index < $rawlength){
152
153
			// frame name
154
			$name   = substr($rawdata, $index, 4);
155
			$index += 4;
156
157
			// name is end tag or garbage
158
			if($name === "\x00\x00\x00\x00" || strlen($name) !== 4){
159
				break;
160
			}
161
162
			// frame length bytes
163
			$length = substr($rawdata, $index, 4);
164
165
			// length data is garbage
166
			if(strlen($length) !== 4){
167
				break;
168
			}
169
170
			$length = unpack('N', $length)[1] ?? 0;
171
			$index += 4;
172
173
			// frame length exceeds tag size
174
			if($length > $rawlength || $index >= $rawlength){
175
				break;
176
			}
177
178
			// frame is empty
179
			if($length < 1){
180
				continue;
181
			}
182
183
			// status & format bytes
184
			$status = ord(substr($rawdata, $index, 1));
185
			$format = ord(substr($rawdata, $index + 1, 1));
186
			$index += 2;
187
188
			// frame data
189
			$data = substr($rawdata, $index, $length);
190
			$index += $length;
191
192
			// frame is empty
193
			if(strlen($data) < 1){
194
				continue; // @codeCoverageIgnore
195
			}
196
197
			$format = $this->getFrameFormat($format);
198
			$status = $this->getFrameStatus($status);
199
200
			if($format['unsync']){
201
				$data = ID3Helpers::unsyncString($data);
202
			}
203
204
			$this->setTermpos($data);
205
206
			$parsedFrame = $this->parseFrame([
207
				'name'   => $name,
208
				'data'   => $data,
209
				'length' => $length,
210
				'format' => $format,
211
				'status' => $status,
212
			]);
213
214
			$this->addFrame($name, $parsedFrame);
215
		}
216
217
		return $this->parsedFrames;
218
	}
219
220
	/**
221
	 * @link http://id3.org/id3v2.3.0#Frame_header_flags
222
	 */
223
	protected function getFrameFormat(int $flags):array{
224
		return [
225
			'flags'       => $flags,
226
			'length'      => false,
227
			'unsync'      => false,
228
			'encryption'  => (bool)($flags & 0b01000000),
229
			'compression' => (bool)($flags & 0b10000000),
230
			'grouping'    => (bool)($flags & 0b00100000),
231
		];
232
	}
233
234
	/**
235
	 * @link http://id3.org/id3v2.3.0#Frame_header_flags
236
	 */
237
	protected function getFrameStatus(int $flags):array{
238
		return [
239
			'flags'     => $flags,
240
			'read-only' => (bool)($flags & 0b00100000),
241
			'file'      => (bool)($flags & 0b01000000),
242
			'tag'       => (bool)($flags & 0b10000000),
243
		];
244
	}
245
246
	/**
247
	 * 4.1. Unique file identifier
248
	 *
249
	 * <Header for 'Unique file identifier', ID: "UFID">
250
	 * Owner identifier        <text string> $00
251
	 * Identifier              <up to 64 bytes binary data>
252
	 */
253
	protected function UFID(array $frame):array{
0 ignored issues
show
Unused Code introduced by
The parameter $frame is not used and could be removed. ( Ignorable by Annotation )

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

253
	protected function UFID(/** @scrutinizer ignore-unused */ array $frame):array{

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
254
		return []; // skip
255
	}
256
257
	/**
258
	 * 4.2.2. User defined text information frame
259
	 *
260
	 * @link http://id3.org/id3v2.3.0#User_defined_text_information_frame
261
	 *
262
	 * <Header for 'User defined text information frame', ID: "TXXX">
263
	 * Text encoding     $xx
264
	 * Description       <text string according to encoding> $00 (00)
265
	 * Value             <text string according to encoding>
266
	 */
267
	protected function TXXX(array $frame):array{
268
		return $this->TXX($frame);
269
	}
270
271
	/**
272
	 * 4.3.2. User defined URL link frame
273
	 *
274
	 * @link http://id3.org/id3v2.3.0#User_defined_URL_link_frame
275
	 *
276
	 * <Header for 'User defined URL link frame', ID: "WXXX">
277
	 * Text encoding     $xx
278
	 * Description       <text string according to encoding> $00 (00)
279
	 * URL               <text string>
280
	 */
281
	protected function WXXX(array $frame):array{
282
		return $this->WXX($frame);
283
	}
284
285
	/**
286
	 * 4.4. Involved people list
287
	 *
288
	 * @link http://id3.org/id3v2.3.0#Involved_people_list
289
	 *
290
	 * <Header for 'Involved people list', ID: "IPLS">
291
	 * Text encoding          $xx
292
	 * People list strings    <text strings according to encoding>
293
	 */
294
	protected function IPLS(array $frame):array{
295
		return $this->T($frame);
296
	}
297
298
	/**
299
	 * 4.5. Music CD identifier
300
	 *
301
	 * @link http://id3.org/id3v2.3.0#Music_CD_identifier
302
	 *
303
	 * <Header for 'Music CD identifier', ID: "MCDI">
304
	 * CD TOC                <binary data>
305
	 */
306
	protected function MCDI(array $frame):array{
307
		return $this->MCI($frame);
308
	}
309
310
	/**
311
	 * 4.9. Unsychronised lyrics/text transcription
312
	 *
313
	 * @link http://id3.org/id3v2.3.0#Unsychronised_lyrics.2Ftext_transcription
314
	 *
315
	 * <Header for 'Unsynchronised lyrics/text transcription', ID: "USLT">
316
	 * Text encoding        $xx
317
	 * Language             $xx xx xx
318
	 * Content descriptor   <text string according to encoding> $00 (00)
319
	 * Lyrics/text          <full text string according to encoding>
320
	 */
321
	protected function USLT(array $frame):array{
322
		return $this->COM($frame);
323
	}
324
325
	/**
326
	 * 4.11. Comments
327
	 *
328
	 * @link http://id3.org/id3v2.3.0#Comments
329
	 *
330
	 * <Header for 'Comment', ID: "COMM">
331
	 * Text encoding          $xx
332
	 * Language               $xx xx xx
333
	 * Short content descrip. <text string according to encoding> $00 (00)
334
	 * The actual text        <full text string according to encoding>
335
	 */
336
	protected function COMM(array $frame):array{
337
		return $this->COM($frame);
338
	}
339
340
	/**
341
	 * 4.15. Attached picture
342
	 *
343
	 * @link http://id3.org/id3v2.3.0#Attached_picture
344
	 *
345
	 * <Header for 'Attached picture', ID: "APIC">
346
	 * Text encoding      $xx
347
	 * MIME type          <text string> $00
348
	 * Picture type       $xx
349
	 * Description        <text string according to encoding> $00 (00)
350
	 * Picture data       <binary data>
351
	 */
352
	protected function APIC(array $frame):array{
353
		$terminator = strpos($frame['data'], "\x00", 1);
354
		$mime       = explode('/', strtolower(substr($frame['data'], 1, $terminator - 1)));
355
		$type       = ord(substr($frame['data'], $terminator + 1, 1));
356
357
		if(count($mime) !== 2 || $mime[0] !== 'image'){
358
359
			// is it a v2.2 style format?
360
			if($mime[0] !== 'image' && isset($this::imageFormatMagicbytes[$mime[0]])){
361
				$mime    = ['image', $mime[0]];
362
				$mime = implode('/', $mime);
363
			}
364
			else{
365
				return [];
366
			}
367
		}
368
369
		if($mime[1] === 'jpeg'){
370
			$mime[1] = 'jpg';
371
		}
372
373
		$magicbytes = $this::imageFormatMagicbytes[$mime[1]] ?? false;
374
375
		if(!$magicbytes){
376
			return [];
377
		}
378
379
		$termpos = strpos($frame['data'], "\x00".$magicbytes, $terminator + 1);
380
		$image   = substr($frame['data'], $termpos + 1);
381
382
		return [
383
			'desc'     => $this->decodeString(substr($frame['data'], $terminator + 2, $termpos - $terminator - 2)),
384
			'content'  => $image, # 'data:'.$mime.';base64,'.base64_encode($image),
385
			'format'   => $mime[1],
386
			'mime'     => $mime,
387
			'typeID'   => $type,
388
			'typeInfo' => $this::PICTURE_TYPE[$type] ?? '',
389
			'hash'     => sha1($image),
390
		];
391
	}
392
393
	/**
394
	 * 4.16.   General encapsulated object
395
	 *
396
	 * <Header for 'General encapsulated object', ID: "GEOB">
397
	 * Text encoding          $xx
398
	 * MIME type              <text string> $00
399
	 * Filename               <text string according to encoding> $00 (00)
400
	 * Content description    <text string according to encoding> $00 (00)
401
	 * Encapsulated object    <binary data>
402
	 */
403
	protected function GEOB(array $frame):array{
0 ignored issues
show
Unused Code introduced by
The parameter $frame is not used and could be removed. ( Ignorable by Annotation )

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

403
	protected function GEOB(/** @scrutinizer ignore-unused */ array $frame):array{

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
404
		return []; // skip
405
	}
406
407
	/**
408
	 * 4.17. Play counter
409
	 *
410
	 * @link http://id3.org/id3v2.3.0#Play_counter
411
	 *
412
	 * <Header for 'Play counter', ID: "PCNT">
413
	 * Counter        $xx xx xx xx (xx ...)
414
	 */
415
	protected function PCNT(array $frame):array{
416
		return $this->CNT($frame);
417
	}
418
419
	/**
420
	 * 4.18. Popularimeter
421
	 *
422
	 * @link http://id3.org/id3v2.3.0#Popularimeter
423
	 *
424
	 * <Header for 'Popularimeter', ID: "POPM">
425
	 * Email to user   <text string> $00
426
	 * Rating          $xx
427
	 * Counter         $xx xx xx xx (xx ...)
428
	 */
429
	protected function POPM(array $frame):array{
430
		return $this->POP($frame);
431
	}
432
433
	/**
434
	 * 4.21. Linked information
435
	 *
436
	 * <Header for 'Linked information', ID: "LINK">
437
	 * Frame identifier        $xx xx xx
438
	 * URL                     <text string> $00
439
	 * ID and additional data  <text string(s)>
440
	 */
441
	protected function LINK(array $frame):array{
0 ignored issues
show
Unused Code introduced by
The parameter $frame is not used and could be removed. ( Ignorable by Annotation )

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

441
	protected function LINK(/** @scrutinizer ignore-unused */ array $frame):array{

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
442
		return []; // skip
443
	}
444
445
	/**
446
	 * 4.28. Private frame
447
	 *
448
	 * @link http://id3.org/id3v2.3.0#Private_frame
449
	 *
450
	 * <Header for 'Private frame', ID: "PRIV">
451
	 * Owner identifier      <text string> $00
452
	 * The private data      <binary data>
453
	 */
454
	protected function PRIV(array $frame):array{
455
		return [
456
			'desc'    => trim(substr($frame['data'], 0, $this->termpos)),
457
			'content' => trim(substr($frame['data'], $this->termpos)),
458
		];
459
	}
460
461
}
462