source: extensions/charlies_content/getid3/getid3/write.apetag.php @ 3318

Revision 3318, 9.0 KB checked in by vdigital, 10 years ago (diff)

+ Add Charlies' content to depository

  • Property svn:eol-style set to LF
  • Property svn:keywords set to Author Date Id Revision
Line 
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
27class getid3_write_apetag extends getid3_handler_write
28{
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   
262}
263
264?>
Note: See TracBrowser for help on using the repository browser.