|
1
|
|
|
<?php |
|
2
|
|
|
/** |
|
3
|
|
|
* Class ID3v2Abstract |
|
4
|
|
|
* |
|
5
|
|
|
* @created 22.09.2018 |
|
6
|
|
|
* @author smiley <[email protected]> |
|
7
|
|
|
* @copyright 2018 smiley |
|
8
|
|
|
* @license MIT |
|
9
|
|
|
*/ |
|
10
|
|
|
|
|
11
|
|
|
namespace chillerlan\ID3Tag; |
|
12
|
|
|
|
|
13
|
|
|
use function bin2hex, call_user_func_array, method_exists, ord, preg_match, strpos, substr, trim; |
|
14
|
|
|
|
|
15
|
|
|
/** |
|
16
|
|
|
* @link http://id3.org/id3guide |
|
17
|
|
|
* @link http://id3.org/id3v2-chapters-1.0 |
|
18
|
|
|
* @link http://id3.org/id3v2-accessibility-1.0 |
|
19
|
|
|
* @link https://sno.phy.queensu.ca/~phil/exiftool/TagNames/ID3.html |
|
20
|
|
|
*/ |
|
21
|
|
|
abstract class ID3v2Abstract implements ParserInterface{ |
|
22
|
|
|
|
|
23
|
|
|
/** |
|
24
|
|
|
* 4.15. Attached picture |
|
25
|
|
|
* |
|
26
|
|
|
* Picture type |
|
27
|
|
|
* |
|
28
|
|
|
* @var array |
|
29
|
|
|
*/ |
|
30
|
|
|
public const PICTURE_TYPE = [ |
|
31
|
|
|
0x00 => 'Other', |
|
32
|
|
|
0x01 => '32x32 pixels \'file icon\' (PNG only)', |
|
33
|
|
|
0x02 => 'Other file icon', |
|
34
|
|
|
0x03 => 'Cover (front)', |
|
35
|
|
|
0x04 => 'Cover (back)', |
|
36
|
|
|
0x05 => 'Leaflet page', |
|
37
|
|
|
0x06 => 'Media (e.g. label side of CD)', |
|
38
|
|
|
0x07 => 'Lead artist/lead performer/soloist', |
|
39
|
|
|
0x08 => 'Artist/performer', |
|
40
|
|
|
0x09 => 'Conductor', |
|
41
|
|
|
0x0A => 'Band/Orchestra', |
|
42
|
|
|
0x0B => 'Composer', |
|
43
|
|
|
0x0C => 'Lyricist/text writer', |
|
44
|
|
|
0x0D => 'Recording Location', |
|
45
|
|
|
0x0E => 'During recording', |
|
46
|
|
|
0x0F => 'During performance', |
|
47
|
|
|
0x10 => 'Movie/video screen capture', |
|
48
|
|
|
0x11 => 'A bright coloured fish', |
|
49
|
|
|
0x12 => 'Illustration', |
|
50
|
|
|
0x13 => 'Band/artist logotype', |
|
51
|
|
|
0x14 => 'Publisher/Studio logotype', |
|
52
|
|
|
]; |
|
53
|
|
|
|
|
54
|
|
|
/** |
|
55
|
|
|
* @var array |
|
56
|
|
|
*/ |
|
57
|
|
|
public const ENCODING_NAMES = [ |
|
58
|
|
|
// $00 ISO-8859-1. |
|
59
|
|
|
// Terminated with $00. |
|
60
|
|
|
0x00 => 'ISO-8859-1', |
|
61
|
|
|
// $01 UTF-16 encoded Unicode with BOM. All strings in the same frame SHALL have the same byteorder. |
|
62
|
|
|
// Terminated with $00 00. |
|
63
|
|
|
0x01 => 'UTF-16', |
|
64
|
|
|
// $02 UTF-16 big endian, encoded Unicode without BOM. |
|
65
|
|
|
// Terminated with $00 00. |
|
66
|
|
|
0x02 => 'UTF-16BE', |
|
67
|
|
|
// $03 UTF-8 encoded Unicode. |
|
68
|
|
|
// Terminated with $00. |
|
69
|
|
|
0x03 => 'UTF-8', |
|
70
|
|
|
]; |
|
71
|
|
|
|
|
72
|
|
|
/** |
|
73
|
|
|
* @var array |
|
74
|
|
|
*/ |
|
75
|
|
|
protected const imageFormatMagicbytes = [ |
|
76
|
|
|
'png' => "\x89\x50\x4e\x47", |
|
77
|
|
|
'jpg' => "\xff\xd8", |
|
78
|
|
|
'gif' => "\x47\x49\x46\x38", |
|
79
|
|
|
'bmp' => "\x42\x4d", |
|
80
|
|
|
]; |
|
81
|
|
|
|
|
82
|
|
|
protected array $parsedFrames; |
|
83
|
|
|
protected array $declaredFrames; |
|
84
|
|
|
protected string $encoding; |
|
85
|
|
|
protected string $terminator = "\x00"; |
|
86
|
|
|
protected int $termpos; |
|
87
|
|
|
|
|
88
|
|
|
/** |
|
89
|
|
|
* |
|
90
|
|
|
*/ |
|
91
|
|
|
protected function setTermpos(string $data):void{ |
|
92
|
|
|
$encodingByte = ord(substr($data, 0, 1)); |
|
93
|
|
|
$this->encoding = $this::ENCODING_NAMES[$encodingByte] ?? 'ISO-8859-1'; |
|
94
|
|
|
$this->termpos = strpos($data, $this->terminator, 1); |
|
95
|
|
|
|
|
96
|
|
|
// UTF-16 |
|
97
|
|
|
if($encodingByte === 1 || $encodingByte === 2){ |
|
98
|
|
|
$this->terminator = "\x00\x00"; |
|
99
|
|
|
|
|
100
|
|
|
// match terminator + BOM |
|
101
|
|
|
preg_match('/[\x00]{2}[\xfe\xff]{2}/', $data, $match); |
|
102
|
|
|
|
|
103
|
|
|
// no BOM / content following the terminator is not encoded |
|
104
|
|
|
if(empty($match) || $encodingByte === 2){ |
|
105
|
|
|
preg_match('/[\x00]{2}[^\x00]+/', $data, $match); |
|
106
|
|
|
} |
|
107
|
|
|
|
|
108
|
|
|
$this->termpos = strpos($data, $match[0] ?? "\x00") + 2; // add 2 bytes for the terminator |
|
109
|
|
|
} |
|
110
|
|
|
|
|
111
|
|
|
} |
|
112
|
|
|
|
|
113
|
|
|
/** |
|
114
|
|
|
* |
|
115
|
|
|
*/ |
|
116
|
|
|
protected function decodeString(string $data):string{ |
|
117
|
|
|
return trim(mb_convert_encoding($data, 'UTF-8', $this->encoding)); |
|
|
|
|
|
|
118
|
|
|
} |
|
119
|
|
|
|
|
120
|
|
|
/** |
|
121
|
|
|
* |
|
122
|
|
|
*/ |
|
123
|
|
|
protected function parseFrame(array $frame):array{ |
|
124
|
|
|
|
|
125
|
|
|
if(method_exists($this, $frame['name'])){ |
|
126
|
|
|
return call_user_func_array([$this, $frame['name']], [$frame]); |
|
127
|
|
|
} |
|
128
|
|
|
|
|
129
|
|
|
$shortname = substr($frame['name'], 0, 1); |
|
130
|
|
|
|
|
131
|
|
|
if(method_exists($this, $shortname)){ |
|
132
|
|
|
return call_user_func_array([$this, $shortname], [$frame]); |
|
133
|
|
|
} |
|
134
|
|
|
|
|
135
|
|
|
$frame['data'] = bin2hex($frame['data']); |
|
136
|
|
|
|
|
137
|
|
|
return ['rawdata' => $frame]; |
|
138
|
|
|
} |
|
139
|
|
|
|
|
140
|
|
|
/** |
|
141
|
|
|
* |
|
142
|
|
|
*/ |
|
143
|
|
|
protected function addFrame(string $frameName, array $parsedFrame):void{ |
|
144
|
|
|
|
|
145
|
|
|
if(empty($parsedFrame)){ |
|
146
|
|
|
return; |
|
147
|
|
|
} |
|
148
|
|
|
|
|
149
|
|
|
$this->parsedFrames[] = [ |
|
150
|
|
|
'name' => $frameName, |
|
151
|
|
|
'info' => $this->declaredFrames[$frameName] ?? '', |
|
152
|
|
|
'encoding' => $this->encoding |
|
153
|
|
|
] + $parsedFrame; |
|
154
|
|
|
} |
|
155
|
|
|
|
|
156
|
|
|
} |
|
157
|
|
|
|