write.real.php 11 KB

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