[SPIP] ~maj v3.2.9-->v3.2.11
[lhc/web/www.git] / www / plugins-dist / medias / lib / getid3 / write.apetag.php
1 <?php
2
3 /////////////////////////////////////////////////////////////////
4 /// getID3() by James Heinrich <info@getid3.org> //
5 // available at https://github.com/JamesHeinrich/getID3 //
6 // or https://www.getid3.org //
7 // or http://getid3.sourceforge.net //
8 // see readme.txt for more details //
9 /////////////////////////////////////////////////////////////////
10 // //
11 // write.apetag.php //
12 // module for writing APE tags //
13 // dependencies: module.tag.apetag.php //
14 // ///
15 /////////////////////////////////////////////////////////////////
16
17 if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
18 exit;
19 }
20 getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.apetag.php', __FILE__, true);
21
22 class getid3_write_apetag
23 {
24 /**
25 * @var string
26 */
27 public $filename;
28
29 /**
30 * @var array
31 */
32 public $tag_data;
33
34 /**
35 * ReplayGain / MP3gain tags will be copied from old tag even if not passed in data.
36 *
37 * @var bool
38 */
39 public $always_preserve_replaygain = true;
40
41 /**
42 * Any non-critical errors will be stored here.
43 *
44 * @var array
45 */
46 public $warnings = array();
47
48 /**
49 * Any critical errors will be stored here.
50 *
51 * @var array
52 */
53 public $errors = array();
54
55 public function __construct() {
56 }
57
58 /**
59 * @return bool
60 */
61 public function WriteAPEtag() {
62 // NOTE: All data passed to this function must be UTF-8 format
63
64 $getID3 = new getID3;
65 $ThisFileInfo = $getID3->analyze($this->filename);
66
67 if (isset($ThisFileInfo['ape']['tag_offset_start']) && isset($ThisFileInfo['lyrics3']['tag_offset_end'])) {
68 if ($ThisFileInfo['ape']['tag_offset_start'] >= $ThisFileInfo['lyrics3']['tag_offset_end']) {
69 // Current APE tag between Lyrics3 and ID3v1/EOF
70 // This break Lyrics3 functionality
71 if (!$this->DeleteAPEtag()) {
72 return false;
73 }
74 $ThisFileInfo = $getID3->analyze($this->filename);
75 }
76 }
77
78 if ($this->always_preserve_replaygain) {
79 $ReplayGainTagsToPreserve = array('mp3gain_minmax', 'mp3gain_album_minmax', 'mp3gain_undo', 'replaygain_track_peak', 'replaygain_track_gain', 'replaygain_album_peak', 'replaygain_album_gain');
80 foreach ($ReplayGainTagsToPreserve as $rg_key) {
81 if (isset($ThisFileInfo['ape']['items'][strtolower($rg_key)]['data'][0]) && !isset($this->tag_data[strtoupper($rg_key)][0])) {
82 $this->tag_data[strtoupper($rg_key)][0] = $ThisFileInfo['ape']['items'][strtolower($rg_key)]['data'][0];
83 }
84 }
85 }
86
87 if ($APEtag = $this->GenerateAPEtag()) {
88 if (getID3::is_writable($this->filename) && is_file($this->filename) && ($fp = fopen($this->filename, 'a+b'))) {
89 $oldignoreuserabort = ignore_user_abort(true);
90 flock($fp, LOCK_EX);
91
92 $PostAPEdataOffset = $ThisFileInfo['avdataend'];
93 if (isset($ThisFileInfo['ape']['tag_offset_end'])) {
94 $PostAPEdataOffset = max($PostAPEdataOffset, $ThisFileInfo['ape']['tag_offset_end']);
95 }
96 if (isset($ThisFileInfo['lyrics3']['tag_offset_start'])) {
97 $PostAPEdataOffset = max($PostAPEdataOffset, $ThisFileInfo['lyrics3']['tag_offset_start']);
98 }
99 fseek($fp, $PostAPEdataOffset);
100 $PostAPEdata = '';
101 if ($ThisFileInfo['filesize'] > $PostAPEdataOffset) {
102 $PostAPEdata = fread($fp, $ThisFileInfo['filesize'] - $PostAPEdataOffset);
103 }
104
105 fseek($fp, $PostAPEdataOffset);
106 if (isset($ThisFileInfo['ape']['tag_offset_start'])) {
107 fseek($fp, $ThisFileInfo['ape']['tag_offset_start']);
108 }
109 ftruncate($fp, ftell($fp));
110 fwrite($fp, $APEtag, strlen($APEtag));
111 if (!empty($PostAPEdata)) {
112 fwrite($fp, $PostAPEdata, strlen($PostAPEdata));
113 }
114 flock($fp, LOCK_UN);
115 fclose($fp);
116 ignore_user_abort($oldignoreuserabort);
117 return true;
118 }
119 }
120 return false;
121 }
122
123 /**
124 * @return bool
125 */
126 public function DeleteAPEtag() {
127 $getID3 = new getID3;
128 $ThisFileInfo = $getID3->analyze($this->filename);
129 if (isset($ThisFileInfo['ape']['tag_offset_start']) && isset($ThisFileInfo['ape']['tag_offset_end'])) {
130 if (getID3::is_writable($this->filename) && is_file($this->filename) && ($fp = fopen($this->filename, 'a+b'))) {
131
132 flock($fp, LOCK_EX);
133 $oldignoreuserabort = ignore_user_abort(true);
134
135 fseek($fp, $ThisFileInfo['ape']['tag_offset_end']);
136 $DataAfterAPE = '';
137 if ($ThisFileInfo['filesize'] > $ThisFileInfo['ape']['tag_offset_end']) {
138 $DataAfterAPE = fread($fp, $ThisFileInfo['filesize'] - $ThisFileInfo['ape']['tag_offset_end']);
139 }
140
141 ftruncate($fp, $ThisFileInfo['ape']['tag_offset_start']);
142 fseek($fp, $ThisFileInfo['ape']['tag_offset_start']);
143
144 if (!empty($DataAfterAPE)) {
145 fwrite($fp, $DataAfterAPE, strlen($DataAfterAPE));
146 }
147
148 flock($fp, LOCK_UN);
149 fclose($fp);
150 ignore_user_abort($oldignoreuserabort);
151
152 return true;
153 }
154 return false;
155 }
156 return true;
157 }
158
159 /**
160 * @return string|false
161 */
162 public function GenerateAPEtag() {
163 // NOTE: All data passed to this function must be UTF-8 format
164
165 $items = array();
166 if (!is_array($this->tag_data)) {
167 return false;
168 }
169 foreach ($this->tag_data as $key => $arrayofvalues) {
170 if (!is_array($arrayofvalues)) {
171 return false;
172 }
173
174 $valuestring = '';
175 foreach ($arrayofvalues as $value) {
176 $valuestring .= str_replace("\x00", '', $value)."\x00";
177 }
178 $valuestring = rtrim($valuestring, "\x00");
179
180 // Length of the assigned value in bytes
181 $tagitem = getid3_lib::LittleEndian2String(strlen($valuestring), 4);
182
183 //$tagitem .= $this->GenerateAPEtagFlags(true, true, false, 0, false);
184 $tagitem .= "\x00\x00\x00\x00";
185
186 $tagitem .= $this->CleanAPEtagItemKey($key)."\x00";
187 $tagitem .= $valuestring;
188
189 $items[] = $tagitem;
190
191 }
192
193 return $this->GenerateAPEtagHeaderFooter($items, true).implode('', $items).$this->GenerateAPEtagHeaderFooter($items, false);
194 }
195
196 /**
197 * @param array $items
198 * @param bool $isheader
199 *
200 * @return string
201 */
202 public function GenerateAPEtagHeaderFooter(&$items, $isheader=false) {
203 $tagdatalength = 0;
204 foreach ($items as $itemdata) {
205 $tagdatalength += strlen($itemdata);
206 }
207
208 $APEheader = 'APETAGEX';
209 $APEheader .= getid3_lib::LittleEndian2String(2000, 4);
210 $APEheader .= getid3_lib::LittleEndian2String(32 + $tagdatalength, 4);
211 $APEheader .= getid3_lib::LittleEndian2String(count($items), 4);
212 $APEheader .= $this->GenerateAPEtagFlags(true, true, $isheader, 0, false);
213 $APEheader .= str_repeat("\x00", 8);
214
215 return $APEheader;
216 }
217
218 /**
219 * @param bool $header
220 * @param bool $footer
221 * @param bool $isheader
222 * @param int $encodingid
223 * @param bool $readonly
224 *
225 * @return string
226 */
227 public function GenerateAPEtagFlags($header=true, $footer=true, $isheader=false, $encodingid=0, $readonly=false) {
228 $APEtagFlags = array_fill(0, 4, 0);
229 if ($header) {
230 $APEtagFlags[0] |= 0x80; // Tag contains a header
231 }
232 if (!$footer) {
233 $APEtagFlags[0] |= 0x40; // Tag contains no footer
234 }
235 if ($isheader) {
236 $APEtagFlags[0] |= 0x20; // This is the header, not the footer
237 }
238
239 // 0: Item contains text information coded in UTF-8
240 // 1: Item contains binary information °)
241 // 2: Item is a locator of external stored information °°)
242 // 3: reserved
243 $APEtagFlags[3] |= ($encodingid << 1);
244
245 if ($readonly) {
246 $APEtagFlags[3] |= 0x01; // Tag or Item is Read Only
247 }
248
249 return chr($APEtagFlags[3]).chr($APEtagFlags[2]).chr($APEtagFlags[1]).chr($APEtagFlags[0]);
250 }
251
252 /**
253 * @param string $itemkey
254 *
255 * @return string
256 */
257 public function CleanAPEtagItemKey($itemkey) {
258 $itemkey = preg_replace("#[^\x20-\x7E]#i", '', $itemkey);
259
260 // http://www.personal.uni-jena.de/~pfk/mpp/sv8/apekey.html
261 switch (strtoupper($itemkey)) {
262 case 'EAN/UPC':
263 case 'ISBN':
264 case 'LC':
265 case 'ISRC':
266 $itemkey = strtoupper($itemkey);
267 break;
268
269 default:
270 $itemkey = ucwords($itemkey);
271 break;
272 }
273 return $itemkey;
274
275 }
276
277 }