[SPIP] v3.2.1-->v3.2.3
[lhc/web/www.git] / www / plugins-dist / medias / lib / getid3 / write.real.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.real.php //
12 // module for writing RealAudio/RealVideo tags //
13 // dependencies: module.tag.real.php //
14 // ///
15 /////////////////////////////////////////////////////////////////
16
17 class getid3_write_real
18 {
19 /**
20 * @var string
21 */
22 public $filename;
23
24 /**
25 * @var array
26 */
27 public $tag_data = array();
28
29 /**
30 * Read buffer size in bytes.
31 *
32 * @var int
33 */
34 public $fread_buffer_size = 32768;
35
36 /**
37 * Any non-critical errors will be stored here.
38 *
39 * @var array
40 */
41 public $warnings = array();
42
43 /**
44 * Any critical errors will be stored here.
45 *
46 * @var array
47 */
48 public $errors = array();
49
50 /**
51 * Minimum length of CONT tag in bytes.
52 *
53 * @var int
54 */
55 public $paddedlength = 512;
56
57 public function __construct() {
58 }
59
60 /**
61 * @return bool
62 */
63 public function WriteReal() {
64 // File MUST be writeable - CHMOD(646) at least
65 if (getID3::is_writable($this->filename) && is_file($this->filename) && ($fp_source = fopen($this->filename, 'r+b'))) {
66
67 // Initialize getID3 engine
68 $getID3 = new getID3;
69 $OldThisFileInfo = $getID3->analyze($this->filename);
70 if (empty($OldThisFileInfo['real']['chunks']) && !empty($OldThisFileInfo['real']['old_ra_header'])) {
71 $this->errors[] = 'Cannot write Real tags on old-style file format';
72 fclose($fp_source);
73 return false;
74 }
75
76 if (empty($OldThisFileInfo['real']['chunks'])) {
77 $this->errors[] = 'Cannot write Real tags because cannot find DATA chunk in file';
78 fclose($fp_source);
79 return false;
80 }
81 $oldChunkInfo = array();
82 foreach ($OldThisFileInfo['real']['chunks'] as $chunknumber => $chunkarray) {
83 $oldChunkInfo[$chunkarray['name']] = $chunkarray;
84 }
85 if (!empty($oldChunkInfo['CONT']['length'])) {
86 $this->paddedlength = max($oldChunkInfo['CONT']['length'], $this->paddedlength);
87 }
88
89 $new_CONT_tag_data = $this->GenerateCONTchunk();
90 $new_PROP_tag_data = $this->GeneratePROPchunk($OldThisFileInfo['real']['chunks'], $new_CONT_tag_data);
91 $new__RMF_tag_data = $this->GenerateRMFchunk($OldThisFileInfo['real']['chunks']);
92
93 if (isset($oldChunkInfo['.RMF']['length']) && ($oldChunkInfo['.RMF']['length'] == strlen($new__RMF_tag_data))) {
94 fseek($fp_source, $oldChunkInfo['.RMF']['offset']);
95 fwrite($fp_source, $new__RMF_tag_data);
96 } else {
97 $this->errors[] = 'new .RMF tag ('.strlen($new__RMF_tag_data).' bytes) different length than old .RMF tag ('.$oldChunkInfo['.RMF']['length'].' bytes)';
98 fclose($fp_source);
99 return false;
100 }
101
102 if (isset($oldChunkInfo['PROP']['length']) && ($oldChunkInfo['PROP']['length'] == strlen($new_PROP_tag_data))) {
103 fseek($fp_source, $oldChunkInfo['PROP']['offset']);
104 fwrite($fp_source, $new_PROP_tag_data);
105 } else {
106 $this->errors[] = 'new PROP tag ('.strlen($new_PROP_tag_data).' bytes) different length than old PROP tag ('.$oldChunkInfo['PROP']['length'].' bytes)';
107 fclose($fp_source);
108 return false;
109 }
110
111 if (isset($oldChunkInfo['CONT']['length']) && ($oldChunkInfo['CONT']['length'] == strlen($new_CONT_tag_data))) {
112
113 // new data length is same as old data length - just overwrite
114 fseek($fp_source, $oldChunkInfo['CONT']['offset']);
115 fwrite($fp_source, $new_CONT_tag_data);
116 fclose($fp_source);
117 return true;
118
119 } else {
120
121 if (empty($oldChunkInfo['CONT'])) {
122 // no existing CONT chunk
123 $BeforeOffset = $oldChunkInfo['DATA']['offset'];
124 $AfterOffset = $oldChunkInfo['DATA']['offset'];
125 } else {
126 // new data is longer than old data
127 $BeforeOffset = $oldChunkInfo['CONT']['offset'];
128 $AfterOffset = $oldChunkInfo['CONT']['offset'] + $oldChunkInfo['CONT']['length'];
129 }
130 if ($tempfilename = tempnam(GETID3_TEMP_DIR, 'getID3')) {
131 if (getID3::is_writable($tempfilename) && is_file($tempfilename) && ($fp_temp = fopen($tempfilename, 'wb'))) {
132
133 rewind($fp_source);
134 fwrite($fp_temp, fread($fp_source, $BeforeOffset));
135 fwrite($fp_temp, $new_CONT_tag_data);
136 fseek($fp_source, $AfterOffset);
137 while ($buffer = fread($fp_source, $this->fread_buffer_size)) {
138 fwrite($fp_temp, $buffer, strlen($buffer));
139 }
140 fclose($fp_temp);
141
142 if (copy($tempfilename, $this->filename)) {
143 unlink($tempfilename);
144 fclose($fp_source);
145 return true;
146 }
147 unlink($tempfilename);
148 $this->errors[] = 'FAILED: copy('.$tempfilename.', '.$this->filename.')';
149
150 } else {
151 $this->errors[] = 'Could not fopen("'.$tempfilename.'", "wb")';
152 }
153 }
154 fclose($fp_source);
155 return false;
156
157 }
158
159 }
160 $this->errors[] = 'Could not fopen("'.$this->filename.'", "r+b")';
161 return false;
162 }
163
164 /**
165 * @param array $chunks
166 *
167 * @return string
168 */
169 public function GenerateRMFchunk(&$chunks) {
170 $oldCONTexists = false;
171 $chunkNameKeys = array();
172 foreach ($chunks as $key => $chunk) {
173 $chunkNameKeys[$chunk['name']] = $key;
174 if ($chunk['name'] == 'CONT') {
175 $oldCONTexists = true;
176 }
177 }
178 $newHeadersCount = $chunks[$chunkNameKeys['.RMF']]['headers_count'] + ($oldCONTexists ? 0 : 1);
179
180 $RMFchunk = "\x00\x00"; // object version
181 $RMFchunk .= getid3_lib::BigEndian2String($chunks[$chunkNameKeys['.RMF']]['file_version'], 4);
182 $RMFchunk .= getid3_lib::BigEndian2String($newHeadersCount, 4);
183
184 $RMFchunk = '.RMF'.getid3_lib::BigEndian2String(strlen($RMFchunk) + 8, 4).$RMFchunk; // .RMF chunk identifier + chunk length
185 return $RMFchunk;
186 }
187
188 /**
189 * @param array $chunks
190 * @param string $new_CONT_tag_data
191 *
192 * @return string
193 */
194 public function GeneratePROPchunk(&$chunks, &$new_CONT_tag_data) {
195 $old_CONT_length = 0;
196 $old_DATA_offset = 0;
197 $old_INDX_offset = 0;
198 $chunkNameKeys = array();
199 foreach ($chunks as $key => $chunk) {
200 $chunkNameKeys[$chunk['name']] = $key;
201 if ($chunk['name'] == 'CONT') {
202 $old_CONT_length = $chunk['length'];
203 } elseif ($chunk['name'] == 'DATA') {
204 if (!$old_DATA_offset) {
205 $old_DATA_offset = $chunk['offset'];
206 }
207 } elseif ($chunk['name'] == 'INDX') {
208 if (!$old_INDX_offset) {
209 $old_INDX_offset = $chunk['offset'];
210 }
211 }
212 }
213 $CONTdelta = strlen($new_CONT_tag_data) - $old_CONT_length;
214
215 $PROPchunk = "\x00\x00"; // object version
216 $PROPchunk .= getid3_lib::BigEndian2String($chunks[$chunkNameKeys['PROP']]['max_bit_rate'], 4);
217 $PROPchunk .= getid3_lib::BigEndian2String($chunks[$chunkNameKeys['PROP']]['avg_bit_rate'], 4);
218 $PROPchunk .= getid3_lib::BigEndian2String($chunks[$chunkNameKeys['PROP']]['max_packet_size'], 4);
219 $PROPchunk .= getid3_lib::BigEndian2String($chunks[$chunkNameKeys['PROP']]['avg_packet_size'], 4);
220 $PROPchunk .= getid3_lib::BigEndian2String($chunks[$chunkNameKeys['PROP']]['num_packets'], 4);
221 $PROPchunk .= getid3_lib::BigEndian2String($chunks[$chunkNameKeys['PROP']]['duration'], 4);
222 $PROPchunk .= getid3_lib::BigEndian2String($chunks[$chunkNameKeys['PROP']]['preroll'], 4);
223 $PROPchunk .= getid3_lib::BigEndian2String(max(0, $old_INDX_offset + $CONTdelta), 4);
224 $PROPchunk .= getid3_lib::BigEndian2String(max(0, $old_DATA_offset + $CONTdelta), 4);
225 $PROPchunk .= getid3_lib::BigEndian2String($chunks[$chunkNameKeys['PROP']]['num_streams'], 2);
226 $PROPchunk .= getid3_lib::BigEndian2String($chunks[$chunkNameKeys['PROP']]['flags_raw'], 2);
227
228 $PROPchunk = 'PROP'.getid3_lib::BigEndian2String(strlen($PROPchunk) + 8, 4).$PROPchunk; // PROP chunk identifier + chunk length
229 return $PROPchunk;
230 }
231
232 /**
233 * @return string
234 */
235 public function GenerateCONTchunk() {
236 foreach ($this->tag_data as $key => $value) {
237 // limit each value to 0xFFFF bytes
238 $this->tag_data[$key] = substr($value, 0, 65535);
239 }
240
241 $CONTchunk = "\x00\x00"; // object version
242
243 $CONTchunk .= getid3_lib::BigEndian2String((!empty($this->tag_data['title']) ? strlen($this->tag_data['title']) : 0), 2);
244 $CONTchunk .= (!empty($this->tag_data['title']) ? strlen($this->tag_data['title']) : '');
245
246 $CONTchunk .= getid3_lib::BigEndian2String((!empty($this->tag_data['artist']) ? strlen($this->tag_data['artist']) : 0), 2);
247 $CONTchunk .= (!empty($this->tag_data['artist']) ? strlen($this->tag_data['artist']) : '');
248
249 $CONTchunk .= getid3_lib::BigEndian2String((!empty($this->tag_data['copyright']) ? strlen($this->tag_data['copyright']) : 0), 2);
250 $CONTchunk .= (!empty($this->tag_data['copyright']) ? strlen($this->tag_data['copyright']) : '');
251
252 $CONTchunk .= getid3_lib::BigEndian2String((!empty($this->tag_data['comment']) ? strlen($this->tag_data['comment']) : 0), 2);
253 $CONTchunk .= (!empty($this->tag_data['comment']) ? strlen($this->tag_data['comment']) : '');
254
255 if ($this->paddedlength > (strlen($CONTchunk) + 8)) {
256 $CONTchunk .= str_repeat("\x00", $this->paddedlength - strlen($CONTchunk) - 8);
257 }
258
259 $CONTchunk = 'CONT'.getid3_lib::BigEndian2String(strlen($CONTchunk) + 8, 4).$CONTchunk; // CONT chunk identifier + chunk length
260
261 return $CONTchunk;
262 }
263
264 /**
265 * @return bool
266 */
267 public function RemoveReal() {
268 // File MUST be writeable - CHMOD(646) at least
269 if (getID3::is_writable($this->filename) && is_file($this->filename) && ($fp_source = fopen($this->filename, 'r+b'))) {
270
271 // Initialize getID3 engine
272 $getID3 = new getID3;
273 $OldThisFileInfo = $getID3->analyze($this->filename);
274 if (empty($OldThisFileInfo['real']['chunks']) && !empty($OldThisFileInfo['real']['old_ra_header'])) {
275 $this->errors[] = 'Cannot remove Real tags from old-style file format';
276 fclose($fp_source);
277 return false;
278 }
279
280 if (empty($OldThisFileInfo['real']['chunks'])) {
281 $this->errors[] = 'Cannot remove Real tags because cannot find DATA chunk in file';
282 fclose($fp_source);
283 return false;
284 }
285 foreach ($OldThisFileInfo['real']['chunks'] as $chunknumber => $chunkarray) {
286 $oldChunkInfo[$chunkarray['name']] = $chunkarray;
287 }
288
289 if (empty($oldChunkInfo['CONT'])) {
290 // no existing CONT chunk
291 fclose($fp_source);
292 return true;
293 }
294
295 $BeforeOffset = $oldChunkInfo['CONT']['offset'];
296 $AfterOffset = $oldChunkInfo['CONT']['offset'] + $oldChunkInfo['CONT']['length'];
297 if ($tempfilename = tempnam(GETID3_TEMP_DIR, 'getID3')) {
298 if (getID3::is_writable($tempfilename) && is_file($tempfilename) && ($fp_temp = fopen($tempfilename, 'wb'))) {
299
300 rewind($fp_source);
301 fwrite($fp_temp, fread($fp_source, $BeforeOffset));
302 fseek($fp_source, $AfterOffset);
303 while ($buffer = fread($fp_source, $this->fread_buffer_size)) {
304 fwrite($fp_temp, $buffer, strlen($buffer));
305 }
306 fclose($fp_temp);
307
308 if (copy($tempfilename, $this->filename)) {
309 unlink($tempfilename);
310 fclose($fp_source);
311 return true;
312 }
313 unlink($tempfilename);
314 $this->errors[] = 'FAILED: copy('.$tempfilename.', '.$this->filename.')';
315
316 } else {
317 $this->errors[] = 'Could not fopen("'.$tempfilename.'", "wb")';
318 }
319 }
320 fclose($fp_source);
321 return false;
322 }
323 $this->errors[] = 'Could not fopen("'.$this->filename.'", "r+b")';
324 return false;
325 }
326
327 }