author | ymh <ymh.work@gmail.com> |
Tue, 15 Dec 2020 13:49:49 +0100 | |
changeset 16 | a86126ab1dd4 |
parent 7 | cf61fcea0001 |
child 19 | 3d72ae0968f4 |
permissions | -rw-r--r-- |
0 | 1 |
<?php |
16 | 2 |
|
0 | 3 |
///////////////////////////////////////////////////////////////// |
4 |
/// getID3() by James Heinrich <info@getid3.org> // |
|
16 | 5 |
// available at https://github.com/JamesHeinrich/getID3 // |
6 |
// or https://www.getid3.org // |
|
7 |
// or http://getid3.sourceforge.net // |
|
8 |
// see readme.txt for more details // |
|
0 | 9 |
///////////////////////////////////////////////////////////////// |
10 |
// // |
|
11 |
// module.audio.ogg.php // |
|
12 |
// module for analyzing Ogg Vorbis, OggFLAC and Speex files // |
|
13 |
// dependencies: module.audio.flac.php // |
|
14 |
// /// |
|
15 |
///////////////////////////////////////////////////////////////// |
|
16 |
||
16 | 17 |
if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers |
18 |
exit; |
|
19 |
} |
|
0 | 20 |
getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.flac.php', __FILE__, true); |
21 |
||
22 |
class getid3_ogg extends getid3_handler |
|
23 |
{ |
|
16 | 24 |
/** |
25 |
* @link http://xiph.org/vorbis/doc/Vorbis_I_spec.html |
|
26 |
* |
|
27 |
* @return bool |
|
28 |
*/ |
|
0 | 29 |
public function Analyze() { |
30 |
$info = &$this->getid3->info; |
|
31 |
||
32 |
$info['fileformat'] = 'ogg'; |
|
33 |
||
34 |
// Warn about illegal tags - only vorbiscomments are allowed |
|
35 |
if (isset($info['id3v2'])) { |
|
7
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
36 |
$this->warning('Illegal ID3v2 tag present.'); |
0 | 37 |
} |
38 |
if (isset($info['id3v1'])) { |
|
7
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
39 |
$this->warning('Illegal ID3v1 tag present.'); |
0 | 40 |
} |
41 |
if (isset($info['ape'])) { |
|
7
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
42 |
$this->warning('Illegal APE tag present.'); |
0 | 43 |
} |
44 |
||
45 |
||
46 |
// Page 1 - Stream Header |
|
47 |
||
48 |
$this->fseek($info['avdataoffset']); |
|
49 |
||
50 |
$oggpageinfo = $this->ParseOggPageHeader(); |
|
51 |
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo; |
|
52 |
||
53 |
if ($this->ftell() >= $this->getid3->fread_buffer_size()) { |
|
7
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
54 |
$this->error('Could not find start of Ogg page in the first '.$this->getid3->fread_buffer_size().' bytes (this might not be an Ogg-Vorbis file?)'); |
0 | 55 |
unset($info['fileformat']); |
56 |
unset($info['ogg']); |
|
57 |
return false; |
|
58 |
} |
|
59 |
||
60 |
$filedata = $this->fread($oggpageinfo['page_length']); |
|
61 |
$filedataoffset = 0; |
|
62 |
||
63 |
if (substr($filedata, 0, 4) == 'fLaC') { |
|
64 |
||
65 |
$info['audio']['dataformat'] = 'flac'; |
|
66 |
$info['audio']['bitrate_mode'] = 'vbr'; |
|
67 |
$info['audio']['lossless'] = true; |
|
68 |
||
69 |
} elseif (substr($filedata, 1, 6) == 'vorbis') { |
|
70 |
||
71 |
$this->ParseVorbisPageHeader($filedata, $filedataoffset, $oggpageinfo); |
|
72 |
||
7
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
73 |
} elseif (substr($filedata, 0, 8) == 'OpusHead') { |
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
74 |
|
16 | 75 |
if ($this->ParseOpusPageHeader($filedata, $filedataoffset, $oggpageinfo) === false) { |
7
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
76 |
return false; |
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
77 |
} |
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
78 |
|
0 | 79 |
} elseif (substr($filedata, 0, 8) == 'Speex ') { |
80 |
||
81 |
// http://www.speex.org/manual/node10.html |
|
82 |
||
83 |
$info['audio']['dataformat'] = 'speex'; |
|
84 |
$info['mime_type'] = 'audio/speex'; |
|
85 |
$info['audio']['bitrate_mode'] = 'abr'; |
|
86 |
$info['audio']['lossless'] = false; |
|
87 |
||
88 |
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_string'] = substr($filedata, $filedataoffset, 8); // hard-coded to 'Speex ' |
|
89 |
$filedataoffset += 8; |
|
90 |
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version'] = substr($filedata, $filedataoffset, 20); |
|
91 |
$filedataoffset += 20; |
|
92 |
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version_id'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); |
|
93 |
$filedataoffset += 4; |
|
94 |
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['header_size'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); |
|
95 |
$filedataoffset += 4; |
|
96 |
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['rate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); |
|
97 |
$filedataoffset += 4; |
|
98 |
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); |
|
99 |
$filedataoffset += 4; |
|
100 |
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode_bitstream_version'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); |
|
101 |
$filedataoffset += 4; |
|
102 |
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['nb_channels'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); |
|
103 |
$filedataoffset += 4; |
|
104 |
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['bitrate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); |
|
105 |
$filedataoffset += 4; |
|
106 |
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['framesize'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); |
|
107 |
$filedataoffset += 4; |
|
108 |
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['vbr'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); |
|
109 |
$filedataoffset += 4; |
|
110 |
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['frames_per_packet'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); |
|
111 |
$filedataoffset += 4; |
|
112 |
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['extra_headers'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); |
|
113 |
$filedataoffset += 4; |
|
114 |
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['reserved1'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); |
|
115 |
$filedataoffset += 4; |
|
116 |
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['reserved2'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); |
|
117 |
$filedataoffset += 4; |
|
118 |
||
119 |
$info['speex']['speex_version'] = trim($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['speex_version']); |
|
120 |
$info['speex']['sample_rate'] = $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['rate']; |
|
121 |
$info['speex']['channels'] = $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['nb_channels']; |
|
122 |
$info['speex']['vbr'] = (bool) $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['vbr']; |
|
123 |
$info['speex']['band_type'] = $this->SpeexBandModeLookup($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['mode']); |
|
124 |
||
125 |
$info['audio']['sample_rate'] = $info['speex']['sample_rate']; |
|
126 |
$info['audio']['channels'] = $info['speex']['channels']; |
|
127 |
if ($info['speex']['vbr']) { |
|
128 |
$info['audio']['bitrate_mode'] = 'vbr'; |
|
129 |
} |
|
130 |
||
5 | 131 |
} elseif (substr($filedata, 0, 7) == "\x80".'theora') { |
132 |
||
133 |
// http://www.theora.org/doc/Theora.pdf (section 6.2) |
|
134 |
||
135 |
$info['ogg']['pageheader']['theora']['theora_magic'] = substr($filedata, $filedataoffset, 7); // hard-coded to "\x80.'theora' |
|
136 |
$filedataoffset += 7; |
|
137 |
$info['ogg']['pageheader']['theora']['version_major'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 1)); |
|
138 |
$filedataoffset += 1; |
|
139 |
$info['ogg']['pageheader']['theora']['version_minor'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 1)); |
|
140 |
$filedataoffset += 1; |
|
141 |
$info['ogg']['pageheader']['theora']['version_revision'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 1)); |
|
142 |
$filedataoffset += 1; |
|
143 |
$info['ogg']['pageheader']['theora']['frame_width_macroblocks'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 2)); |
|
144 |
$filedataoffset += 2; |
|
145 |
$info['ogg']['pageheader']['theora']['frame_height_macroblocks'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 2)); |
|
146 |
$filedataoffset += 2; |
|
147 |
$info['ogg']['pageheader']['theora']['resolution_x'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 3)); |
|
148 |
$filedataoffset += 3; |
|
149 |
$info['ogg']['pageheader']['theora']['resolution_y'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 3)); |
|
150 |
$filedataoffset += 3; |
|
151 |
$info['ogg']['pageheader']['theora']['picture_offset_x'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 1)); |
|
152 |
$filedataoffset += 1; |
|
153 |
$info['ogg']['pageheader']['theora']['picture_offset_y'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 1)); |
|
154 |
$filedataoffset += 1; |
|
155 |
$info['ogg']['pageheader']['theora']['frame_rate_numerator'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 4)); |
|
156 |
$filedataoffset += 4; |
|
157 |
$info['ogg']['pageheader']['theora']['frame_rate_denominator'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 4)); |
|
158 |
$filedataoffset += 4; |
|
159 |
$info['ogg']['pageheader']['theora']['pixel_aspect_numerator'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 3)); |
|
160 |
$filedataoffset += 3; |
|
161 |
$info['ogg']['pageheader']['theora']['pixel_aspect_denominator'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 3)); |
|
162 |
$filedataoffset += 3; |
|
163 |
$info['ogg']['pageheader']['theora']['color_space_id'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 1)); |
|
164 |
$filedataoffset += 1; |
|
165 |
$info['ogg']['pageheader']['theora']['nominal_bitrate'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 3)); |
|
166 |
$filedataoffset += 3; |
|
167 |
$info['ogg']['pageheader']['theora']['flags'] = getid3_lib::BigEndian2Int(substr($filedata, $filedataoffset, 2)); |
|
168 |
$filedataoffset += 2; |
|
169 |
||
170 |
$info['ogg']['pageheader']['theora']['quality'] = ($info['ogg']['pageheader']['theora']['flags'] & 0xFC00) >> 10; |
|
171 |
$info['ogg']['pageheader']['theora']['kfg_shift'] = ($info['ogg']['pageheader']['theora']['flags'] & 0x03E0) >> 5; |
|
172 |
$info['ogg']['pageheader']['theora']['pixel_format_id'] = ($info['ogg']['pageheader']['theora']['flags'] & 0x0018) >> 3; |
|
173 |
$info['ogg']['pageheader']['theora']['reserved'] = ($info['ogg']['pageheader']['theora']['flags'] & 0x0007) >> 0; // should be 0 |
|
174 |
$info['ogg']['pageheader']['theora']['color_space'] = self::TheoraColorSpace($info['ogg']['pageheader']['theora']['color_space_id']); |
|
175 |
$info['ogg']['pageheader']['theora']['pixel_format'] = self::TheoraPixelFormat($info['ogg']['pageheader']['theora']['pixel_format_id']); |
|
176 |
||
177 |
$info['video']['dataformat'] = 'theora'; |
|
178 |
$info['mime_type'] = 'video/ogg'; |
|
179 |
//$info['audio']['bitrate_mode'] = 'abr'; |
|
180 |
//$info['audio']['lossless'] = false; |
|
181 |
$info['video']['resolution_x'] = $info['ogg']['pageheader']['theora']['resolution_x']; |
|
182 |
$info['video']['resolution_y'] = $info['ogg']['pageheader']['theora']['resolution_y']; |
|
183 |
if ($info['ogg']['pageheader']['theora']['frame_rate_denominator'] > 0) { |
|
184 |
$info['video']['frame_rate'] = (float) $info['ogg']['pageheader']['theora']['frame_rate_numerator'] / $info['ogg']['pageheader']['theora']['frame_rate_denominator']; |
|
185 |
} |
|
186 |
if ($info['ogg']['pageheader']['theora']['pixel_aspect_denominator'] > 0) { |
|
187 |
$info['video']['pixel_aspect_ratio'] = (float) $info['ogg']['pageheader']['theora']['pixel_aspect_numerator'] / $info['ogg']['pageheader']['theora']['pixel_aspect_denominator']; |
|
188 |
} |
|
16 | 189 |
$this->warning('Ogg Theora (v3) not fully supported in this version of getID3 ['.$this->getid3->version().'] -- bitrate, playtime and all audio data are currently unavailable'); |
5 | 190 |
|
0 | 191 |
|
192 |
} elseif (substr($filedata, 0, 8) == "fishead\x00") { |
|
193 |
||
194 |
// Ogg Skeleton version 3.0 Format Specification |
|
195 |
// http://xiph.org/ogg/doc/skeleton.html |
|
196 |
$filedataoffset += 8; |
|
197 |
$info['ogg']['skeleton']['fishead']['raw']['version_major'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 2)); |
|
198 |
$filedataoffset += 2; |
|
199 |
$info['ogg']['skeleton']['fishead']['raw']['version_minor'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 2)); |
|
200 |
$filedataoffset += 2; |
|
201 |
$info['ogg']['skeleton']['fishead']['raw']['presentationtime_numerator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8)); |
|
202 |
$filedataoffset += 8; |
|
203 |
$info['ogg']['skeleton']['fishead']['raw']['presentationtime_denominator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8)); |
|
204 |
$filedataoffset += 8; |
|
205 |
$info['ogg']['skeleton']['fishead']['raw']['basetime_numerator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8)); |
|
206 |
$filedataoffset += 8; |
|
207 |
$info['ogg']['skeleton']['fishead']['raw']['basetime_denominator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8)); |
|
208 |
$filedataoffset += 8; |
|
209 |
$info['ogg']['skeleton']['fishead']['raw']['utc'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 20)); |
|
210 |
$filedataoffset += 20; |
|
211 |
||
212 |
$info['ogg']['skeleton']['fishead']['version'] = $info['ogg']['skeleton']['fishead']['raw']['version_major'].'.'.$info['ogg']['skeleton']['fishead']['raw']['version_minor']; |
|
213 |
$info['ogg']['skeleton']['fishead']['presentationtime'] = $info['ogg']['skeleton']['fishead']['raw']['presentationtime_numerator'] / $info['ogg']['skeleton']['fishead']['raw']['presentationtime_denominator']; |
|
214 |
$info['ogg']['skeleton']['fishead']['basetime'] = $info['ogg']['skeleton']['fishead']['raw']['basetime_numerator'] / $info['ogg']['skeleton']['fishead']['raw']['basetime_denominator']; |
|
215 |
$info['ogg']['skeleton']['fishead']['utc'] = $info['ogg']['skeleton']['fishead']['raw']['utc']; |
|
216 |
||
217 |
||
218 |
$counter = 0; |
|
219 |
do { |
|
220 |
$oggpageinfo = $this->ParseOggPageHeader(); |
|
221 |
$info['ogg']['pageheader'][$oggpageinfo['page_seqno'].'.'.$counter++] = $oggpageinfo; |
|
222 |
$filedata = $this->fread($oggpageinfo['page_length']); |
|
223 |
$this->fseek($oggpageinfo['page_end_offset']); |
|
224 |
||
225 |
if (substr($filedata, 0, 8) == "fisbone\x00") { |
|
226 |
||
227 |
$filedataoffset = 8; |
|
228 |
$info['ogg']['skeleton']['fisbone']['raw']['message_header_offset'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); |
|
229 |
$filedataoffset += 4; |
|
230 |
$info['ogg']['skeleton']['fisbone']['raw']['serial_number'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); |
|
231 |
$filedataoffset += 4; |
|
232 |
$info['ogg']['skeleton']['fisbone']['raw']['number_header_packets'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); |
|
233 |
$filedataoffset += 4; |
|
234 |
$info['ogg']['skeleton']['fisbone']['raw']['granulerate_numerator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8)); |
|
235 |
$filedataoffset += 8; |
|
236 |
$info['ogg']['skeleton']['fisbone']['raw']['granulerate_denominator'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8)); |
|
237 |
$filedataoffset += 8; |
|
238 |
$info['ogg']['skeleton']['fisbone']['raw']['basegranule'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8)); |
|
239 |
$filedataoffset += 8; |
|
240 |
$info['ogg']['skeleton']['fisbone']['raw']['preroll'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); |
|
241 |
$filedataoffset += 4; |
|
242 |
$info['ogg']['skeleton']['fisbone']['raw']['granuleshift'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); |
|
243 |
$filedataoffset += 1; |
|
244 |
$info['ogg']['skeleton']['fisbone']['raw']['padding'] = substr($filedata, $filedataoffset, 3); |
|
245 |
$filedataoffset += 3; |
|
246 |
||
247 |
} elseif (substr($filedata, 1, 6) == 'theora') { |
|
248 |
||
5 | 249 |
$info['video']['dataformat'] = 'theora1'; |
7
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
250 |
$this->error('Ogg Theora (v1) not correctly handled in this version of getID3 ['.$this->getid3->version().']'); |
0 | 251 |
//break; |
252 |
||
253 |
} elseif (substr($filedata, 1, 6) == 'vorbis') { |
|
254 |
||
255 |
$this->ParseVorbisPageHeader($filedata, $filedataoffset, $oggpageinfo); |
|
256 |
||
257 |
} else { |
|
7
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
258 |
$this->error('unexpected'); |
0 | 259 |
//break; |
260 |
} |
|
261 |
//} while ($oggpageinfo['page_seqno'] == 0); |
|
262 |
} while (($oggpageinfo['page_seqno'] == 0) && (substr($filedata, 0, 8) != "fisbone\x00")); |
|
263 |
||
264 |
$this->fseek($oggpageinfo['page_start_offset']); |
|
265 |
||
7
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
266 |
$this->error('Ogg Skeleton not correctly handled in this version of getID3 ['.$this->getid3->version().']'); |
0 | 267 |
//return false; |
268 |
||
16 | 269 |
} elseif (substr($filedata, 0, 5) == "\x7F".'FLAC') { |
270 |
// https://xiph.org/flac/ogg_mapping.html |
|
271 |
||
272 |
$info['audio']['dataformat'] = 'flac'; |
|
273 |
$info['audio']['bitrate_mode'] = 'vbr'; |
|
274 |
$info['audio']['lossless'] = true; |
|
275 |
||
276 |
$info['ogg']['flac']['header']['version_major'] = ord(substr($filedata, 5, 1)); |
|
277 |
$info['ogg']['flac']['header']['version_minor'] = ord(substr($filedata, 6, 1)); |
|
278 |
$info['ogg']['flac']['header']['header_packets'] = getid3_lib::BigEndian2Int(substr($filedata, 7, 2)) + 1; // "A two-byte, big-endian binary number signifying the number of header (non-audio) packets, not including this one. This number may be zero (0x0000) to signify 'unknown' but be aware that some decoders may not be able to handle such streams." |
|
279 |
$info['ogg']['flac']['header']['magic'] = substr($filedata, 9, 4); |
|
280 |
if ($info['ogg']['flac']['header']['magic'] != 'fLaC') { |
|
281 |
$this->error('Ogg-FLAC expecting "fLaC", found "'.$info['ogg']['flac']['header']['magic'].'" ('.trim(getid3_lib::PrintHexBytes($info['ogg']['flac']['header']['magic'])).')'); |
|
282 |
return false; |
|
283 |
} |
|
284 |
$info['ogg']['flac']['header']['STREAMINFO_bytes'] = getid3_lib::BigEndian2Int(substr($filedata, 13, 4)); |
|
285 |
$info['flac']['STREAMINFO'] = getid3_flac::parseSTREAMINFOdata(substr($filedata, 17, 34)); |
|
286 |
if (!empty($info['flac']['STREAMINFO']['sample_rate'])) { |
|
287 |
$info['audio']['bitrate_mode'] = 'vbr'; |
|
288 |
$info['audio']['sample_rate'] = $info['flac']['STREAMINFO']['sample_rate']; |
|
289 |
$info['audio']['channels'] = $info['flac']['STREAMINFO']['channels']; |
|
290 |
$info['audio']['bits_per_sample'] = $info['flac']['STREAMINFO']['bits_per_sample']; |
|
291 |
$info['playtime_seconds'] = $info['flac']['STREAMINFO']['samples_stream'] / $info['flac']['STREAMINFO']['sample_rate']; |
|
292 |
} |
|
293 |
||
0 | 294 |
} else { |
295 |
||
16 | 296 |
$this->error('Expecting one of "vorbis", "Speex", "OpusHead", "vorbis", "fishhead", "theora", "fLaC" identifier strings, found "'.substr($filedata, 0, 8).'"'); |
0 | 297 |
unset($info['ogg']); |
298 |
unset($info['mime_type']); |
|
299 |
return false; |
|
300 |
||
301 |
} |
|
302 |
||
303 |
// Page 2 - Comment Header |
|
304 |
$oggpageinfo = $this->ParseOggPageHeader(); |
|
305 |
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo; |
|
306 |
||
307 |
switch ($info['audio']['dataformat']) { |
|
308 |
case 'vorbis': |
|
309 |
$filedata = $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']); |
|
310 |
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int(substr($filedata, 0, 1)); |
|
311 |
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] = substr($filedata, 1, 6); // hard-coded to 'vorbis' |
|
312 |
||
313 |
$this->ParseVorbisComments(); |
|
314 |
break; |
|
315 |
||
316 |
case 'flac': |
|
317 |
$flac = new getid3_flac($this->getid3); |
|
318 |
if (!$flac->parseMETAdata()) { |
|
7
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
319 |
$this->error('Failed to parse FLAC headers'); |
0 | 320 |
return false; |
321 |
} |
|
322 |
unset($flac); |
|
323 |
break; |
|
324 |
||
325 |
case 'speex': |
|
326 |
$this->fseek($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length'], SEEK_CUR); |
|
327 |
$this->ParseVorbisComments(); |
|
328 |
break; |
|
7
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
329 |
|
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
330 |
case 'opus': |
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
331 |
$filedata = $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']); |
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
332 |
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] = substr($filedata, 0, 8); // hard-coded to 'OpusTags' |
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
333 |
if(substr($filedata, 0, 8) != 'OpusTags') { |
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
334 |
$this->error('Expected "OpusTags" as header but got "'.substr($filedata, 0, 8).'"'); |
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
335 |
return false; |
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
336 |
} |
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
337 |
|
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
338 |
$this->ParseVorbisComments(); |
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
339 |
break; |
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
340 |
|
0 | 341 |
} |
342 |
||
343 |
// Last Page - Number of Samples |
|
344 |
if (!getid3_lib::intValueSupported($info['avdataend'])) { |
|
345 |
||
7
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
346 |
$this->warning('Unable to parse Ogg end chunk file (PHP does not support file operations beyond '.round(PHP_INT_MAX / 1073741824).'GB)'); |
0 | 347 |
|
348 |
} else { |
|
349 |
||
350 |
$this->fseek(max($info['avdataend'] - $this->getid3->fread_buffer_size(), 0)); |
|
351 |
$LastChunkOfOgg = strrev($this->fread($this->getid3->fread_buffer_size())); |
|
352 |
if ($LastOggSpostion = strpos($LastChunkOfOgg, 'SggO')) { |
|
353 |
$this->fseek($info['avdataend'] - ($LastOggSpostion + strlen('SggO'))); |
|
354 |
$info['avdataend'] = $this->ftell(); |
|
355 |
$info['ogg']['pageheader']['eos'] = $this->ParseOggPageHeader(); |
|
356 |
$info['ogg']['samples'] = $info['ogg']['pageheader']['eos']['pcm_abs_position']; |
|
357 |
if ($info['ogg']['samples'] == 0) { |
|
7
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
358 |
$this->error('Corrupt Ogg file: eos.number of samples == zero'); |
0 | 359 |
return false; |
360 |
} |
|
361 |
if (!empty($info['audio']['sample_rate'])) { |
|
362 |
$info['ogg']['bitrate_average'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / ($info['ogg']['samples'] / $info['audio']['sample_rate']); |
|
363 |
} |
|
364 |
} |
|
365 |
||
366 |
} |
|
367 |
||
368 |
if (!empty($info['ogg']['bitrate_average'])) { |
|
369 |
$info['audio']['bitrate'] = $info['ogg']['bitrate_average']; |
|
370 |
} elseif (!empty($info['ogg']['bitrate_nominal'])) { |
|
371 |
$info['audio']['bitrate'] = $info['ogg']['bitrate_nominal']; |
|
372 |
} elseif (!empty($info['ogg']['bitrate_min']) && !empty($info['ogg']['bitrate_max'])) { |
|
373 |
$info['audio']['bitrate'] = ($info['ogg']['bitrate_min'] + $info['ogg']['bitrate_max']) / 2; |
|
374 |
} |
|
375 |
if (isset($info['audio']['bitrate']) && !isset($info['playtime_seconds'])) { |
|
376 |
if ($info['audio']['bitrate'] == 0) { |
|
7
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
377 |
$this->error('Corrupt Ogg file: bitrate_audio == zero'); |
0 | 378 |
return false; |
379 |
} |
|
380 |
$info['playtime_seconds'] = (float) ((($info['avdataend'] - $info['avdataoffset']) * 8) / $info['audio']['bitrate']); |
|
381 |
} |
|
382 |
||
383 |
if (isset($info['ogg']['vendor'])) { |
|
384 |
$info['audio']['encoder'] = preg_replace('/^Encoded with /', '', $info['ogg']['vendor']); |
|
385 |
||
386 |
// Vorbis only |
|
387 |
if ($info['audio']['dataformat'] == 'vorbis') { |
|
388 |
||
389 |
// Vorbis 1.0 starts with Xiph.Org |
|
390 |
if (preg_match('/^Xiph.Org/', $info['audio']['encoder'])) { |
|
391 |
||
392 |
if ($info['audio']['bitrate_mode'] == 'abr') { |
|
393 |
||
394 |
// Set -b 128 on abr files |
|
395 |
$info['audio']['encoder_options'] = '-b '.round($info['ogg']['bitrate_nominal'] / 1000); |
|
396 |
||
397 |
} elseif (($info['audio']['bitrate_mode'] == 'vbr') && ($info['audio']['channels'] == 2) && ($info['audio']['sample_rate'] >= 44100) && ($info['audio']['sample_rate'] <= 48000)) { |
|
398 |
// Set -q N on vbr files |
|
399 |
$info['audio']['encoder_options'] = '-q '.$this->get_quality_from_nominal_bitrate($info['ogg']['bitrate_nominal']); |
|
400 |
||
401 |
} |
|
402 |
} |
|
403 |
||
404 |
if (empty($info['audio']['encoder_options']) && !empty($info['ogg']['bitrate_nominal'])) { |
|
405 |
$info['audio']['encoder_options'] = 'Nominal bitrate: '.intval(round($info['ogg']['bitrate_nominal'] / 1000)).'kbps'; |
|
406 |
} |
|
407 |
} |
|
408 |
} |
|
409 |
||
410 |
return true; |
|
411 |
} |
|
412 |
||
16 | 413 |
/** |
414 |
* @param string $filedata |
|
415 |
* @param int $filedataoffset |
|
416 |
* @param array $oggpageinfo |
|
417 |
* |
|
418 |
* @return bool |
|
419 |
*/ |
|
0 | 420 |
public function ParseVorbisPageHeader(&$filedata, &$filedataoffset, &$oggpageinfo) { |
421 |
$info = &$this->getid3->info; |
|
422 |
$info['audio']['dataformat'] = 'vorbis'; |
|
423 |
$info['audio']['lossless'] = false; |
|
424 |
||
425 |
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['packet_type'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); |
|
426 |
$filedataoffset += 1; |
|
427 |
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['stream_type'] = substr($filedata, $filedataoffset, 6); // hard-coded to 'vorbis' |
|
428 |
$filedataoffset += 6; |
|
429 |
$info['ogg']['bitstreamversion'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); |
|
430 |
$filedataoffset += 4; |
|
431 |
$info['ogg']['numberofchannels'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); |
|
432 |
$filedataoffset += 1; |
|
433 |
$info['audio']['channels'] = $info['ogg']['numberofchannels']; |
|
434 |
$info['ogg']['samplerate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); |
|
435 |
$filedataoffset += 4; |
|
436 |
if ($info['ogg']['samplerate'] == 0) { |
|
7
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
437 |
$this->error('Corrupt Ogg file: sample rate == zero'); |
0 | 438 |
return false; |
439 |
} |
|
440 |
$info['audio']['sample_rate'] = $info['ogg']['samplerate']; |
|
441 |
$info['ogg']['samples'] = 0; // filled in later |
|
442 |
$info['ogg']['bitrate_average'] = 0; // filled in later |
|
443 |
$info['ogg']['bitrate_max'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); |
|
444 |
$filedataoffset += 4; |
|
445 |
$info['ogg']['bitrate_nominal'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); |
|
446 |
$filedataoffset += 4; |
|
447 |
$info['ogg']['bitrate_min'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); |
|
448 |
$filedataoffset += 4; |
|
449 |
$info['ogg']['blocksize_small'] = pow(2, getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)) & 0x0F); |
|
450 |
$info['ogg']['blocksize_large'] = pow(2, (getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)) & 0xF0) >> 4); |
|
451 |
$info['ogg']['stop_bit'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); // must be 1, marks end of packet |
|
452 |
||
453 |
$info['audio']['bitrate_mode'] = 'vbr'; // overridden if actually abr |
|
454 |
if ($info['ogg']['bitrate_max'] == 0xFFFFFFFF) { |
|
455 |
unset($info['ogg']['bitrate_max']); |
|
456 |
$info['audio']['bitrate_mode'] = 'abr'; |
|
457 |
} |
|
458 |
if ($info['ogg']['bitrate_nominal'] == 0xFFFFFFFF) { |
|
459 |
unset($info['ogg']['bitrate_nominal']); |
|
460 |
} |
|
461 |
if ($info['ogg']['bitrate_min'] == 0xFFFFFFFF) { |
|
462 |
unset($info['ogg']['bitrate_min']); |
|
463 |
$info['audio']['bitrate_mode'] = 'abr'; |
|
464 |
} |
|
465 |
return true; |
|
466 |
} |
|
467 |
||
16 | 468 |
/** |
469 |
* @link http://tools.ietf.org/html/draft-ietf-codec-oggopus-03 |
|
470 |
* |
|
471 |
* @param string $filedata |
|
472 |
* @param int $filedataoffset |
|
473 |
* @param array $oggpageinfo |
|
474 |
* |
|
475 |
* @return bool |
|
476 |
*/ |
|
7
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
477 |
public function ParseOpusPageHeader(&$filedata, &$filedataoffset, &$oggpageinfo) { |
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
478 |
$info = &$this->getid3->info; |
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
479 |
$info['audio']['dataformat'] = 'opus'; |
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
480 |
$info['mime_type'] = 'audio/ogg; codecs=opus'; |
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
481 |
|
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
482 |
/** @todo find a usable way to detect abr (vbr that is padded to be abr) */ |
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
483 |
$info['audio']['bitrate_mode'] = 'vbr'; |
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
484 |
|
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
485 |
$info['audio']['lossless'] = false; |
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
486 |
|
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
487 |
$info['ogg']['pageheader']['opus']['opus_magic'] = substr($filedata, $filedataoffset, 8); // hard-coded to 'OpusHead' |
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
488 |
$filedataoffset += 8; |
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
489 |
$info['ogg']['pageheader']['opus']['version'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); |
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
490 |
$filedataoffset += 1; |
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
491 |
|
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
492 |
if ($info['ogg']['pageheader']['opus']['version'] < 1 || $info['ogg']['pageheader']['opus']['version'] > 15) { |
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
493 |
$this->error('Unknown opus version number (only accepting 1-15)'); |
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
494 |
return false; |
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
495 |
} |
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
496 |
|
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
497 |
$info['ogg']['pageheader']['opus']['out_channel_count'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); |
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
498 |
$filedataoffset += 1; |
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
499 |
|
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
500 |
if ($info['ogg']['pageheader']['opus']['out_channel_count'] == 0) { |
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
501 |
$this->error('Invalid channel count in opus header (must not be zero)'); |
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
502 |
return false; |
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
503 |
} |
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
504 |
|
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
505 |
$info['ogg']['pageheader']['opus']['pre_skip'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 2)); |
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
506 |
$filedataoffset += 2; |
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
507 |
|
16 | 508 |
$info['ogg']['pageheader']['opus']['input_sample_rate'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); |
7
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
509 |
$filedataoffset += 4; |
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
510 |
|
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
511 |
//$info['ogg']['pageheader']['opus']['output_gain'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 2)); |
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
512 |
//$filedataoffset += 2; |
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
513 |
|
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
514 |
//$info['ogg']['pageheader']['opus']['channel_mapping_family'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); |
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
515 |
//$filedataoffset += 1; |
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
516 |
|
16 | 517 |
$info['opus']['opus_version'] = $info['ogg']['pageheader']['opus']['version']; |
518 |
$info['opus']['sample_rate_input'] = $info['ogg']['pageheader']['opus']['input_sample_rate']; |
|
519 |
$info['opus']['out_channel_count'] = $info['ogg']['pageheader']['opus']['out_channel_count']; |
|
7
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
520 |
|
16 | 521 |
$info['audio']['channels'] = $info['opus']['out_channel_count']; |
522 |
$info['audio']['sample_rate_input'] = $info['opus']['sample_rate_input']; |
|
523 |
$info['audio']['sample_rate'] = 48000; // "All Opus audio is coded at 48 kHz, and should also be decoded at 48 kHz for playback (unless the target hardware does not support this sampling rate). However, this field may be used to resample the audio back to the original sampling rate, for example, when saving the output to a file." -- https://mf4.xiph.org/jenkins/view/opus/job/opusfile-unix/ws/doc/html/structOpusHead.html |
|
7
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
524 |
return true; |
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
525 |
} |
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
526 |
|
16 | 527 |
/** |
528 |
* @return array|false |
|
529 |
*/ |
|
0 | 530 |
public function ParseOggPageHeader() { |
531 |
// http://xiph.org/ogg/vorbis/doc/framing.html |
|
532 |
$oggheader['page_start_offset'] = $this->ftell(); // where we started from in the file |
|
533 |
||
534 |
$filedata = $this->fread($this->getid3->fread_buffer_size()); |
|
535 |
$filedataoffset = 0; |
|
536 |
while ((substr($filedata, $filedataoffset++, 4) != 'OggS')) { |
|
537 |
if (($this->ftell() - $oggheader['page_start_offset']) >= $this->getid3->fread_buffer_size()) { |
|
538 |
// should be found before here |
|
539 |
return false; |
|
540 |
} |
|
541 |
if ((($filedataoffset + 28) > strlen($filedata)) || (strlen($filedata) < 28)) { |
|
16 | 542 |
if ($this->feof() || (($filedata .= $this->fread($this->getid3->fread_buffer_size())) === '')) { |
0 | 543 |
// get some more data, unless eof, in which case fail |
544 |
return false; |
|
545 |
} |
|
546 |
} |
|
547 |
} |
|
548 |
$filedataoffset += strlen('OggS') - 1; // page, delimited by 'OggS' |
|
549 |
||
550 |
$oggheader['stream_structver'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); |
|
551 |
$filedataoffset += 1; |
|
552 |
$oggheader['flags_raw'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); |
|
553 |
$filedataoffset += 1; |
|
554 |
$oggheader['flags']['fresh'] = (bool) ($oggheader['flags_raw'] & 0x01); // fresh packet |
|
555 |
$oggheader['flags']['bos'] = (bool) ($oggheader['flags_raw'] & 0x02); // first page of logical bitstream (bos) |
|
556 |
$oggheader['flags']['eos'] = (bool) ($oggheader['flags_raw'] & 0x04); // last page of logical bitstream (eos) |
|
557 |
||
558 |
$oggheader['pcm_abs_position'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 8)); |
|
559 |
$filedataoffset += 8; |
|
560 |
$oggheader['stream_serialno'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); |
|
561 |
$filedataoffset += 4; |
|
562 |
$oggheader['page_seqno'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); |
|
563 |
$filedataoffset += 4; |
|
564 |
$oggheader['page_checksum'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 4)); |
|
565 |
$filedataoffset += 4; |
|
566 |
$oggheader['page_segments'] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); |
|
567 |
$filedataoffset += 1; |
|
568 |
$oggheader['page_length'] = 0; |
|
569 |
for ($i = 0; $i < $oggheader['page_segments']; $i++) { |
|
570 |
$oggheader['segment_table'][$i] = getid3_lib::LittleEndian2Int(substr($filedata, $filedataoffset, 1)); |
|
571 |
$filedataoffset += 1; |
|
572 |
$oggheader['page_length'] += $oggheader['segment_table'][$i]; |
|
573 |
} |
|
574 |
$oggheader['header_end_offset'] = $oggheader['page_start_offset'] + $filedataoffset; |
|
575 |
$oggheader['page_end_offset'] = $oggheader['header_end_offset'] + $oggheader['page_length']; |
|
576 |
$this->fseek($oggheader['header_end_offset']); |
|
577 |
||
578 |
return $oggheader; |
|
579 |
} |
|
580 |
||
16 | 581 |
/** |
582 |
* @link http://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-810005 |
|
583 |
* |
|
584 |
* @return bool |
|
585 |
*/ |
|
0 | 586 |
public function ParseVorbisComments() { |
587 |
$info = &$this->getid3->info; |
|
588 |
||
589 |
$OriginalOffset = $this->ftell(); |
|
16 | 590 |
$commentdata = null; |
0 | 591 |
$commentdataoffset = 0; |
592 |
$VorbisCommentPage = 1; |
|
16 | 593 |
$CommentStartOffset = 0; |
0 | 594 |
|
595 |
switch ($info['audio']['dataformat']) { |
|
596 |
case 'vorbis': |
|
597 |
case 'speex': |
|
7
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
598 |
case 'opus': |
0 | 599 |
$CommentStartOffset = $info['ogg']['pageheader'][$VorbisCommentPage]['page_start_offset']; // Second Ogg page, after header block |
600 |
$this->fseek($CommentStartOffset); |
|
601 |
$commentdataoffset = 27 + $info['ogg']['pageheader'][$VorbisCommentPage]['page_segments']; |
|
602 |
$commentdata = $this->fread(self::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1) + $commentdataoffset); |
|
603 |
||
604 |
if ($info['audio']['dataformat'] == 'vorbis') { |
|
605 |
$commentdataoffset += (strlen('vorbis') + 1); |
|
606 |
} |
|
7
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
607 |
else if ($info['audio']['dataformat'] == 'opus') { |
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
608 |
$commentdataoffset += strlen('OpusTags'); |
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
609 |
} |
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
610 |
|
0 | 611 |
break; |
612 |
||
613 |
case 'flac': |
|
614 |
$CommentStartOffset = $info['flac']['VORBIS_COMMENT']['raw']['offset'] + 4; |
|
615 |
$this->fseek($CommentStartOffset); |
|
616 |
$commentdata = $this->fread($info['flac']['VORBIS_COMMENT']['raw']['block_length']); |
|
617 |
break; |
|
618 |
||
619 |
default: |
|
620 |
return false; |
|
621 |
} |
|
622 |
||
623 |
$VendorSize = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4)); |
|
624 |
$commentdataoffset += 4; |
|
625 |
||
626 |
$info['ogg']['vendor'] = substr($commentdata, $commentdataoffset, $VendorSize); |
|
627 |
$commentdataoffset += $VendorSize; |
|
628 |
||
629 |
$CommentsCount = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4)); |
|
630 |
$commentdataoffset += 4; |
|
631 |
$info['avdataoffset'] = $CommentStartOffset + $commentdataoffset; |
|
632 |
||
633 |
$basicfields = array('TITLE', 'ARTIST', 'ALBUM', 'TRACKNUMBER', 'GENRE', 'DATE', 'DESCRIPTION', 'COMMENT'); |
|
634 |
$ThisFileInfo_ogg_comments_raw = &$info['ogg']['comments_raw']; |
|
635 |
for ($i = 0; $i < $CommentsCount; $i++) { |
|
636 |
||
7
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
637 |
if ($i >= 10000) { |
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
638 |
// https://github.com/owncloud/music/issues/212#issuecomment-43082336 |
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
639 |
$this->warning('Unexpectedly large number ('.$CommentsCount.') of Ogg comments - breaking after reading '.$i.' comments'); |
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
640 |
break; |
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
641 |
} |
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
642 |
|
0 | 643 |
$ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] = $CommentStartOffset + $commentdataoffset; |
644 |
||
645 |
if ($this->ftell() < ($ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] + 4)) { |
|
646 |
if ($oggpageinfo = $this->ParseOggPageHeader()) { |
|
647 |
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo; |
|
648 |
||
649 |
$VorbisCommentPage++; |
|
650 |
||
651 |
// First, save what we haven't read yet |
|
652 |
$AsYetUnusedData = substr($commentdata, $commentdataoffset); |
|
653 |
||
654 |
// Then take that data off the end |
|
655 |
$commentdata = substr($commentdata, 0, $commentdataoffset); |
|
656 |
||
657 |
// Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct |
|
658 |
$commentdata .= str_repeat("\x00", 27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']); |
|
659 |
$commentdataoffset += (27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']); |
|
660 |
||
661 |
// Finally, stick the unused data back on the end |
|
662 |
$commentdata .= $AsYetUnusedData; |
|
663 |
||
664 |
//$commentdata .= $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']); |
|
665 |
$commentdata .= $this->fread($this->OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1)); |
|
666 |
} |
|
667 |
||
668 |
} |
|
669 |
$ThisFileInfo_ogg_comments_raw[$i]['size'] = getid3_lib::LittleEndian2Int(substr($commentdata, $commentdataoffset, 4)); |
|
670 |
||
671 |
// replace avdataoffset with position just after the last vorbiscomment |
|
672 |
$info['avdataoffset'] = $ThisFileInfo_ogg_comments_raw[$i]['dataoffset'] + $ThisFileInfo_ogg_comments_raw[$i]['size'] + 4; |
|
673 |
||
674 |
$commentdataoffset += 4; |
|
675 |
while ((strlen($commentdata) - $commentdataoffset) < $ThisFileInfo_ogg_comments_raw[$i]['size']) { |
|
676 |
if (($ThisFileInfo_ogg_comments_raw[$i]['size'] > $info['avdataend']) || ($ThisFileInfo_ogg_comments_raw[$i]['size'] < 0)) { |
|
7
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
677 |
$this->warning('Invalid Ogg comment size (comment #'.$i.', claims to be '.number_format($ThisFileInfo_ogg_comments_raw[$i]['size']).' bytes) - aborting reading comments'); |
0 | 678 |
break 2; |
679 |
} |
|
680 |
||
681 |
$VorbisCommentPage++; |
|
682 |
||
683 |
$oggpageinfo = $this->ParseOggPageHeader(); |
|
684 |
$info['ogg']['pageheader'][$oggpageinfo['page_seqno']] = $oggpageinfo; |
|
685 |
||
686 |
// First, save what we haven't read yet |
|
687 |
$AsYetUnusedData = substr($commentdata, $commentdataoffset); |
|
688 |
||
689 |
// Then take that data off the end |
|
690 |
$commentdata = substr($commentdata, 0, $commentdataoffset); |
|
691 |
||
692 |
// Add [headerlength] bytes of dummy data for the Ogg Page Header, just to keep absolute offsets correct |
|
693 |
$commentdata .= str_repeat("\x00", 27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']); |
|
694 |
$commentdataoffset += (27 + $info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_segments']); |
|
695 |
||
696 |
// Finally, stick the unused data back on the end |
|
697 |
$commentdata .= $AsYetUnusedData; |
|
698 |
||
699 |
//$commentdata .= $this->fread($info['ogg']['pageheader'][$oggpageinfo['page_seqno']]['page_length']); |
|
700 |
if (!isset($info['ogg']['pageheader'][$VorbisCommentPage])) { |
|
7
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
701 |
$this->warning('undefined Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.$this->ftell()); |
0 | 702 |
break; |
703 |
} |
|
704 |
$readlength = self::OggPageSegmentLength($info['ogg']['pageheader'][$VorbisCommentPage], 1); |
|
705 |
if ($readlength <= 0) { |
|
7
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
706 |
$this->warning('invalid length Vorbis Comment page "'.$VorbisCommentPage.'" at offset '.$this->ftell()); |
0 | 707 |
break; |
708 |
} |
|
709 |
$commentdata .= $this->fread($readlength); |
|
710 |
||
711 |
//$filebaseoffset += $oggpageinfo['header_end_offset'] - $oggpageinfo['page_start_offset']; |
|
712 |
} |
|
713 |
$ThisFileInfo_ogg_comments_raw[$i]['offset'] = $commentdataoffset; |
|
714 |
$commentstring = substr($commentdata, $commentdataoffset, $ThisFileInfo_ogg_comments_raw[$i]['size']); |
|
715 |
$commentdataoffset += $ThisFileInfo_ogg_comments_raw[$i]['size']; |
|
716 |
||
717 |
if (!$commentstring) { |
|
718 |
||
719 |
// no comment? |
|
7
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
720 |
$this->warning('Blank Ogg comment ['.$i.']'); |
0 | 721 |
|
722 |
} elseif (strstr($commentstring, '=')) { |
|
723 |
||
724 |
$commentexploded = explode('=', $commentstring, 2); |
|
725 |
$ThisFileInfo_ogg_comments_raw[$i]['key'] = strtoupper($commentexploded[0]); |
|
726 |
$ThisFileInfo_ogg_comments_raw[$i]['value'] = (isset($commentexploded[1]) ? $commentexploded[1] : ''); |
|
727 |
||
728 |
if ($ThisFileInfo_ogg_comments_raw[$i]['key'] == 'METADATA_BLOCK_PICTURE') { |
|
729 |
||
730 |
// http://wiki.xiph.org/VorbisComment#METADATA_BLOCK_PICTURE |
|
731 |
// The unencoded format is that of the FLAC picture block. The fields are stored in big endian order as in FLAC, picture data is stored according to the relevant standard. |
|
732 |
// http://flac.sourceforge.net/format.html#metadata_block_picture |
|
733 |
$flac = new getid3_flac($this->getid3); |
|
734 |
$flac->setStringMode(base64_decode($ThisFileInfo_ogg_comments_raw[$i]['value'])); |
|
735 |
$flac->parsePICTURE(); |
|
736 |
$info['ogg']['comments']['picture'][] = $flac->getid3->info['flac']['PICTURE'][0]; |
|
737 |
unset($flac); |
|
738 |
||
739 |
} elseif ($ThisFileInfo_ogg_comments_raw[$i]['key'] == 'COVERART') { |
|
740 |
||
741 |
$data = base64_decode($ThisFileInfo_ogg_comments_raw[$i]['value']); |
|
742 |
$this->notice('Found deprecated COVERART tag, it should be replaced in honor of METADATA_BLOCK_PICTURE structure'); |
|
743 |
/** @todo use 'coverartmime' where available */ |
|
744 |
$imageinfo = getid3_lib::GetDataImageSize($data); |
|
745 |
if ($imageinfo === false || !isset($imageinfo['mime'])) { |
|
746 |
$this->warning('COVERART vorbiscomment tag contains invalid image'); |
|
747 |
continue; |
|
748 |
} |
|
749 |
||
750 |
$ogg = new self($this->getid3); |
|
751 |
$ogg->setStringMode($data); |
|
752 |
$info['ogg']['comments']['picture'][] = array( |
|
7
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
753 |
'image_mime' => $imageinfo['mime'], |
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
754 |
'datalength' => strlen($data), |
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
755 |
'picturetype' => 'cover art', |
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
756 |
'image_height' => $imageinfo['height'], |
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
757 |
'image_width' => $imageinfo['width'], |
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
758 |
'data' => $ogg->saveAttachment('coverart', 0, strlen($data), $imageinfo['mime']), |
0 | 759 |
); |
760 |
unset($ogg); |
|
761 |
||
762 |
} else { |
|
763 |
||
764 |
$info['ogg']['comments'][strtolower($ThisFileInfo_ogg_comments_raw[$i]['key'])][] = $ThisFileInfo_ogg_comments_raw[$i]['value']; |
|
765 |
||
766 |
} |
|
767 |
||
768 |
} else { |
|
769 |
||
7
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
770 |
$this->warning('[known problem with CDex >= v1.40, < v1.50b7] Invalid Ogg comment name/value pair ['.$i.']: '.$commentstring); |
0 | 771 |
|
772 |
} |
|
773 |
unset($ThisFileInfo_ogg_comments_raw[$i]); |
|
774 |
} |
|
775 |
unset($ThisFileInfo_ogg_comments_raw); |
|
776 |
||
777 |
||
778 |
// Replay Gain Adjustment |
|
779 |
// http://privatewww.essex.ac.uk/~djmrob/replaygain/ |
|
780 |
if (isset($info['ogg']['comments']) && is_array($info['ogg']['comments'])) { |
|
781 |
foreach ($info['ogg']['comments'] as $index => $commentvalue) { |
|
782 |
switch ($index) { |
|
783 |
case 'rg_audiophile': |
|
784 |
case 'replaygain_album_gain': |
|
785 |
$info['replay_gain']['album']['adjustment'] = (double) $commentvalue[0]; |
|
786 |
unset($info['ogg']['comments'][$index]); |
|
787 |
break; |
|
788 |
||
789 |
case 'rg_radio': |
|
790 |
case 'replaygain_track_gain': |
|
791 |
$info['replay_gain']['track']['adjustment'] = (double) $commentvalue[0]; |
|
792 |
unset($info['ogg']['comments'][$index]); |
|
793 |
break; |
|
794 |
||
795 |
case 'replaygain_album_peak': |
|
796 |
$info['replay_gain']['album']['peak'] = (double) $commentvalue[0]; |
|
797 |
unset($info['ogg']['comments'][$index]); |
|
798 |
break; |
|
799 |
||
800 |
case 'rg_peak': |
|
801 |
case 'replaygain_track_peak': |
|
802 |
$info['replay_gain']['track']['peak'] = (double) $commentvalue[0]; |
|
803 |
unset($info['ogg']['comments'][$index]); |
|
804 |
break; |
|
805 |
||
806 |
case 'replaygain_reference_loudness': |
|
807 |
$info['replay_gain']['reference_volume'] = (double) $commentvalue[0]; |
|
808 |
unset($info['ogg']['comments'][$index]); |
|
809 |
break; |
|
810 |
||
811 |
default: |
|
812 |
// do nothing |
|
813 |
break; |
|
814 |
} |
|
815 |
} |
|
816 |
} |
|
817 |
||
818 |
$this->fseek($OriginalOffset); |
|
819 |
||
820 |
return true; |
|
821 |
} |
|
822 |
||
16 | 823 |
/** |
824 |
* @param int $mode |
|
825 |
* |
|
826 |
* @return string|null |
|
827 |
*/ |
|
0 | 828 |
public static function SpeexBandModeLookup($mode) { |
829 |
static $SpeexBandModeLookup = array(); |
|
830 |
if (empty($SpeexBandModeLookup)) { |
|
831 |
$SpeexBandModeLookup[0] = 'narrow'; |
|
832 |
$SpeexBandModeLookup[1] = 'wide'; |
|
833 |
$SpeexBandModeLookup[2] = 'ultra-wide'; |
|
834 |
} |
|
835 |
return (isset($SpeexBandModeLookup[$mode]) ? $SpeexBandModeLookup[$mode] : null); |
|
836 |
} |
|
837 |
||
16 | 838 |
/** |
839 |
* @param array $OggInfoArray |
|
840 |
* @param int $SegmentNumber |
|
841 |
* |
|
842 |
* @return int |
|
843 |
*/ |
|
0 | 844 |
public static function OggPageSegmentLength($OggInfoArray, $SegmentNumber=1) { |
16 | 845 |
$segmentlength = 0; |
0 | 846 |
for ($i = 0; $i < $SegmentNumber; $i++) { |
847 |
$segmentlength = 0; |
|
848 |
foreach ($OggInfoArray['segment_table'] as $key => $value) { |
|
849 |
$segmentlength += $value; |
|
850 |
if ($value < 255) { |
|
851 |
break; |
|
852 |
} |
|
853 |
} |
|
854 |
} |
|
855 |
return $segmentlength; |
|
856 |
} |
|
857 |
||
16 | 858 |
/** |
859 |
* @param int $nominal_bitrate |
|
860 |
* |
|
861 |
* @return float |
|
862 |
*/ |
|
0 | 863 |
public static function get_quality_from_nominal_bitrate($nominal_bitrate) { |
864 |
||
865 |
// decrease precision |
|
866 |
$nominal_bitrate = $nominal_bitrate / 1000; |
|
867 |
||
868 |
if ($nominal_bitrate < 128) { |
|
869 |
// q-1 to q4 |
|
870 |
$qval = ($nominal_bitrate - 64) / 16; |
|
871 |
} elseif ($nominal_bitrate < 256) { |
|
872 |
// q4 to q8 |
|
873 |
$qval = $nominal_bitrate / 32; |
|
874 |
} elseif ($nominal_bitrate < 320) { |
|
875 |
// q8 to q9 |
|
876 |
$qval = ($nominal_bitrate + 256) / 64; |
|
877 |
} else { |
|
878 |
// q9 to q10 |
|
879 |
$qval = ($nominal_bitrate + 1300) / 180; |
|
880 |
} |
|
881 |
//return $qval; // 5.031324 |
|
882 |
//return intval($qval); // 5 |
|
883 |
return round($qval, 1); // 5 or 4.9 |
|
884 |
} |
|
885 |
||
16 | 886 |
/** |
887 |
* @param int $colorspace_id |
|
888 |
* |
|
889 |
* @return string|null |
|
890 |
*/ |
|
5 | 891 |
public static function TheoraColorSpace($colorspace_id) { |
892 |
// http://www.theora.org/doc/Theora.pdf (table 6.3) |
|
893 |
static $TheoraColorSpaceLookup = array(); |
|
894 |
if (empty($TheoraColorSpaceLookup)) { |
|
895 |
$TheoraColorSpaceLookup[0] = 'Undefined'; |
|
896 |
$TheoraColorSpaceLookup[1] = 'Rec. 470M'; |
|
897 |
$TheoraColorSpaceLookup[2] = 'Rec. 470BG'; |
|
898 |
$TheoraColorSpaceLookup[3] = 'Reserved'; |
|
899 |
} |
|
900 |
return (isset($TheoraColorSpaceLookup[$colorspace_id]) ? $TheoraColorSpaceLookup[$colorspace_id] : null); |
|
901 |
} |
|
902 |
||
16 | 903 |
/** |
904 |
* @param int $pixelformat_id |
|
905 |
* |
|
906 |
* @return string|null |
|
907 |
*/ |
|
5 | 908 |
public static function TheoraPixelFormat($pixelformat_id) { |
909 |
// http://www.theora.org/doc/Theora.pdf (table 6.4) |
|
910 |
static $TheoraPixelFormatLookup = array(); |
|
911 |
if (empty($TheoraPixelFormatLookup)) { |
|
912 |
$TheoraPixelFormatLookup[0] = '4:2:0'; |
|
913 |
$TheoraPixelFormatLookup[1] = 'Reserved'; |
|
914 |
$TheoraPixelFormatLookup[2] = '4:2:2'; |
|
915 |
$TheoraPixelFormatLookup[3] = '4:4:4'; |
|
916 |
} |
|
917 |
return (isset($TheoraPixelFormatLookup[$pixelformat_id]) ? $TheoraPixelFormatLookup[$pixelformat_id] : null); |
|
918 |
} |
|
919 |
||
7
cf61fcea0001
resynchronize code repo with production
ymh <ymh.work@gmail.com>
parents:
5
diff
changeset
|
920 |
} |