- Timestamp:
- Jul 7, 2009, 10:27:37 PM (15 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
extensions/charlies_content/getid3/getid3/write.apetag.php
r3318 r3544 1 1 <?php 2 // +----------------------------------------------------------------------+ 3 // | PHP version 5 | 4 // +----------------------------------------------------------------------+ 5 // | Copyright (c) 2002-2006 James Heinrich, Allan Hansen | 6 // +----------------------------------------------------------------------+ 7 // | This source file is subject to version 2 of the GPL license, | 8 // | that is bundled with this package in the file license.txt and is | 9 // | available through the world-wide-web at the following url: | 10 // | http://www.gnu.org/copyleft/gpl.html | 11 // +----------------------------------------------------------------------+ 12 // | getID3() - http://getid3.sourceforge.net or http://www.getid3.org | 13 // +----------------------------------------------------------------------+ 14 // | Authors: James Heinrich <infoØgetid3*org> | 15 // | Allan Hansen <ahØartemis*dk> | 16 // +----------------------------------------------------------------------+ 17 // | write.apetag.php | 18 // | writing module for ape tags | 19 // | dependencies: module.tag.apetag.php | 20 // | dependencies: module.tag.id3v1.php | 21 // | dependencies: module.tag.lyrics3.php | 22 // +----------------------------------------------------------------------+ 23 // 24 // $Id$ 25 26 27 class getid3_write_apetag extends getid3_handler_write 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.apetag.php // 11 // module for writing APE tags // 12 // dependencies: module.tag.apetag.php // 13 // /// 14 ///////////////////////////////////////////////////////////////// 15 16 17 getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.apetag.php', __FILE__, true); 18 19 class getid3_write_apetag 28 20 { 29 30 public $comments; 31 32 33 public function read() { 34 35 $engine = new getid3; 36 $engine->filename = $this->filename; 37 $engine->fp = fopen($this->filename, 'rb'); 38 $engine->include_module('tag.apetag'); 39 40 $tag = new getid3_apetag($engine); 41 $tag->Analyze(); 42 43 if (!isset($engine->info['ape']['comments'])) { 44 return; 45 } 46 47 $this->comments = $engine->info['ape']['comments']; 48 49 // convert single element arrays to string 50 foreach ($this->comments as $key => $value) { 51 if (sizeof($value) == 1) { 52 $this->comments[$key] = $value[0]; 53 } 54 } 55 56 return true; 57 } 58 59 60 public function write() { 61 62 // remove existing apetag 63 $this->remove(); 64 65 $engine = new getid3; 66 $engine->filename = $this->filename; 67 $engine->fp = fopen($this->filename, 'rb'); 68 $engine->include_module('tag.id3v1'); 69 $engine->include_module('tag.lyrics3'); 70 71 $tag = new getid3_id3v1($engine); 72 $tag->Analyze(); 73 74 $tag = new getid3_lyrics3($engine); 75 $tag->Analyze(); 76 77 $apetag = $this->generate_tag(); 78 79 if (!$fp = @fopen($this->filename, 'a+b')) { 80 throw new getid3_exception('Could not open a+b: ' . $this->filename); 81 } 82 83 // init: audio ends at eof 84 $post_audio_offset = filesize($this->filename); 85 86 // lyrics3 tag present 87 if (@$engine->info['lyrics3']['tag_offset_start']) { 88 89 // audio ends before lyrics3 tag 90 $post_audio_offset = @$engine->info['lyrics3']['tag_offset_start']; 91 } 92 93 // id3v1 tag present 94 elseif (@$engine->info['id3v1']['tag_offset_start']) { 95 96 // audio ends before id3v1 tag 97 $post_audio_offset = $engine->info['id3v1']['tag_offset_start']; 98 } 99 100 // seek to end of audio data 101 fseek($fp, $post_audio_offset, SEEK_SET); 102 103 // save data after audio data 104 $post_audio_data = ''; 105 if (filesize($this->filename) > $post_audio_offset) { 106 $post_audio_data = fread($fp, filesize($this->filename) - $post_audio_offset); 107 } 108 109 // truncate file before start of new apetag 110 fseek($fp, $post_audio_offset, SEEK_SET); 111 ftruncate($fp, ftell($fp)); 112 113 // write new apetag 114 fwrite($fp, $apetag, strlen($apetag)); 115 116 // rewrite data after audio 117 if (!empty($post_audio_data)) { 118 fwrite($fp, $post_audio_data, strlen($post_audio_data)); 119 } 120 121 fclose($fp); 122 clearstatcache(); 123 124 return true; 125 } 126 127 128 public function remove() { 129 130 $engine = new getid3; 131 $engine->filename = $this->filename; 132 $engine->fp = fopen($this->filename, 'rb'); 133 $engine->include_module('tag.apetag'); 134 135 $tag = new getid3_apetag($engine); 136 $tag->Analyze(); 137 138 if (isset($engine->info['ape']['tag_offset_start']) && isset($engine->info['ape']['tag_offset_end'])) { 139 140 if (!$fp = @fopen($this->filename, 'a+b')) { 141 throw new getid3_exception('Could not open a+b: ' . $this->filename); 142 } 143 144 // get data after apetag 145 if (filesize($this->filename) > $engine->info['ape']['tag_offset_end']) { 146 fseek($fp, $engine->info['ape']['tag_offset_end'], SEEK_SET); 147 $data_after_ape = fread($fp, filesize($this->filename) - $engine->info['ape']['tag_offset_end']); 148 } 149 150 // truncate file before start of apetag 151 ftruncate($fp, $engine->info['ape']['tag_offset_start']); 152 153 // rewrite data after apetag 154 if (isset($data_after_ape)) { 155 fseek($fp, $engine->info['ape']['tag_offset_start'], SEEK_SET); 156 fwrite($fp, $data_after_ape, strlen($data_after_ape)); 157 } 158 159 fclose($fp); 160 clearstatcache(); 161 } 162 163 // success when removing non-existant tag 164 return true; 165 } 166 167 168 protected function generate_tag() { 169 170 // NOTE: All data passed to this function must be UTF-8 format 171 172 $items = array(); 173 if (!is_array($this->comments)) { 174 throw new getid3_exception('Cannot write empty tag, use remove() instead.'); 175 } 176 177 foreach ($this->comments as $key => $values) { 178 179 // http://www.personal.uni-jena.de/~pfk/mpp/sv8/apekey.html 180 // A case-insensitive vobiscomment field name that may consist of ASCII 0x20 through 0x7E. 181 // ASCII 0x41 through 0x5A inclusive (A-Z) is to be considered equivalent to ASCII 0x61 through 0x7A inclusive (a-z). 182 if (preg_match("/[^\x20-\x7E]/", $key)) { 183 throw new getid3_exception('Field name "' . $key . '" contains invalid character(s).'); 184 } 185 186 $key = strtolower($key); 187 188 // convert single value comment to array 189 if (!is_array($values)) { 190 $values = array ($values); 191 } 192 193 $value_array = array (); 194 foreach ($values as $value) { 195 $value_array[] = str_replace("\x00", '', $value); 196 } 197 $value_string = implode("\x00", $value_array); 198 199 // length of the assigned value in bytes 200 $tag_item = getid3_lib::LittleEndian2String(strlen($value_string), 4); 201 202 $tag_item .= "\x00\x00\x00\x00" . $key . "\x00" . $value_string; 203 204 $items[] = $tag_item; 205 } 206 207 return $this->generate_header_footer($items, true) . implode('', $items) . $this->generate_header_footer($items, false); 208 } 209 210 211 protected function generate_header_footer(&$items, $is_header=false) { 212 213 $comments_length = 0; 214 foreach ($items as $item_data) { 215 $comments_length += strlen($item_data); 216 } 217 218 $header = 'APETAGEX'; 219 $header .= getid3_lib::LittleEndian2String(2000, 4); 220 $header .= getid3_lib::LittleEndian2String(32 + $comments_length, 4); 221 $header .= getid3_lib::LittleEndian2String(count($items), 4); 222 $header .= $this->generate_flags(true, true, $is_header, 0, false); 223 $header .= str_repeat("\x00", 8); 224 225 return $header; 226 } 227 228 229 protected function generate_flags($header=true, $footer=true, $is_header=false, $encoding_id=0, $read_only=false) { 230 231 $flags = array_fill(0, 4, 0); 232 233 // Tag contains a header 234 if ($header) { 235 $flags[0] |= 0x80; 236 } 237 238 // Tag contains no footer 239 if (!$footer) { 240 $flags[0] |= 0x40; 241 } 242 243 // This is the header, not the footer 244 if ($is_header) { 245 $flags[0] |= 0x20; 246 } 247 248 // 0: Item contains text information coded in UTF-8 249 // 1: Item contains binary information °) 250 // 2: Item is a locator of external stored information °°) 251 // 3: reserved 252 $flags[3] |= ($encoding_id << 1); 253 254 // Tag or Item is Read Only 255 if ($read_only) { 256 $flags[3] |= 0x01; 257 } 258 259 return chr($flags[3]).chr($flags[2]).chr($flags[1]).chr($flags[0]); 260 } 261 21 22 var $filename; 23 var $tag_data; 24 var $always_preserve_replaygain = true; // ReplayGain / MP3gain tags will be copied from old tag even if not passed in data 25 var $warnings = array(); // any non-critical errors will be stored here 26 var $errors = array(); // any critical errors will be stored here 27 28 function getid3_write_apetag() { 29 return true; 30 } 31 32 function WriteAPEtag() { 33 // NOTE: All data passed to this function must be UTF-8 format 34 35 $getID3 = new getID3; 36 $ThisFileInfo = $getID3->analyze($this->filename); 37 38 if (isset($ThisFileInfo['ape']['tag_offset_start']) && isset($ThisFileInfo['lyrics3']['tag_offset_end'])) { 39 if ($ThisFileInfo['ape']['tag_offset_start'] >= $ThisFileInfo['lyrics3']['tag_offset_end']) { 40 // Current APE tag between Lyrics3 and ID3v1/EOF 41 // This break Lyrics3 functionality 42 if (!$this->DeleteAPEtag()) { 43 return false; 44 } 45 $ThisFileInfo = $getID3->analyze($this->filename); 46 } 47 } 48 49 if ($this->always_preserve_replaygain) { 50 $ReplayGainTagsToPreserve = array('mp3gain_minmax', 'mp3gain_album_minmax', 'mp3gain_undo', 'replaygain_track_peak', 'replaygain_track_gain', 'replaygain_album_peak', 'replaygain_album_gain'); 51 foreach ($ReplayGainTagsToPreserve as $rg_key) { 52 if (isset($ThisFileInfo['ape']['items'][strtolower($rg_key)]['data'][0]) && !isset($this->tag_data[strtoupper($rg_key)][0])) { 53 $this->tag_data[strtoupper($rg_key)][0] = $ThisFileInfo['ape']['items'][strtolower($rg_key)]['data'][0]; 54 } 55 } 56 } 57 58 if ($APEtag = $this->GenerateAPEtag()) { 59 if ($fp = @fopen($this->filename, 'a+b')) { 60 $oldignoreuserabort = ignore_user_abort(true); 61 flock($fp, LOCK_EX); 62 63 $PostAPEdataOffset = $ThisFileInfo['avdataend']; 64 if (isset($ThisFileInfo['ape']['tag_offset_end'])) { 65 $PostAPEdataOffset = max($PostAPEdataOffset, $ThisFileInfo['ape']['tag_offset_end']); 66 } 67 if (isset($ThisFileInfo['lyrics3']['tag_offset_start'])) { 68 $PostAPEdataOffset = max($PostAPEdataOffset, $ThisFileInfo['lyrics3']['tag_offset_start']); 69 } 70 fseek($fp, $PostAPEdataOffset, SEEK_SET); 71 $PostAPEdata = ''; 72 if ($ThisFileInfo['filesize'] > $PostAPEdataOffset) { 73 $PostAPEdata = fread($fp, $ThisFileInfo['filesize'] - $PostAPEdataOffset); 74 } 75 76 fseek($fp, $PostAPEdataOffset, SEEK_SET); 77 if (isset($ThisFileInfo['ape']['tag_offset_start'])) { 78 fseek($fp, $ThisFileInfo['ape']['tag_offset_start'], SEEK_SET); 79 } 80 ftruncate($fp, ftell($fp)); 81 fwrite($fp, $APEtag, strlen($APEtag)); 82 if (!empty($PostAPEdata)) { 83 fwrite($fp, $PostAPEdata, strlen($PostAPEdata)); 84 } 85 flock($fp, LOCK_UN); 86 fclose($fp); 87 ignore_user_abort($oldignoreuserabort); 88 return true; 89 90 } 91 return false; 92 } 93 return false; 94 } 95 96 function DeleteAPEtag() { 97 $getID3 = new getID3; 98 $ThisFileInfo = $getID3->analyze($this->filename); 99 if (isset($ThisFileInfo['ape']['tag_offset_start']) && isset($ThisFileInfo['ape']['tag_offset_end'])) { 100 if ($fp = @fopen($this->filename, 'a+b')) { 101 102 flock($fp, LOCK_EX); 103 $oldignoreuserabort = ignore_user_abort(true); 104 105 fseek($fp, $ThisFileInfo['ape']['tag_offset_end'], SEEK_SET); 106 $DataAfterAPE = ''; 107 if ($ThisFileInfo['filesize'] > $ThisFileInfo['ape']['tag_offset_end']) { 108 $DataAfterAPE = fread($fp, $ThisFileInfo['filesize'] - $ThisFileInfo['ape']['tag_offset_end']); 109 } 110 111 ftruncate($fp, $ThisFileInfo['ape']['tag_offset_start']); 112 fseek($fp, $ThisFileInfo['ape']['tag_offset_start'], SEEK_SET); 113 114 if (!empty($DataAfterAPE)) { 115 fwrite($fp, $DataAfterAPE, strlen($DataAfterAPE)); 116 } 117 118 flock($fp, LOCK_UN); 119 fclose($fp); 120 ignore_user_abort($oldignoreuserabort); 121 122 return true; 123 124 } 125 return false; 126 } 127 return true; 128 } 129 130 131 function GenerateAPEtag() { 132 // NOTE: All data passed to this function must be UTF-8 format 133 134 $items = array(); 135 if (!is_array($this->tag_data)) { 136 return false; 137 } 138 foreach ($this->tag_data as $key => $arrayofvalues) { 139 if (!is_array($arrayofvalues)) { 140 return false; 141 } 142 143 $valuestring = ''; 144 foreach ($arrayofvalues as $value) { 145 $valuestring .= str_replace("\x00", '', $value)."\x00"; 146 } 147 $valuestring = rtrim($valuestring, "\x00"); 148 149 // Length of the assigned value in bytes 150 $tagitem = getid3_lib::LittleEndian2String(strlen($valuestring), 4); 151 152 //$tagitem .= $this->GenerateAPEtagFlags(true, true, false, 0, false); 153 $tagitem .= "\x00\x00\x00\x00"; 154 155 $tagitem .= $this->CleanAPEtagItemKey($key)."\x00"; 156 $tagitem .= $valuestring; 157 158 $items[] = $tagitem; 159 160 } 161 162 return $this->GenerateAPEtagHeaderFooter($items, true).implode('', $items).$this->GenerateAPEtagHeaderFooter($items, false); 163 } 164 165 function GenerateAPEtagHeaderFooter(&$items, $isheader=false) { 166 $tagdatalength = 0; 167 foreach ($items as $itemdata) { 168 $tagdatalength += strlen($itemdata); 169 } 170 171 $APEheader = 'APETAGEX'; 172 $APEheader .= getid3_lib::LittleEndian2String(2000, 4); 173 $APEheader .= getid3_lib::LittleEndian2String(32 + $tagdatalength, 4); 174 $APEheader .= getid3_lib::LittleEndian2String(count($items), 4); 175 $APEheader .= $this->GenerateAPEtagFlags(true, true, $isheader, 0, false); 176 $APEheader .= str_repeat("\x00", 8); 177 178 return $APEheader; 179 } 180 181 function GenerateAPEtagFlags($header=true, $footer=true, $isheader=false, $encodingid=0, $readonly=false) { 182 $APEtagFlags = array_fill(0, 4, 0); 183 if ($header) { 184 $APEtagFlags[0] |= 0x80; // Tag contains a header 185 } 186 if (!$footer) { 187 $APEtagFlags[0] |= 0x40; // Tag contains no footer 188 } 189 if ($isheader) { 190 $APEtagFlags[0] |= 0x20; // This is the header, not the footer 191 } 192 193 // 0: Item contains text information coded in UTF-8 194 // 1: Item contains binary information °) 195 // 2: Item is a locator of external stored information °°) 196 // 3: reserved 197 $APEtagFlags[3] |= ($encodingid << 1); 198 199 if ($readonly) { 200 $APEtagFlags[3] |= 0x01; // Tag or Item is Read Only 201 } 202 203 return chr($APEtagFlags[3]).chr($APEtagFlags[2]).chr($APEtagFlags[1]).chr($APEtagFlags[0]); 204 } 205 206 function CleanAPEtagItemKey($itemkey) { 207 $itemkey = eregi_replace("[^\x20-\x7E]", '', $itemkey); 208 209 // http://www.personal.uni-jena.de/~pfk/mpp/sv8/apekey.html 210 switch (strtoupper($itemkey)) { 211 case 'EAN/UPC': 212 case 'ISBN': 213 case 'LC': 214 case 'ISRC': 215 $itemkey = strtoupper($itemkey); 216 break; 217 218 default: 219 $itemkey = ucwords($itemkey); 220 break; 221 } 222 return $itemkey; 223 224 } 225 262 226 } 263 227
Note: See TracChangeset
for help on using the changeset viewer.