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 /////////////////////////////////////////////////////////////////
12 // module for writing RealAudio/RealVideo tags //
13 // dependencies: module.tag.real.php //
15 /////////////////////////////////////////////////////////////////
17 class getid3_write_real
20 public $tag_data = array();
21 public $fread_buffer_size = 32768; // read buffer size in bytes
22 public $warnings = array(); // any non-critical errors will be stored here
23 public $errors = array(); // any critical errors will be stored here
24 public $paddedlength = 512; // minimum length of CONT tag in bytes
26 public function __construct() {
30 public function WriteReal() {
31 // File MUST be writeable - CHMOD(646) at least
32 if (is_writeable($this->filename
) && is_file($this->filename
) && ($fp_source = fopen($this->filename
, 'r+b'))) {
34 // Initialize getID3 engine
36 $OldThisFileInfo = $getID3->analyze($this->filename
);
37 if (empty($OldThisFileInfo['real']['chunks']) && !empty($OldThisFileInfo['real']['old_ra_header'])) {
38 $this->errors
[] = 'Cannot write Real tags on old-style file format';
43 if (empty($OldThisFileInfo['real']['chunks'])) {
44 $this->errors
[] = 'Cannot write Real tags because cannot find DATA chunk in file';
48 foreach ($OldThisFileInfo['real']['chunks'] as $chunknumber => $chunkarray) {
49 $oldChunkInfo[$chunkarray['name']] = $chunkarray;
51 if (!empty($oldChunkInfo['CONT']['length'])) {
52 $this->paddedlength
= max($oldChunkInfo['CONT']['length'], $this->paddedlength
);
55 $new_CONT_tag_data = $this->GenerateCONTchunk();
56 $new_PROP_tag_data = $this->GeneratePROPchunk($OldThisFileInfo['real']['chunks'], $new_CONT_tag_data);
57 $new__RMF_tag_data = $this->GenerateRMFchunk($OldThisFileInfo['real']['chunks']);
59 if (isset($oldChunkInfo['.RMF']['length']) && ($oldChunkInfo['.RMF']['length'] == strlen($new__RMF_tag_data))) {
60 fseek($fp_source, $oldChunkInfo['.RMF']['offset']);
61 fwrite($fp_source, $new__RMF_tag_data);
63 $this->errors
[] = 'new .RMF tag ('.strlen($new__RMF_tag_data).' bytes) different length than old .RMF tag ('.$oldChunkInfo['.RMF']['length'].' bytes)';
68 if (isset($oldChunkInfo['PROP']['length']) && ($oldChunkInfo['PROP']['length'] == strlen($new_PROP_tag_data))) {
69 fseek($fp_source, $oldChunkInfo['PROP']['offset']);
70 fwrite($fp_source, $new_PROP_tag_data);
72 $this->errors
[] = 'new PROP tag ('.strlen($new_PROP_tag_data).' bytes) different length than old PROP tag ('.$oldChunkInfo['PROP']['length'].' bytes)';
77 if (isset($oldChunkInfo['CONT']['length']) && ($oldChunkInfo['CONT']['length'] == strlen($new_CONT_tag_data))) {
79 // new data length is same as old data length - just overwrite
80 fseek($fp_source, $oldChunkInfo['CONT']['offset']);
81 fwrite($fp_source, $new_CONT_tag_data);
87 if (empty($oldChunkInfo['CONT'])) {
88 // no existing CONT chunk
89 $BeforeOffset = $oldChunkInfo['DATA']['offset'];
90 $AfterOffset = $oldChunkInfo['DATA']['offset'];
92 // new data is longer than old data
93 $BeforeOffset = $oldChunkInfo['CONT']['offset'];
94 $AfterOffset = $oldChunkInfo['CONT']['offset'] +
$oldChunkInfo['CONT']['length'];
96 if ($tempfilename = tempnam(GETID3_TEMP_DIR
, 'getID3')) {
97 if (is_writable($tempfilename) && is_file($tempfilename) && ($fp_temp = fopen($tempfilename, 'wb'))) {
100 fwrite($fp_temp, fread($fp_source, $BeforeOffset));
101 fwrite($fp_temp, $new_CONT_tag_data);
102 fseek($fp_source, $AfterOffset);
103 while ($buffer = fread($fp_source, $this->fread_buffer_size
)) {
104 fwrite($fp_temp, $buffer, strlen($buffer));
108 if (copy($tempfilename, $this->filename
)) {
109 unlink($tempfilename);
113 unlink($tempfilename);
114 $this->errors
[] = 'FAILED: copy('.$tempfilename.', '.$this->filename
.')';
117 $this->errors
[] = 'Could not fopen("'.$tempfilename.'", "wb")';
126 $this->errors
[] = 'Could not fopen("'.$this->filename
.'", "r+b")';
130 public function GenerateRMFchunk(&$chunks) {
131 $oldCONTexists = false;
132 foreach ($chunks as $key => $chunk) {
133 $chunkNameKeys[$chunk['name']] = $key;
134 if ($chunk['name'] == 'CONT') {
135 $oldCONTexists = true;
138 $newHeadersCount = $chunks[$chunkNameKeys['.RMF']]['headers_count'] +
($oldCONTexists ?
0 : 1);
140 $RMFchunk = "\x00\x00"; // object version
141 $RMFchunk .= getid3_lib
::BigEndian2String($chunks[$chunkNameKeys['.RMF']]['file_version'], 4);
142 $RMFchunk .= getid3_lib
::BigEndian2String($newHeadersCount, 4);
144 $RMFchunk = '.RMF'.getid3_lib
::BigEndian2String(strlen($RMFchunk) +
8, 4).$RMFchunk; // .RMF chunk identifier + chunk length
148 public function GeneratePROPchunk(&$chunks, &$new_CONT_tag_data) {
149 $old_CONT_length = 0;
150 $old_DATA_offset = 0;
151 $old_INDX_offset = 0;
152 foreach ($chunks as $key => $chunk) {
153 $chunkNameKeys[$chunk['name']] = $key;
154 if ($chunk['name'] == 'CONT') {
155 $old_CONT_length = $chunk['length'];
156 } elseif ($chunk['name'] == 'DATA') {
157 if (!$old_DATA_offset) {
158 $old_DATA_offset = $chunk['offset'];
160 } elseif ($chunk['name'] == 'INDX') {
161 if (!$old_INDX_offset) {
162 $old_INDX_offset = $chunk['offset'];
166 $CONTdelta = strlen($new_CONT_tag_data) - $old_CONT_length;
168 $PROPchunk = "\x00\x00"; // object version
169 $PROPchunk .= getid3_lib
::BigEndian2String($chunks[$chunkNameKeys['PROP']]['max_bit_rate'], 4);
170 $PROPchunk .= getid3_lib
::BigEndian2String($chunks[$chunkNameKeys['PROP']]['avg_bit_rate'], 4);
171 $PROPchunk .= getid3_lib
::BigEndian2String($chunks[$chunkNameKeys['PROP']]['max_packet_size'], 4);
172 $PROPchunk .= getid3_lib
::BigEndian2String($chunks[$chunkNameKeys['PROP']]['avg_packet_size'], 4);
173 $PROPchunk .= getid3_lib
::BigEndian2String($chunks[$chunkNameKeys['PROP']]['num_packets'], 4);
174 $PROPchunk .= getid3_lib
::BigEndian2String($chunks[$chunkNameKeys['PROP']]['duration'], 4);
175 $PROPchunk .= getid3_lib
::BigEndian2String($chunks[$chunkNameKeys['PROP']]['preroll'], 4);
176 $PROPchunk .= getid3_lib
::BigEndian2String(max(0, $old_INDX_offset +
$CONTdelta), 4);
177 $PROPchunk .= getid3_lib
::BigEndian2String(max(0, $old_DATA_offset +
$CONTdelta), 4);
178 $PROPchunk .= getid3_lib
::BigEndian2String($chunks[$chunkNameKeys['PROP']]['num_streams'], 2);
179 $PROPchunk .= getid3_lib
::BigEndian2String($chunks[$chunkNameKeys['PROP']]['flags_raw'], 2);
181 $PROPchunk = 'PROP'.getid3_lib
::BigEndian2String(strlen($PROPchunk) +
8, 4).$PROPchunk; // PROP chunk identifier + chunk length
185 public function GenerateCONTchunk() {
186 foreach ($this->tag_data
as $key => $value) {
187 // limit each value to 0xFFFF bytes
188 $this->tag_data
[$key] = substr($value, 0, 65535);
191 $CONTchunk = "\x00\x00"; // object version
193 $CONTchunk .= getid3_lib
::BigEndian2String((!empty($this->tag_data
['title']) ?
strlen($this->tag_data
['title']) : 0), 2);
194 $CONTchunk .= (!empty($this->tag_data
['title']) ?
strlen($this->tag_data
['title']) : '');
196 $CONTchunk .= getid3_lib
::BigEndian2String((!empty($this->tag_data
['artist']) ?
strlen($this->tag_data
['artist']) : 0), 2);
197 $CONTchunk .= (!empty($this->tag_data
['artist']) ?
strlen($this->tag_data
['artist']) : '');
199 $CONTchunk .= getid3_lib
::BigEndian2String((!empty($this->tag_data
['copyright']) ?
strlen($this->tag_data
['copyright']) : 0), 2);
200 $CONTchunk .= (!empty($this->tag_data
['copyright']) ?
strlen($this->tag_data
['copyright']) : '');
202 $CONTchunk .= getid3_lib
::BigEndian2String((!empty($this->tag_data
['comment']) ?
strlen($this->tag_data
['comment']) : 0), 2);
203 $CONTchunk .= (!empty($this->tag_data
['comment']) ?
strlen($this->tag_data
['comment']) : '');
205 if ($this->paddedlength
> (strlen($CONTchunk) +
8)) {
206 $CONTchunk .= str_repeat("\x00", $this->paddedlength
- strlen($CONTchunk) - 8);
209 $CONTchunk = 'CONT'.getid3_lib
::BigEndian2String(strlen($CONTchunk) +
8, 4).$CONTchunk; // CONT chunk identifier + chunk length
214 public function RemoveReal() {
215 // File MUST be writeable - CHMOD(646) at least
216 if (is_writeable($this->filename
) && is_file($this->filename
) && ($fp_source = fopen($this->filename
, 'r+b'))) {
218 // Initialize getID3 engine
219 $getID3 = new getID3
;
220 $OldThisFileInfo = $getID3->analyze($this->filename
);
221 if (empty($OldThisFileInfo['real']['chunks']) && !empty($OldThisFileInfo['real']['old_ra_header'])) {
222 $this->errors
[] = 'Cannot remove Real tags from old-style file format';
227 if (empty($OldThisFileInfo['real']['chunks'])) {
228 $this->errors
[] = 'Cannot remove Real tags because cannot find DATA chunk in file';
232 foreach ($OldThisFileInfo['real']['chunks'] as $chunknumber => $chunkarray) {
233 $oldChunkInfo[$chunkarray['name']] = $chunkarray;
236 if (empty($oldChunkInfo['CONT'])) {
237 // no existing CONT chunk
242 $BeforeOffset = $oldChunkInfo['CONT']['offset'];
243 $AfterOffset = $oldChunkInfo['CONT']['offset'] +
$oldChunkInfo['CONT']['length'];
244 if ($tempfilename = tempnam(GETID3_TEMP_DIR
, 'getID3')) {
245 if (is_writable($tempfilename) && is_file($tempfilename) && ($fp_temp = fopen($tempfilename, 'wb'))) {
248 fwrite($fp_temp, fread($fp_source, $BeforeOffset));
249 fseek($fp_source, $AfterOffset);
250 while ($buffer = fread($fp_source, $this->fread_buffer_size
)) {
251 fwrite($fp_temp, $buffer, strlen($buffer));
255 if (copy($tempfilename, $this->filename
)) {
256 unlink($tempfilename);
260 unlink($tempfilename);
261 $this->errors
[] = 'FAILED: copy('.$tempfilename.', '.$this->filename
.')';
264 $this->errors
[] = 'Could not fopen("'.$tempfilename.'", "wb")';
270 $this->errors
[] = 'Could not fopen("'.$this->filename
.'", "r+b")';