ID3v1::parse()   A
last analyzed

Complexity

Conditions 5
Paths 9

Size

Total Lines 24
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 11
c 1
b 0
f 0
nc 9
nop 1
dl 0
loc 24
rs 9.6111
1
<?php
2
/**
3
 * Class ID3v1
4
 *
5
 * @created      26.09.2018
6
 * @author       smiley <[email protected]>
7
 * @copyright    2018 smiley
8
 * @license      MIT
9
 */
10
11
namespace chillerlan\ID3Tag;
12
13
use function array_keys, array_map, array_values, call_user_func_array, date, intval, method_exists, strlen, substr, trim, unpack;
14
15
/**
16
 * @link http://id3.org/ID3v1
17
 * @link http://www.birdcagesoft.com/ID3v12.txt
18
 */
19
final class ID3v1 implements ParserInterface{
20
21
	/**
22
	 * Genre definitions 0-79 follow the ID3 tag specification of 1999
23
	 * and the first set of Winamp extensions (80-125)
24
	 */
25
	public const GENRES = [
26
		0   => 'Blues', // starting at 0 was the stupidest thing to do...
27
		1   => 'Classic Rock',
28
		2   => 'Country',
29
		3   => 'Dance',
30
		4   => 'Disco',
31
		5   => 'Funk',
32
		6   => 'Grunge',
33
		7   => 'Hip-Hop',
34
		8   => 'Jazz',
35
		9   => 'Metal',
36
		10  => 'New Age',
37
		11  => 'Oldies',
38
		12  => 'Other',
39
		13  => 'Pop',
40
		14  => 'R&B',
41
		15  => 'Rap',
42
		16  => 'Reggae',
43
		17  => 'Rock',
44
		18  => 'Techno',
45
		19  => 'Industrial',
46
		20  => 'Alternative',
47
		21  => 'Ska',
48
		22  => 'Death Metal',
49
		23  => 'Pranks',
50
		24  => 'Soundtrack',
51
		25  => 'Euro-Techno',
52
		26  => 'Ambient',
53
		27  => 'Trip-Hop',
54
		28  => 'Vocal',
55
		29  => 'Jazz+Funk',
56
		30  => 'Fusion',
57
		31  => 'Trance',
58
		32  => 'Classical',
59
		33  => 'Instrumental',
60
		34  => 'Acid',
61
		35  => 'House',
62
		36  => 'Game',
63
		37  => 'Sound Clip',
64
		38  => 'Gospel',
65
		39  => 'Noise',
66
		40  => 'Alternative Rock',
67
		41  => 'Bass',
68
		42  => 'Soul',
69
		43  => 'Punk',
70
		44  => 'Space',
71
		45  => 'Meditative',
72
		46  => 'Instrumental Pop',
73
		47  => 'Instrumental Rock',
74
		48  => 'Ethnic',
75
		49  => 'Gothic',
76
		50  => 'Darkwave',
77
		51  => 'Techno-Industrial',
78
		52  => 'Electronic',
79
		53  => 'Pop-Folk',
80
		54  => 'Eurodance',
81
		55  => 'Dream',
82
		56  => 'Southern Rock',
83
		57  => 'Comedy',
84
		58  => 'Cult',
85
		59  => 'Gangsta',
86
		60  => 'Top 40',
87
		61  => 'Christian Rap',
88
		62  => 'Pop/Funk',
89
		63  => 'Jungle',
90
		64  => 'Native US',
91
		65  => 'Cabaret',
92
		66  => 'New Wave',
93
		67  => 'Psychadelic',
94
		68  => 'Rave',
95
		69  => 'Showtunes',
96
		70  => 'Trailer',
97
		71  => 'Lo-Fi',
98
		72  => 'Tribal',
99
		73  => 'Acid Punk',
100
		74  => 'Acid Jazz',
101
		75  => 'Polka',
102
		76  => 'Retro',
103
		77  => 'Musical',
104
		78  => 'Rock & Roll',
105
		79  => 'Hard Rock',
106
		// Winamp extensions
107
		80  => 'Folk',
108
		81  => 'Folk-Rock',
109
		82  => 'National Folk',
110
		83  => 'Swing',
111
		84  => 'Fast Fusion',
112
		85  => 'Bebob',
113
		86  => 'Latin',
114
		87  => 'Revival',
115
		88  => 'Celtic',
116
		89  => 'Bluegrass',
117
		90  => 'Avantgarde',
118
		91  => 'Gothic Rock',
119
		92  => 'Progressive Rock',
120
		93  => 'Psychedelic Rock',
121
		94  => 'Symphonic Rock',
122
		95  => 'Slow Rock',
123
		96  => 'Big Band',
124
		97  => 'Chorus',
125
		98  => 'Easy Listening',
126
		99  => 'Acoustic',
127
		100 => 'Humour',
128
		101 => 'Speech',
129
		102 => 'Chanson',
130
		103 => 'Opera',
131
		104 => 'Chamber Music',
132
		105 => 'Sonata',
133
		106 => 'Symphony',
134
		107 => 'Booty Bass',
135
		108 => 'Primus',
136
		109 => 'Porn Groove',
137
		110 => 'Satire',
138
		111 => 'Slow Jam',
139
		112 => 'Club',
140
		113 => 'Tango',
141
		114 => 'Samba',
142
		115 => 'Folklore',
143
		116 => 'Ballad',
144
		117 => 'Power Ballad',
145
		118 => 'Rhythmic Soul',
146
		119 => 'Freestyle',
147
		120 => 'Duet',
148
		121 => 'Punk Rock',
149
		122 => 'Drum Solo',
150
		123 => 'Acapella',
151
		124 => 'Euro-House',
152
		125 => 'Dance Hall',
153
	];
154
155
	/**
156
	 * @inheritDoc
157
	 */
158
	public function parse(string $rawdata):?array{
159
160
		if(strlen($rawdata) !== 256){
161
#			throw new ID3Exception('invalid id3v1 tag size');
162
			return null;
163
		}
164
165
		$tagdata = substr($rawdata, 128);
166
#		$extdata = substr($rawdata, 0, 128); // @todo v1.2
167
168
		$format = $tagdata[125] === "\x00" && $tagdata[126] !== "\x00"
169
			? 'a28comment/a1null/c1track/c1genre' // v1.1
170
			: 'a30comment/c1genre'; // v1.0
171
172
		$data = unpack('a3identifier/a30title/a30artist/a30album/a4year/'.$format, $tagdata);
173
174
		if($data['identifier'] !== 'TAG'){
175
#			throw new ID3Exception('invalid id3v1 identifier');
176
			return null;
177
		}
178
179
		unset($data['identifier'], $data['null']);
180
181
		return array_map([$this, 'parseFrame'], array_keys($data), array_values($data));
182
	}
183
184
	/**
185
	 * @param string $tag
186
	 * @param mixed  $content
187
	 *
188
	 * @return array
189
	 */
190
	protected function parseFrame(string $tag, $content):array{
191
192
		if(method_exists($this, $tag)){
193
			return call_user_func_array([$this, $tag], [$tag, $content]);
194
		}
195
196
		// Detecting UTF-8 before ISO-8859-1 will cause ASCII strings being tagged as UTF-8, which is fine.
197
		// However, it will prevent UTF-8 encoded strings from being wrongly decoded twice.
198
		$encoding = mb_detect_encoding($content, ['Windows-1251', 'Windows-1252', 'KOI8-R', 'UTF-8', 'ISO-8859-1']);
199
200
		if($encoding !== 'UTF-8'){
201
			$content = mb_convert_encoding($content, 'UTF-8', $encoding);
202
		}
203
204
		return [
205
			'tag'      => $tag,
206
			'content'  => trim($content),
0 ignored issues
show
Bug introduced by
It seems like $content can also be of type array; however, parameter $string of trim() 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

206
			'content'  => trim(/** @scrutinizer ignore-type */ $content),
Loading history...
207
			'encoding' => $encoding,
208
		];
209
	}
210
211
	/**
212
	 * @param string $tag
213
	 * @param mixed  $content
214
	 *
215
	 * @return array
216
	 */
217
	protected function year(string $tag, $content):array{
218
		$content = intval(trim($content));
219
220
		return [
221
			'tag'     => $tag,
222
			'content' => $content <= date('Y') && $content > 0 ? $content : null,
223
		];
224
	}
225
226
	/**
227
	 *
228
	 */
229
	protected function genre(string $tag, int $content):array{
230
		return [
231
			'tag'     => $tag,
232
			'content' => $content, // there's no sane way to tell whether a genre was actually set
233
			'desc'    => $this::GENRES[$content] ?? null,
234
		];
235
	}
236
237
	/**
238
	 *
239
	 */
240
	protected function track(string $tag, int $content):array{
241
		return [
242
			'tag'     => $tag,
243
			'content' => $content,
244
		];
245
	}
246
247
}
248