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
|
|
|
|