File indexing completed on 2024-12-22 05:33:15
0001 <?php 0002 ///////////////////////////////////////////////////////////////// 0003 /// getID3() by James Heinrich <info@getid3.org> // 0004 // available at http://getid3.sourceforge.net // 0005 // or http://www.getid3.org // 0006 // also https://github.com/JamesHeinrich/getID3 // 0007 ///////////////////////////////////////////////////////////////// 0008 // See readme.txt for more details // 0009 ///////////////////////////////////////////////////////////////// 0010 // // 0011 // write.real.php // 0012 // module for writing RealAudio/RealVideo tags // 0013 // dependencies: module.tag.real.php // 0014 // /// 0015 ///////////////////////////////////////////////////////////////// 0016 0017 class getid3_write_real 0018 { 0019 public $filename; 0020 public $tag_data = array(); 0021 public $fread_buffer_size = 32768; // read buffer size in bytes 0022 public $warnings = array(); // any non-critical errors will be stored here 0023 public $errors = array(); // any critical errors will be stored here 0024 public $paddedlength = 512; // minimum length of CONT tag in bytes 0025 0026 public function getid3_write_real() { 0027 return true; 0028 } 0029 0030 public function WriteReal() { 0031 // File MUST be writeable - CHMOD(646) at least 0032 if (is_writeable($this->filename) && is_file($this->filename) && ($fp_source = fopen($this->filename, 'r+b'))) { 0033 0034 // Initialize getID3 engine 0035 $getID3 = new getID3; 0036 $OldThisFileInfo = $getID3->analyze($this->filename); 0037 if (empty($OldThisFileInfo['real']['chunks']) && !empty($OldThisFileInfo['real']['old_ra_header'])) { 0038 $this->errors[] = 'Cannot write Real tags on old-style file format'; 0039 fclose($fp_source); 0040 return false; 0041 } 0042 0043 if (empty($OldThisFileInfo['real']['chunks'])) { 0044 $this->errors[] = 'Cannot write Real tags because cannot find DATA chunk in file'; 0045 fclose($fp_source); 0046 return false; 0047 } 0048 foreach ($OldThisFileInfo['real']['chunks'] as $chunknumber => $chunkarray) { 0049 $oldChunkInfo[$chunkarray['name']] = $chunkarray; 0050 } 0051 if (!empty($oldChunkInfo['CONT']['length'])) { 0052 $this->paddedlength = max($oldChunkInfo['CONT']['length'], $this->paddedlength); 0053 } 0054 0055 $new_CONT_tag_data = $this->GenerateCONTchunk(); 0056 $new_PROP_tag_data = $this->GeneratePROPchunk($OldThisFileInfo['real']['chunks'], $new_CONT_tag_data); 0057 $new__RMF_tag_data = $this->GenerateRMFchunk($OldThisFileInfo['real']['chunks']); 0058 0059 if (isset($oldChunkInfo['.RMF']['length']) && ($oldChunkInfo['.RMF']['length'] == strlen($new__RMF_tag_data))) { 0060 fseek($fp_source, $oldChunkInfo['.RMF']['offset']); 0061 fwrite($fp_source, $new__RMF_tag_data); 0062 } else { 0063 $this->errors[] = 'new .RMF tag ('.strlen($new__RMF_tag_data).' bytes) different length than old .RMF tag ('.$oldChunkInfo['.RMF']['length'].' bytes)'; 0064 fclose($fp_source); 0065 return false; 0066 } 0067 0068 if (isset($oldChunkInfo['PROP']['length']) && ($oldChunkInfo['PROP']['length'] == strlen($new_PROP_tag_data))) { 0069 fseek($fp_source, $oldChunkInfo['PROP']['offset']); 0070 fwrite($fp_source, $new_PROP_tag_data); 0071 } else { 0072 $this->errors[] = 'new PROP tag ('.strlen($new_PROP_tag_data).' bytes) different length than old PROP tag ('.$oldChunkInfo['PROP']['length'].' bytes)'; 0073 fclose($fp_source); 0074 return false; 0075 } 0076 0077 if (isset($oldChunkInfo['CONT']['length']) && ($oldChunkInfo['CONT']['length'] == strlen($new_CONT_tag_data))) { 0078 0079 // new data length is same as old data length - just overwrite 0080 fseek($fp_source, $oldChunkInfo['CONT']['offset']); 0081 fwrite($fp_source, $new_CONT_tag_data); 0082 fclose($fp_source); 0083 return true; 0084 0085 } else { 0086 0087 if (empty($oldChunkInfo['CONT'])) { 0088 // no existing CONT chunk 0089 $BeforeOffset = $oldChunkInfo['DATA']['offset']; 0090 $AfterOffset = $oldChunkInfo['DATA']['offset']; 0091 } else { 0092 // new data is longer than old data 0093 $BeforeOffset = $oldChunkInfo['CONT']['offset']; 0094 $AfterOffset = $oldChunkInfo['CONT']['offset'] + $oldChunkInfo['CONT']['length']; 0095 } 0096 if ($tempfilename = tempnam(GETID3_TEMP_DIR, 'getID3')) { 0097 if (is_writable($tempfilename) && is_file($tempfilename) && ($fp_temp = fopen($tempfilename, 'wb'))) { 0098 0099 rewind($fp_source); 0100 fwrite($fp_temp, fread($fp_source, $BeforeOffset)); 0101 fwrite($fp_temp, $new_CONT_tag_data); 0102 fseek($fp_source, $AfterOffset); 0103 while ($buffer = fread($fp_source, $this->fread_buffer_size)) { 0104 fwrite($fp_temp, $buffer, strlen($buffer)); 0105 } 0106 fclose($fp_temp); 0107 0108 if (copy($tempfilename, $this->filename)) { 0109 unlink($tempfilename); 0110 fclose($fp_source); 0111 return true; 0112 } 0113 unlink($tempfilename); 0114 $this->errors[] = 'FAILED: copy('.$tempfilename.', '.$this->filename.')'; 0115 0116 } else { 0117 $this->errors[] = 'Could not fopen("'.$tempfilename.'", "wb")'; 0118 } 0119 } 0120 fclose($fp_source); 0121 return false; 0122 0123 } 0124 0125 } 0126 $this->errors[] = 'Could not fopen("'.$this->filename.'", "r+b")'; 0127 return false; 0128 } 0129 0130 public function GenerateRMFchunk(&$chunks) { 0131 $oldCONTexists = false; 0132 foreach ($chunks as $key => $chunk) { 0133 $chunkNameKeys[$chunk['name']] = $key; 0134 if ($chunk['name'] == 'CONT') { 0135 $oldCONTexists = true; 0136 } 0137 } 0138 $newHeadersCount = $chunks[$chunkNameKeys['.RMF']]['headers_count'] + ($oldCONTexists ? 0 : 1); 0139 0140 $RMFchunk = "\x00\x00"; // object version 0141 $RMFchunk .= getid3_lib::BigEndian2String($chunks[$chunkNameKeys['.RMF']]['file_version'], 4); 0142 $RMFchunk .= getid3_lib::BigEndian2String($newHeadersCount, 4); 0143 0144 $RMFchunk = '.RMF'.getid3_lib::BigEndian2String(strlen($RMFchunk) + 8, 4).$RMFchunk; // .RMF chunk identifier + chunk length 0145 return $RMFchunk; 0146 } 0147 0148 public function GeneratePROPchunk(&$chunks, &$new_CONT_tag_data) { 0149 $old_CONT_length = 0; 0150 $old_DATA_offset = 0; 0151 $old_INDX_offset = 0; 0152 foreach ($chunks as $key => $chunk) { 0153 $chunkNameKeys[$chunk['name']] = $key; 0154 if ($chunk['name'] == 'CONT') { 0155 $old_CONT_length = $chunk['length']; 0156 } elseif ($chunk['name'] == 'DATA') { 0157 if (!$old_DATA_offset) { 0158 $old_DATA_offset = $chunk['offset']; 0159 } 0160 } elseif ($chunk['name'] == 'INDX') { 0161 if (!$old_INDX_offset) { 0162 $old_INDX_offset = $chunk['offset']; 0163 } 0164 } 0165 } 0166 $CONTdelta = strlen($new_CONT_tag_data) - $old_CONT_length; 0167 0168 $PROPchunk = "\x00\x00"; // object version 0169 $PROPchunk .= getid3_lib::BigEndian2String($chunks[$chunkNameKeys['PROP']]['max_bit_rate'], 4); 0170 $PROPchunk .= getid3_lib::BigEndian2String($chunks[$chunkNameKeys['PROP']]['avg_bit_rate'], 4); 0171 $PROPchunk .= getid3_lib::BigEndian2String($chunks[$chunkNameKeys['PROP']]['max_packet_size'], 4); 0172 $PROPchunk .= getid3_lib::BigEndian2String($chunks[$chunkNameKeys['PROP']]['avg_packet_size'], 4); 0173 $PROPchunk .= getid3_lib::BigEndian2String($chunks[$chunkNameKeys['PROP']]['num_packets'], 4); 0174 $PROPchunk .= getid3_lib::BigEndian2String($chunks[$chunkNameKeys['PROP']]['duration'], 4); 0175 $PROPchunk .= getid3_lib::BigEndian2String($chunks[$chunkNameKeys['PROP']]['preroll'], 4); 0176 $PROPchunk .= getid3_lib::BigEndian2String(max(0, $old_INDX_offset + $CONTdelta), 4); 0177 $PROPchunk .= getid3_lib::BigEndian2String(max(0, $old_DATA_offset + $CONTdelta), 4); 0178 $PROPchunk .= getid3_lib::BigEndian2String($chunks[$chunkNameKeys['PROP']]['num_streams'], 2); 0179 $PROPchunk .= getid3_lib::BigEndian2String($chunks[$chunkNameKeys['PROP']]['flags_raw'], 2); 0180 0181 $PROPchunk = 'PROP'.getid3_lib::BigEndian2String(strlen($PROPchunk) + 8, 4).$PROPchunk; // PROP chunk identifier + chunk length 0182 return $PROPchunk; 0183 } 0184 0185 public function GenerateCONTchunk() { 0186 foreach ($this->tag_data as $key => $value) { 0187 // limit each value to 0xFFFF bytes 0188 $this->tag_data[$key] = substr($value, 0, 65535); 0189 } 0190 0191 $CONTchunk = "\x00\x00"; // object version 0192 0193 $CONTchunk .= getid3_lib::BigEndian2String((!empty($this->tag_data['title']) ? strlen($this->tag_data['title']) : 0), 2); 0194 $CONTchunk .= (!empty($this->tag_data['title']) ? strlen($this->tag_data['title']) : ''); 0195 0196 $CONTchunk .= getid3_lib::BigEndian2String((!empty($this->tag_data['artist']) ? strlen($this->tag_data['artist']) : 0), 2); 0197 $CONTchunk .= (!empty($this->tag_data['artist']) ? strlen($this->tag_data['artist']) : ''); 0198 0199 $CONTchunk .= getid3_lib::BigEndian2String((!empty($this->tag_data['copyright']) ? strlen($this->tag_data['copyright']) : 0), 2); 0200 $CONTchunk .= (!empty($this->tag_data['copyright']) ? strlen($this->tag_data['copyright']) : ''); 0201 0202 $CONTchunk .= getid3_lib::BigEndian2String((!empty($this->tag_data['comment']) ? strlen($this->tag_data['comment']) : 0), 2); 0203 $CONTchunk .= (!empty($this->tag_data['comment']) ? strlen($this->tag_data['comment']) : ''); 0204 0205 if ($this->paddedlength > (strlen($CONTchunk) + 8)) { 0206 $CONTchunk .= str_repeat("\x00", $this->paddedlength - strlen($CONTchunk) - 8); 0207 } 0208 0209 $CONTchunk = 'CONT'.getid3_lib::BigEndian2String(strlen($CONTchunk) + 8, 4).$CONTchunk; // CONT chunk identifier + chunk length 0210 0211 return $CONTchunk; 0212 } 0213 0214 public function RemoveReal() { 0215 // File MUST be writeable - CHMOD(646) at least 0216 if (is_writeable($this->filename) && is_file($this->filename) && ($fp_source = fopen($this->filename, 'r+b'))) { 0217 0218 // Initialize getID3 engine 0219 $getID3 = new getID3; 0220 $OldThisFileInfo = $getID3->analyze($this->filename); 0221 if (empty($OldThisFileInfo['real']['chunks']) && !empty($OldThisFileInfo['real']['old_ra_header'])) { 0222 $this->errors[] = 'Cannot remove Real tags from old-style file format'; 0223 fclose($fp_source); 0224 return false; 0225 } 0226 0227 if (empty($OldThisFileInfo['real']['chunks'])) { 0228 $this->errors[] = 'Cannot remove Real tags because cannot find DATA chunk in file'; 0229 fclose($fp_source); 0230 return false; 0231 } 0232 foreach ($OldThisFileInfo['real']['chunks'] as $chunknumber => $chunkarray) { 0233 $oldChunkInfo[$chunkarray['name']] = $chunkarray; 0234 } 0235 0236 if (empty($oldChunkInfo['CONT'])) { 0237 // no existing CONT chunk 0238 fclose($fp_source); 0239 return true; 0240 } 0241 0242 $BeforeOffset = $oldChunkInfo['CONT']['offset']; 0243 $AfterOffset = $oldChunkInfo['CONT']['offset'] + $oldChunkInfo['CONT']['length']; 0244 if ($tempfilename = tempnam(GETID3_TEMP_DIR, 'getID3')) { 0245 if (is_writable($tempfilename) && is_file($tempfilename) && ($fp_temp = fopen($tempfilename, 'wb'))) { 0246 0247 rewind($fp_source); 0248 fwrite($fp_temp, fread($fp_source, $BeforeOffset)); 0249 fseek($fp_source, $AfterOffset); 0250 while ($buffer = fread($fp_source, $this->fread_buffer_size)) { 0251 fwrite($fp_temp, $buffer, strlen($buffer)); 0252 } 0253 fclose($fp_temp); 0254 0255 if (copy($tempfilename, $this->filename)) { 0256 unlink($tempfilename); 0257 fclose($fp_source); 0258 return true; 0259 } 0260 unlink($tempfilename); 0261 $this->errors[] = 'FAILED: copy('.$tempfilename.', '.$this->filename.')'; 0262 0263 } else { 0264 $this->errors[] = 'Could not fopen("'.$tempfilename.'", "wb")'; 0265 } 0266 } 0267 fclose($fp_source); 0268 return false; 0269 } 0270 $this->errors[] = 'Could not fopen("'.$this->filename.'", "r+b")'; 0271 return false; 0272 } 0273 0274 }