ID3v22::MCI()   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 ID3v22
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 bin2hex, ord, sha1, strlen, strpos, strtolower, substr, trim, unpack;
16
17
/**
18
 * @link http://id3.org/id3v2-00
19
 */
20
class ID3v22 extends ID3v2Abstract{
21
22
	/**
23
	 * 4. Declared ID3v2 frames
24
	 */
25
	protected array $declaredFrames = [
26
		'BUF' => 'Recommended buffer size',
27
		'CNT' => 'Play counter',
28
		'COM' => 'Comments',
29
		'CRA' => 'Audio encryption',
30
		'CRM' => 'Encrypted meta frame',
31
		'ETC' => 'Event timing codes',
32
		'EQU' => 'Equalization',
33
		'GEO' => 'General encapsulated object',
34
		'IPL' => 'Involved people list',
35
		'LNK' => 'Linked information',
36
		'MCI' => 'Music CD Identifier',
37
		'MLL' => 'MPEG location lookup table',
38
		'PIC' => 'Attached picture',
39
		'POP' => 'Popularimeter',
40
		'REV' => 'Reverb',
41
		'RVA' => 'Relative volume adjustment',
42
		'SLT' => 'Synchronized lyric/text',
43
		'STC' => 'Synced tempo codes',
44
		'TAL' => 'Album/Movie/Show title',
45
		'TBP' => 'BPM (Beats Per Minute)',
46
		'TCM' => 'Composer',
47
		'TCO' => 'Content type',
48
		'TCR' => 'Copyright message',
49
		'TDA' => 'Date',
50
		'TDY' => 'Playlist delay',
51
		'TEN' => 'Encoded by',
52
		'TFT' => 'File type',
53
		'TIM' => 'Time',
54
		'TKE' => 'Initial key',
55
		'TLA' => 'Language(s)',
56
		'TLE' => 'Length',
57
		'TMT' => 'Media type',
58
		'TOA' => 'Original artist(s)/performer(s)',
59
		'TOF' => 'Original filename',
60
		'TOL' => 'Original Lyricist(s)/text writer(s)',
61
		'TOR' => 'Original release year',
62
		'TOT' => 'Original album/Movie/Show title',
63
		'TP1' => 'Lead artist(s)/Lead performer(s)/Soloist(s)/Performing group',
64
		'TP2' => 'Band/Orchestra/Accompaniment',
65
		'TP3' => 'Conductor/Performer refinement',
66
		'TP4' => 'Interpreted, remixed, or otherwise modified by',
67
		'TPA' => 'Part of a set',
68
		'TPB' => 'Publisher',
69
		'TRC' => 'ISRC (International Standard Recording Code)',
70
		'TRD' => 'Recording dates',
71
		'TRK' => 'Track number/Position in set',
72
		'TSI' => 'Size',
73
		'TSS' => 'Software/hardware and settings used for encoding',
74
		'TT1' => 'Content group description',
75
		'TT2' => 'Title/Songname/Content description',
76
		'TT3' => 'Subtitle/Description refinement',
77
		'TXT' => 'Lyricist/text writer',
78
		'TXX' => 'User defined text information frame',
79
		'TYE' => 'Year',
80
		'UFI' => 'Unique file identifier',
81
		'ULT' => 'Unsychronized lyric/text transcription',
82
		'WAF' => 'Official audio file webpage',
83
		'WAR' => 'Official artist/performer webpage',
84
		'WAS' => 'Official audio source webpage',
85
		'WCM' => 'Commercial information',
86
		'WCP' => 'Copyright/Legal information',
87
		'WPB' => 'Publishers official webpage',
88
		'WXX' => 'User defined URL link frame',
89
90
		'ITU' => 'iTunes?',
91
		'PCS' => 'Podcast?',
92
		'TDR' => 'Release date',
93
		'TDS' => '?',
94
		'TID' => '?',
95
		'WFD' => '?',
96
		'CM1' => '?',
97
	];
98
99
100
	/**
101
	 * 3.2. ID3v2.2 frames overview (excerpt)
102
	 *
103
	 * @inheritDoc
104
	 */
105
	public function parse(string $rawdata):array{
106
		$this->parsedFrames = [];
107
		$index              = 0;
108
		$rawlength          = strlen($rawdata);
109
110
		while($index < $rawlength){
111
112
			// frame name
113
			$name   = substr($rawdata, $index, 3);
114
			$index += 3;
115
116
			// name is end tag or garbage
117
			if($name === "\x00\x00\x00" || strlen($name) !== 3){
118
				break;
119
			}
120
121
			// frame length bytes
122
			$length = substr($rawdata, $index, 3);
123
124
			// length data is garbage
125
			if(strlen($length) !== 3){
126
				break;
127
			}
128
129
			$length = unpack('N', "\x00".$length)[1] ?? 0;
130
			$index += 3;
131
132
			// frame length exceeds tag size
133
			if($length > $rawlength || $index >= $rawlength){
134
				break;
135
			}
136
137
			// frame is empty
138
			if($length < 1){
139
				continue;
140
			}
141
142
			// frame data
143
			$data = substr($rawdata, $index, $length);
144
			$index += $length;
145
146
			// frame is empty
147
			if(strlen($data) < 1){
148
				continue;
149
			}
150
151
			$this->setTermpos($data);
152
153
			$parsedFrame = $this->parseFrame([
154
				'name'   => $name,
155
				'data'   => $data,
156
				'length' => $length,
157
			]);
158
159
			$this->addFrame($name, $parsedFrame);
160
		}
161
162
		return $this->parsedFrames;
163
	}
164
165
	/**
166
	 * 4.2. Text information frames
167
	 *
168
	 * Text information identifier  "T00" - "TZZ" , excluding "TXX",
169
	 * described in 4.2.2.
170
	 * Frame size                   $xx xx xx
171
	 * Text encoding                $xx
172
	 * Information                  <textstring>
173
	 */
174
	protected function T(array $frame):array{
175
		$content = $this->decodeString(substr($frame['data'], 1));
176
177
		// lists with a slash-delimiter
178
#		if(in_array($frame['name'], ['TP1', 'TCM', 'TXT', 'TOA', 'TOL'], true)){
179
#			$content = explode('/', $content);
180
#		}
181
182
		return ['content' => $content];
183
	}
184
185
	/**
186
	 * 4.2.2. User defined text information frame
187
	 *
188
	 * User defined...   "TXX"
189
	 * Frame size        $xx xx xx
190
	 * Text encoding     $xx
191
	 * Description       <textstring> $00 (00)
192
	 * Value             <textstring>
193
	 */
194
	protected function TXX(array $frame):array{
195
		$content = $this->decodeString(substr($frame['data'], $this->termpos));
196
197
		// multi value delimited by a null byte
198
#		if(strpos($content, "\x00") !== false){
199
#			$content = explode("\x00", $content);
200
#		}
201
202
		return [
203
			'desc'    => $this->decodeString(substr($frame['data'], 1, $this->termpos)),
204
			'content' => $content,
205
		];
206
	}
207
208
	/**
209
	 * 4.3. URL link frames
210
	 *
211
	 * URL link frame   "W00" - "WZZ" , excluding "WXX"
212
	 * (described in 4.3.2.)
213
	 * Frame size       $xx xx xx
214
	 * URL              <textstring>
215
	 */
216
	protected function W(array $frame):array{
217
		return ['content' => trim($frame['data'])];
218
	}
219
220
	/**
221
	 * 4.3.2. User defined URL link frame
222
	 *
223
	 * User defined...   "WXX"
224
	 * Frame size        $xx xx xx
225
	 * Text encoding     $xx
226
	 * Description       <textstring> $00 (00)
227
	 * URL               <textstring>
228
	 */
229
	protected function WXX(array $frame):array{
230
		return [
231
			'desc'    => $this->decodeString(substr($frame['data'], 1, $this->termpos)),
232
			'content' => trim(substr($frame['data'], $this->termpos)),
233
		];
234
	}
235
236
	/**
237
	 * 4.4. Involved people list
238
	 *
239
	 * Involved people list   "IPL"
240
	 * Frame size             $xx xx xx
241
	 * Text encoding          $xx
242
	 * People list strings    <textstrings>
243
	 */
244
	protected function IPL(array $frame):array{
245
		return $this->T($frame);
246
	}
247
248
	/**
249
	 * 4.5. Music CD Identifier
250
	 *
251
	 * Music CD identifier   "MCI"
252
	 * Frame size            $xx xx xx
253
	 * CD TOC                <binary data>
254
	 */
255
	protected function MCI(array $frame):array{
256
		return ['content' => $frame['data'],];
257
	}
258
259
	/**
260
	 * 4.9. Unsychronised lyrics/text transcription
261
	 *
262
	 * Unsynced lyrics/text "ULT"
263
	 * Frame size           $xx xx xx
264
	 * Text encoding        $xx
265
	 * Language             $xx xx xx
266
	 * Content descriptor   <textstring> $00 (00)
267
	 * Lyrics/text          <textstring>
268
	 */
269
	protected function ULT(array $frame):array{
270
		return $this->COM($frame);
271
	}
272
273
	/**
274
	 * 4.11. Comments
275
	 *
276
	 * Comment                   "COM"
277
	 * Frame size                $xx xx xx
278
	 * Text encoding             $xx
279
	 * Language                  $xx xx xx
280
	 * Short content description <textstring> $00 (00)
281
	 * The actual text           <textstring>
282
	 */
283
	protected function COM(array $frame):array{
284
		return [
285
			'desc'    => $this->decodeString(substr($frame['data'], 4, $this->termpos - 3)),
286
			'content' => $this->decodeString(substr($frame['data'], $this->termpos)),
287
			'lang'    => substr($frame['data'], 1, 3),
288
		];
289
	}
290
291
	/**
292
	 * 4.15.   Attached picture
293
	 *
294
	 * Attached picture   "PIC"
295
	 * Frame size         $xx xx xx
296
	 * Text encoding      $xx
297
	 * Image format       $xx xx xx
298
	 * Picture type       $xx
299
	 * Description        <textstring> $00 (00)
300
	 * Picture data       <binary data>
301
	 */
302
	protected function PIC(array $frame):array{
303
		$format = strtolower(substr($frame['data'], 1, 3));
304
		$type   = ord(substr($frame['data'], 4, 1));
305
306
		if($format === 'jpeg'){
307
			$format = 'jpg';
308
		}
309
310
		$magicbytes = $this::imageFormatMagicbytes[$format] ?? false;
311
312
		if(!$magicbytes){
313
			return ['rawdata' => bin2hex($frame['data'])];
314
		}
315
316
		$termpos = strpos($frame['data'], "\x00".$magicbytes);
317
		$image   = substr($frame['data'], $termpos + 1);
318
319
		return [
320
			'desc'     => $this->decodeString(substr($frame['data'], 5, $termpos - 5)),
321
			'content'  => $image, # 'data:image/'.$format.';base64,'.base64_encode($image),
322
			'format'   => $format,
323
			'mime'     => 'image/'.$format,
324
			'typeID'   => $type,
325
			'typeInfo' => $this::PICTURE_TYPE[$type] ?? '',
326
			'hash'     => sha1($image),
327
		];
328
	}
329
330
	/**
331
	 * 4.17. Play counter
332
	 *
333
	 * Play counter   "CNT"
334
	 * Frame size     $xx xx xx
335
	 * Counter        $xx xx xx xx (xx ...)
336
	 */
337
	protected function CNT(array $frame):array{
338
		return ['count' => ID3Helpers::bigEndian2Int($frame['data']) ?? 0];
339
	}
340
341
	/**
342
	 * 4.18. Popularimeter
343
	 *
344
	 * Popularimeter   "POP"
345
	 * Frame size      $xx xx xx
346
	 * Email to user   <textstring> $00
347
	 * Rating          $xx
348
	 * Counter         $xx xx xx xx (xx ...)
349
	 *
350
	 *
351
	 * The following list details how Windows Explorer reads and writes the POPM frame:
352
	 *
353
	 * 224-255 = 5 stars when READ with Windows Explorer, writes 255
354
	 * 160-223 = 4 stars, writes 196
355
	 * 096-159 = 3 stars, writes 128
356
	 * 032-095 = 2 stars, writes 64
357
	 * 001-031 = 1 star, writes 1
358
	 */
359
	protected function POP(array $frame):array{
360
		$t = strpos($frame['data'], "\x00", 1);
361
362
		return [
363
			'desc'   => substr($frame['data'], 0, $t),
364
			'rating' => ord(substr($frame['data'], $t + 1, 1)),
365
#			'count'  => ID3Helpers::bigEndian2Int(substr($frame['data'], $t + 2)) ?? 0,
366
		];
367
	}
368
369
}
370