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