write.real.php 11 KB

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