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 }