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