rtMedia for WordPress, BuddyPress and bbPress - Version 2.0.1

Version Description

  • Replaced codec finding library
  • Fixed warning on activities page
Download this release

Release Info

Developer rtcamp
Plugin Icon 128x128 rtMedia for WordPress, BuddyPress and bbPress
Version 2.0.1
Comparing to
See all releases

Code changes from version 2.0 to 2.0.1

includes/bp-media-class-wordpress.php CHANGED
@@ -100,23 +100,19 @@ class BP_Media_Host_Wordpress {
100
  switch ($type) {
101
  case 'video/mp4' :
102
  $type = 'video';
103
- include_once(trailingslashit(BP_MEDIA_PLUGIN_DIR) . 'includes/lib/MP4Info.php');
104
  try {
105
- $vid_info = MP4Info::getInfo($file);
106
- } catch (MP4Info_Exception $e) {
107
- wp_delete_post($post_id, true);
108
- unlink($file);
109
- $activity_content = false;
110
- throw new Exception(__('MP4 file you have uploaded is currupt.', 'bp-media'));
111
  } catch (Exception $e) {
112
  wp_delete_post($post_id, true);
113
  unlink($file);
114
  $activity_content = false;
115
  throw new Exception(__('MP4 file you have uploaded is currupt.', 'bp-media'));
116
  }
117
- if (is_object($vid_info)) {
118
- if (isset($vid_info->hasVideo) && $vid_info->hasVideo && isset($vid_info->video)) {
119
- if (!(isset($vid_info->video->codecStr) && $vid_info->video->codecStr == 'H.264')) {
120
  wp_delete_post($post_id, true);
121
  unlink($file);
122
  $activity_content = false;
@@ -126,7 +122,7 @@ class BP_Media_Host_Wordpress {
126
  wp_delete_post($post_id, true);
127
  unlink($file);
128
  $activity_content = false;
129
- throw new Exception(__('The MP4 file you have uploaded contains no video.', 'bp-media'));
130
  }
131
  } else {
132
  wp_delete_post($post_id, true);
100
  switch ($type) {
101
  case 'video/mp4' :
102
  $type = 'video';
103
+ include_once(trailingslashit(BP_MEDIA_PLUGIN_DIR) . 'includes/lib/getid3/getid3.php');
104
  try {
105
+ $getID3 = new getID3;
106
+ $vid_info = $getID3->analyze($file);
 
 
 
 
107
  } catch (Exception $e) {
108
  wp_delete_post($post_id, true);
109
  unlink($file);
110
  $activity_content = false;
111
  throw new Exception(__('MP4 file you have uploaded is currupt.', 'bp-media'));
112
  }
113
+ if (is_array($vid_info)) {
114
+ if (!array_key_exists('error',$vid_info)&& array_key_exists('fileformat', $vid_info) && array_key_exists('video', $vid_info)&&array_key_exists('fourcc',$vid_info['video'])) {
115
+ if (!($vid_info['fileformat']=='mp4'&&$vid_info['video']['fourcc']=='avc1')) {
116
  wp_delete_post($post_id, true);
117
  unlink($file);
118
  $activity_content = false;
122
  wp_delete_post($post_id, true);
123
  unlink($file);
124
  $activity_content = false;
125
+ throw new Exception(__('The MP4 file you have uploaded is using an unsupported video codec. Supported video codec is H.264.', 'bp-media'));
126
  }
127
  } else {
128
  wp_delete_post($post_id, true);
includes/bp-media-filters.php CHANGED
@@ -1,11 +1,11 @@
1
  <?php
2
- function bp_media_activity_permalink_filter($link, $activity_obj) {
3
- if ('media_upload' == $activity_obj->type) {
4
  add_shortcode('bp_media_url', 'bp_media_shortcode_url');
5
  $link = do_shortcode($activity_obj->primary_link);
6
  remove_shortcode('bp_media_url');
7
  }
8
- if ('activity_comment' == $activity_obj->type) {
9
  $parent = bp_activity_get_meta($activity_obj->item_id, 'bp_media_parent_post');
10
  if ($parent) {
11
  $parent = new BP_Media_Host_Wordpress($parent);
@@ -16,8 +16,8 @@ function bp_media_activity_permalink_filter($link, $activity_obj) {
16
  }
17
  add_filter('bp_activity_get_permalink', 'bp_media_activity_permalink_filter', 10, 2);
18
 
19
- function bp_media_activity_action_filter($activity_action, $activity_obj) {
20
- if ('media_upload' == $activity_obj->type) {
21
  add_shortcode('bp_media_action', 'bp_media_shortcode_action');
22
  $activity_action = do_shortcode($activity_action);
23
  remove_shortcode('bp_media_action');
@@ -26,8 +26,8 @@ function bp_media_activity_action_filter($activity_action, $activity_obj) {
26
  }
27
  add_filter('bp_get_activity_action', 'bp_media_activity_action_filter', 10, 2);
28
 
29
- function bp_media_activity_content_filter($activity_content, $activity_obj) {
30
- if ('media_upload' == $activity_obj->type) {
31
  add_shortcode('bp_media_content', 'bp_media_shortcode_content');
32
  $activity_content = do_shortcode($activity_content);
33
  remove_shortcode('bp_media_content');
@@ -44,7 +44,6 @@ function bp_media_activity_parent_content_filter($content) {
44
  remove_shortcode('bp_media_content');
45
  return $content;
46
  }
47
-
48
  add_filter('bp_get_activity_parent_content', 'bp_media_activity_parent_content_filter');
49
 
50
  function bp_media_delete_button_handler($link) {
1
  <?php
2
+ function bp_media_activity_permalink_filter($link, $activity_obj = null) {
3
+ if ($activity_obj != null && 'media_upload' == $activity_obj->type) {
4
  add_shortcode('bp_media_url', 'bp_media_shortcode_url');
5
  $link = do_shortcode($activity_obj->primary_link);
6
  remove_shortcode('bp_media_url');
7
  }
8
+ if ($activity_obj != null && 'activity_comment' == $activity_obj->type) {
9
  $parent = bp_activity_get_meta($activity_obj->item_id, 'bp_media_parent_post');
10
  if ($parent) {
11
  $parent = new BP_Media_Host_Wordpress($parent);
16
  }
17
  add_filter('bp_activity_get_permalink', 'bp_media_activity_permalink_filter', 10, 2);
18
 
19
+ function bp_media_activity_action_filter($activity_action, $activity_obj = null) {
20
+ if ($activity_obj != null && 'media_upload' == $activity_obj->type) {
21
  add_shortcode('bp_media_action', 'bp_media_shortcode_action');
22
  $activity_action = do_shortcode($activity_action);
23
  remove_shortcode('bp_media_action');
26
  }
27
  add_filter('bp_get_activity_action', 'bp_media_activity_action_filter', 10, 2);
28
 
29
+ function bp_media_activity_content_filter($activity_content, $activity_obj = null ) {
30
+ if ($activity_obj != null && 'media_upload' == $activity_obj->type) {
31
  add_shortcode('bp_media_content', 'bp_media_shortcode_content');
32
  $activity_content = do_shortcode($activity_content);
33
  remove_shortcode('bp_media_content');
44
  remove_shortcode('bp_media_content');
45
  return $content;
46
  }
 
47
  add_filter('bp_get_activity_parent_content', 'bp_media_activity_parent_content_filter');
48
 
49
  function bp_media_delete_button_handler($link) {
includes/bp-media-functions.php CHANGED
@@ -69,8 +69,8 @@ function bp_media_show_formatted_error_message($messages, $type) {
69
  echo '</div>';
70
  }
71
 
72
- function bp_media_conditional_override_allowed_tags($content, $activity) {
73
- if ($activity->type == 'media_upload') {
74
  add_filter('bp_activity_allowed_tags', 'bp_media_override_allowed_tags', 1);
75
  }
76
  return bp_activity_filter_kses($content);
69
  echo '</div>';
70
  }
71
 
72
+ function bp_media_conditional_override_allowed_tags($content, $activity=null) {
73
+ if ($activity != null && $activity->type == 'media_upload') {
74
  add_filter('bp_activity_allowed_tags', 'bp_media_override_allowed_tags', 1);
75
  }
76
  return bp_activity_filter_kses($content);
includes/lib/MP4Info.php DELETED
@@ -1,257 +0,0 @@
1
- <?php
2
- /**
3
- * MP4Info
4
- *
5
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
6
- * @copyright Copyright (c) 2006-2009 Tommy Lacroix
7
- * @license LGPL version 3, http://www.gnu.org/licenses/lgpl.html
8
- * @package php-mp4info
9
- * @link $HeadURL: https://php-mp4info.googlecode.com/svn/trunk/MP4Info.php $
10
- */
11
-
12
- // ---
13
-
14
- /**
15
- * MP4Info main class
16
- *
17
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
18
- * @version 1.1.20090611 $Id: MP4Info.php 2 2009-06-11 14:12:31Z lacroix.tommy@gmail.com $
19
- */
20
- class MP4Info {
21
- // {{{ Audio codec types
22
- const MP4_AUDIO_CODEC_UNCOMPRESSED = 0x00;
23
- const MP4_AUDIO_CODEC_MP3 = 0x02;
24
- const MP4_AUDIO_CODEC_AAC = 0xe0;
25
- // }}}
26
-
27
- // {{{ Video codec types
28
- const MP4_VIDEO_CODEC_H264 = 0xe0;
29
- // }}}
30
-
31
-
32
- /**
33
- * Get information from MP4 file
34
- *
35
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
36
- * @param string $file
37
- * @return array
38
- * @access public
39
- * @static
40
- */
41
- public static function getInfo($file) {
42
- // Open file
43
- $f = fopen($file,'rb');
44
- if (!$f) {
45
- throw new Exception('Cannot open file: '.$file);
46
- }
47
-
48
- // Get all boxes
49
- try {
50
- while (($box = MP4Info_Box::fromStream($f))) {
51
- $boxes[] = $box;
52
- }
53
- } catch (Exception $e) { }
54
-
55
- // Close
56
- fclose($f);
57
-
58
- // Return info
59
- return self::getInfoFromBoxes($boxes);
60
- } // getInfo method
61
-
62
-
63
- /**
64
- * Get information from MP4 boxes
65
- *
66
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
67
- * @param string $file
68
- * @return array
69
- * @access public
70
- * @static
71
- */
72
- public static function getInfoFromBoxes($boxes, &$context=null) {
73
- if ($context === null) {
74
- $context = new stdClass();
75
- $context->hasVideo = false;
76
- $context->hasAudio = false;
77
- $context->video = new stdClass();
78
- $context->audio = new stdClass();
79
- }
80
-
81
- foreach ($boxes as &$box) {
82
- // Interpret box
83
- switch ($box->getBoxTypeStr()) {
84
- case 'hdlr':
85
- switch ($box->getHandlerType()) {
86
- case MP4Info_Box_hdlr::HANDLER_VIDEO:
87
- $context->hasVideo = true;
88
- break;
89
- case MP4Info_Box_hdlr::HANDLER_SOUND:
90
- $context->hasAudio = true;
91
- break;
92
- }
93
- break;
94
- case 'mvhd':
95
- $context->duration = $box->getRealDuration();
96
- break;
97
- case 'ilst':
98
- if ($box->hasValue('©too')) {
99
- $context->encoder = $box->getValue('©too');
100
- }
101
- break;
102
- case 'uuid':
103
- $meta = $box->getXMPMetaData();
104
- if ($meta !== false) {
105
- // Try to get duration
106
- if (!isset($context->duration)) {
107
- if (preg_match('/<(|[a-z]+:)duration[\s\n\r]([^>]*)>/im',$meta,$m)) {
108
- if (preg_match_all('/xmpDM:([a-z]+)="([^"]+)"/',$m[2],$mm)) {
109
- $value = $scale = false;
110
- foreach ($mm[1] as $k=>$v) {
111
- if (($v == 'value') || ($v == 'scale')) {
112
- if (preg_match('/^1\/([0-9]+)$/',$mm[2][$k],$mmm)) {
113
- $mm[2][$k] = 1/$mmm[1];
114
- }
115
- $$v = $mm[2][$k];
116
- }
117
- }
118
- if (($value !== false) && ($scale !== false)) {
119
- $context->duration = $value*$scale;
120
- }
121
- }
122
- }
123
- }
124
-
125
- // Try to get size
126
- if ((!isset($context->width)) || (!isset($context->height))) {
127
- if (preg_match('/<(|[a-z]+:)videoFrameSize[\s\n\r]([^>]*)>/im',$meta,$m)) {
128
- if (preg_match_all('/[a-z]:([a-z]+)="([^"]+)"/',$m[2],$mm)) {
129
- $w = $h = false;
130
- foreach ($mm[1] as $k=>$v) {
131
- if (($v == 'w') || ($v == 'h')) {
132
- $$v = $mm[2][$k];
133
- }
134
- }
135
- if ($w != false) {
136
- $context->video->width = $w;
137
- $context->hasVideo = true;
138
- }
139
- if ($h != false) {
140
- $context->video->height = $h;
141
- $context->hasVideo = true;
142
- }
143
- }
144
- }
145
- }
146
-
147
- // Try to get encoder
148
- if (preg_match('/softwareAgent="([^"]+)"/i',$meta,$m)) {
149
- $context->encoder = $m[1];
150
- }
151
-
152
- // Try to get audio channels
153
- if (preg_match('/audioChannelType="([^"]+)"/i',$meta,$m)) {
154
- switch (strtolower($m[1])) {
155
- case 'stereo':
156
- case '2':
157
- $context->audio->channels = 2;
158
- $context->hasAudio = true;
159
- break;
160
- case 'mono':
161
- case '1':
162
- $context->audio->channels = 1;
163
- $context->hasAudio = true;
164
- break;
165
- case '5.1':
166
- case '5':
167
- $context->audio->channels = 5;
168
- $context->hasAudio = true;
169
- break;
170
- }
171
- }
172
-
173
- // Try to get audio frequency
174
- if (preg_match('/audioSampleRate="([^"]+)"/i',$meta,$m)) {
175
- $context->audio->frequency = $m[1]/1000;
176
- $context->hasAudio = true;
177
- }
178
-
179
- // Try to get video frame rate
180
- if (preg_match('/videoFrameRate="([^"]+)"/i',$meta,$m)) {
181
- $context->video->fps = $m[1];
182
- $context->hasVideo = true;
183
- }
184
-
185
- //print htmlentities($meta);
186
- }
187
- break;
188
- case 'stsd':
189
- $values = $box->getValues();
190
- foreach (array_keys($values) as $codec) {
191
- switch ($codec) {
192
- case '.mp3':
193
- $context->audio->codec = self::MP4_AUDIO_CODEC_MP3;
194
- $context->audio->codecStr = 'MP3';
195
- $context->hasAudio = true;
196
- break;
197
- case 'mp4a':
198
- case 'mp4s':
199
- $context->audio->codec = self::MP4_AUDIO_CODEC_AAC;
200
- $context->audio->codecStr = 'AAC';
201
- $context->hasAudio = true;
202
- break;
203
- case 'avc1':
204
- case 'h264':
205
- case 'H264':
206
- $context->video->codec = self::MP4_VIDEO_CODEC_H264;
207
- $context->video->codecStr = 'H.264';
208
- $context->hasVideo = true;
209
- break;
210
- }
211
- }
212
- break;
213
- case 'tkhd':
214
- if ($box->getWidth() > 0) {
215
- $context->hasVideo = true;
216
- $context->video->width = $box->getWidth();
217
- $context->video->height = $box->getHeight();
218
- $context->hasVideo = true;
219
- }
220
- break;
221
- }
222
-
223
- // Process children
224
- if ($box->hasChildren()) {
225
- self::getInfoFromBoxes($box->children(), $context);
226
- }
227
- }
228
-
229
- return $context;
230
- } // getInfoFromBoxes method
231
-
232
-
233
- /**
234
- * Display boxes for debugging
235
- *
236
- * @param MP4Info_Box[] $boxes
237
- * @param int $level
238
- * @access public
239
- * @static
240
- */
241
- public static function displayBoxes($boxes,$level=0) {
242
- foreach ($boxes as $box) {
243
- print str_repeat('&nbsp;',$level*4) . $box->toString() . '<br>';
244
- if ($box->hasChildren()) {
245
- $this->displayBoxes($box->children(), $level+1);
246
- }
247
- }
248
- } // displayBoxes method
249
- } // MP4Info class
250
-
251
- // ---
252
-
253
- // {{{ Dependencies
254
- include "MP4Info/Helper.php";
255
- include "MP4Info/Exception.php";
256
- include "MP4Info/Box.php";
257
- // }}} Dependencies
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/lib/MP4Info/Box.php DELETED
@@ -1,446 +0,0 @@
1
- <?php
2
- /**
3
- * MP4Info
4
- *
5
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
6
- * @copyright Copyright (c) 2006-2009 Tommy Lacroix
7
- * @license LGPL version 3, http://www.gnu.org/licenses/lgpl.html
8
- * @package php-mp4info
9
- * @link $HeadURL: https://php-mp4info.googlecode.com/svn/trunk/MP4Info/Box.php $
10
- *
11
- * Based on:
12
- * - http://www.geocities.com/xhelmboyx/quicktime/formats/mp4-layout.txt
13
- * - http://neuron2.net/library/avc/c041828_ISO_IEC_14496-12_2005(E).pdf
14
- * - http://www.adobe.com/devnet/flv/pdf/video_file_format_spec_v10.pdf
15
- */
16
-
17
- // ---
18
-
19
- /**
20
- * MP4Info General Box
21
- *
22
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
23
- * @version 1.1.20090611 $Id: Box.php 2 2009-06-11 14:12:31Z lacroix.tommy@gmail.com $
24
- */
25
- class MP4Info_Box {
26
- /**
27
- * Total box size, including box header (8 bytes)
28
- *
29
- * @var int
30
- */
31
- protected $totalSize;
32
-
33
- /**
34
- * Box type, numeric
35
- *
36
- * @var int
37
- */
38
- protected $boxType;
39
-
40
- /**
41
- * Box type, string
42
- *
43
- * @var string
44
- */
45
- protected $boxTypeStr;
46
-
47
- /**
48
- * Box data
49
- *
50
- * @var string(binary)
51
- */
52
- protected $data;
53
-
54
- /**
55
- * Parent
56
- *
57
- * @var MP4Info_Box|false
58
- */
59
- protected $parent;
60
-
61
- /**
62
- * Children
63
- *
64
- * @var MP4Info_Box[]
65
- */
66
- protected $children = array();
67
-
68
- /**
69
- * Constructor
70
- *
71
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
72
- * @param int $totalSize
73
- * @param int $boxType
74
- * @param file|string $f
75
- * @param MP4Info_Box $parent
76
- * @access public
77
- */
78
- public function __construct($totalSize, $boxType, $f, $parent=false) {
79
- $this->totalSize = $totalSize;
80
- $this->boxType = $boxType;
81
- $this->boxTypeStr = pack('N',$boxType);
82
- $this->data = self::getDataFrom3rd($f,$totalSize);
83
- $this->parent = $parent;
84
- if ($parent != false) {
85
- $parent->addChild($this);
86
- }
87
- } // Constructor
88
-
89
- /**
90
- * Add a child to this box
91
- *
92
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
93
- * @param MP4Info_Box $child
94
- * @access public
95
- */
96
- public function addChild(&$child) {
97
- if (!$child instanceof MP4Info_Box) {
98
- throw new Exception('Child is not MP4Info_Box');
99
- }
100
- $this->children[] = &$child;
101
- }
102
-
103
- /**
104
- * Check if the box has children
105
- *
106
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
107
- * @return bool
108
- * @access public
109
- */
110
- public function hasChildren() {
111
- return count($this->children) > 0;
112
- } // hasChildren method
113
-
114
- /**
115
- * Get boxes' children
116
- *
117
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
118
- * @return MP4Info_Box[]
119
- * @access public
120
- */
121
- public function children() {
122
- return $this->children;
123
- } // children method
124
-
125
-
126
- /**
127
- * Get data from 3rd argument (file or string)
128
- *
129
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
130
- * @param file|string $f
131
- * @param int $totalSize
132
- * @return string
133
- * @access public
134
- * @static
135
- */
136
- public static function getDataFrom3rd($f, $totalSize) {
137
- // Get data
138
- if ($f === false) {
139
- return '';
140
- } else if (is_string($f)) {
141
- $data = substr($f,0,$totalSize-8);
142
- } else {
143
- $data = fread($f,$totalSize-8);
144
- }
145
-
146
- return $data;
147
- } // getDataFrom3rd method
148
-
149
-
150
- /**
151
- * Create an MP4Info_Box object from data
152
- *
153
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
154
- * @param int $totalSize
155
- * @param int $boxType
156
- * @param file|string $f
157
- * @param MP4Info_Box|false $parent
158
- * @return MP4Info_Box
159
- * @access public
160
- * @static
161
- */
162
- public static function factory($totalSize, $boxType, $f, $parent=false) {
163
- if (MP4Info_Box_Container::isCompatible($boxType,$parent)) {
164
- $box = new MP4Info_Box_Container($totalSize, $boxType, $f, $parent);
165
- } else if (MP4Info_Box_ftyp::isCompatible($boxType,$parent)) {
166
- $box = new MP4Info_Box_ftyp($totalSize, $boxType, $f, $parent);
167
- } else if (MP4Info_Box_uuid::isCompatible($boxType,$parent)) {
168
- $box = new MP4Info_Box_uuid($totalSize, $boxType, $f, $parent);
169
- } else if (MP4Info_Box_hdlr::isCompatible($boxType,$parent)) {
170
- $box = new MP4Info_Box_hdlr($totalSize, $boxType, $f, $parent);
171
- } else if (MP4Info_Box_mvhd::isCompatible($boxType,$parent)) {
172
- $box = new MP4Info_Box_mvhd($totalSize, $boxType, $f, $parent);
173
- } else if (MP4Info_Box_tkhd::isCompatible($boxType,$parent)) {
174
- $box = new MP4Info_Box_tkhd($totalSize, $boxType, $f, $parent);
175
- } else if (MP4Info_Box_mdhd::isCompatible($boxType,$parent)) {
176
- $box = new MP4Info_Box_mdhd($totalSize, $boxType, $f, $parent);
177
- } else if (MP4Info_Box_meta::isCompatible($boxType,$parent)) {
178
- $box = new MP4Info_Box_meta($totalSize, $boxType, $f, $parent);
179
- } else if (MP4Info_Box_stsd::isCompatible($boxType,$parent)) {
180
- $box = new MP4Info_Box_stsd($totalSize, $boxType, $f, $parent);
181
- } else if (MP4Info_Box_ilst::isCompatible($boxType,$parent)) {
182
- $box = new MP4Info_Box_ilst($totalSize, $boxType, $f, $parent);
183
- } else if (MP4Info_Box_ilst_sub::isCompatible($boxType,$parent)) {
184
- $box = new MP4Info_Box_ilst_sub($totalSize, $boxType, $f, $parent);
185
- } else {
186
- throw new Exception('Media type error');
187
- }
188
-
189
- // Return box
190
- return $box;
191
- } // factory method
192
-
193
-
194
- /**
195
- * Create a box from string
196
- *
197
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
198
- * @param string $data
199
- * @param MP4Info_Box|false $parent
200
- * @return MP4Info_Box
201
- * @access public
202
- * @static
203
- */
204
- public static function fromString(&$data,$parent=false) {
205
- if (strlen($data) < 8) {
206
- throw new Exception('Not enough data, need at least 8 bytes!');
207
- }
208
-
209
- $ar = unpack('NtotalSize/NboxType',$data);
210
- if ($ar['totalSize'] == 1) {
211
- // Size is bigger than 4GB :-O die
212
- // Skip ExtendedSize(UI64) and try to decode anyway
213
- $ar2 = unpack('N2extSize',substr($data,8));
214
- if ($ar2['extSize1'] > 0) {
215
- throw new Exception('Extended size not supported');
216
- } else {
217
- $ar['totalSize'] = $ar2['extSize2'];
218
- }
219
- $skip = 8;
220
- } else {
221
- $skip = 0;
222
- }
223
-
224
- // Check if we need to skip
225
- if (self::skipBoxType($ar['boxType'])) {
226
- //print '+++ Skipping box '.pack('N',$ar['boxType']).'<br>';
227
- $data = substr($data,$ar['totalSize']);
228
- return self::fromString($data,$parent);
229
- }
230
-
231
- // Check if box is a container, and skip to content if so
232
- if (self::ignoreBoxType($ar['boxType'])) {
233
- //print '+++ Ignoring box '.pack('N',$ar['boxType']).'<br>';
234
- $data = substr($data,8+$skip);
235
- return self::fromString($data,$parent);
236
- }
237
-
238
- // Create box
239
- $box = self::factory($ar['totalSize'],$ar['boxType'],substr($data,8+$skip),$parent);
240
- if ($box instanceof MP4Info_Box) {
241
- $data = substr($data,$box->getTotalSize());
242
- }
243
-
244
- return $box;
245
- } // fromString method
246
-
247
-
248
- /**
249
- * Create a box from file stream
250
- *
251
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
252
- * @param file $f
253
- * @param MP4Info_Box|false $parent
254
- * @return MP4Info_Box
255
- * @access public
256
- * @static
257
- */
258
- public static function fromStream($f,$parent=false) {
259
- // Get box header
260
- $buf = fread($f,8);
261
- if (strlen($buf) < 8) {
262
- return false;
263
- }
264
- $ar = unpack('NtotalSize/NboxType',$buf);
265
- if ($ar['totalSize'] == 1) {
266
- // Size is bigger than 4GB :-O die
267
- // Skip ExtendedSize(UI64) and try to decode anyway
268
- $buf = fread($f,8);
269
- $ar2 = unpack('N2extSize',$buf);
270
- if ($ar2['extSize1'] > 0) {
271
- throw new Exception('Extended size not supported');
272
- } else {
273
- $ar['totalSize'] = $ar2['extSize2'];
274
- }
275
- }
276
-
277
- // Check if we need to skip
278
- if (self::skipBoxType($ar['boxType'])) {
279
- //print '+++ Skipping box '.pack('N',$ar['boxType']).'<br>';
280
- fseek($f,$ar['totalSize']-8,SEEK_CUR);
281
- return self::fromStream($f,$parent);
282
- }
283
-
284
- // Check if box is a container, and skip it if so
285
- if (self::ignoreBoxType($ar['boxType'])) {
286
- //print '+++ Ignoring box '.pack('N',$ar['boxType']).' of size '.$ar['totalSize'].'<br>';
287
- return self::fromStream($f,$parent);
288
- }
289
-
290
- // Get box content
291
- if ($ar['totalSize'] > 0) {
292
- if ($ar['totalSize'] < 256*1024) {
293
- $data = fread($f,$ar['totalSize']-8);
294
- } else {
295
- $data = $f;
296
- }
297
- } else {
298
- $data = '';
299
- }
300
-
301
- // Create box object
302
- $box = MP4Info_Box::factory($ar['totalSize'], $ar['boxType'], $data, $parent);
303
- //print 'Got box from stream of type 0x'.dechex($ar['boxType']).'('.pack('N',$ar['boxType']).') and size '.$ar['totalSize'].' bytes: '.$box->toString().'<br>';
304
- return $box;
305
- } // fromStream method
306
-
307
-
308
- /**
309
- * Check if we need to ignore that box, based on type
310
- *
311
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
312
- * @param int $boxType
313
- * @return bool
314
- * @access public
315
- * @static
316
- * @todo Cleanup, legacy stuff
317
- */
318
- public static function ignoreBoxType($boxType) {
319
- return false;
320
- } // ignoreBoxType method
321
-
322
-
323
- /**
324
- * Check if we need to skip a box based on type
325
- *
326
- * @param int $boxType
327
- * @return bool
328
- * @access public
329
- * @static
330
- */
331
- public static function skipBoxType($boxType) {
332
- switch ($boxType) {
333
- case 0x696f6473: // iods 5.1 Initial Object Descriptor Box
334
- case 0x55c40000: // ??? ??
335
- case 0x6d646174: // mdat ?? Movie Data
336
- case 0x736d6864: // smhd 8.11.3 Sound Media Header Box
337
- case 0x766d6864: // vmhd 8.11.2 Video Media Header Box
338
- case 0x6e6d6864: // nmhd 8.11.5 Null Media Header Box
339
- case 0x64696e66: // dinf ??
340
- case 0x73747473: // stts 8.15.2 Decoding Time to Sample Box
341
- case 0x73747363: // stsc 8.18 Sample To Chunk Box
342
- case 0x7374737a: // stsz 8.17 Sample Size Boxes
343
- case 0x7374636f: // stco 8.19 Chunk Offset Box
344
- case 0x636f3634: // co64 8.19 Chunk Offset Box
345
- case 0x63747473: // ctts 8.15.3 Composition Time to Sample Box
346
- case 0x73747373: // stss 8.20 Sync Sample Box
347
- case 0x74726566: // tref 8.6 Track Reference Box
348
- return true;
349
- default:
350
- return false;
351
- }
352
- } // skipBoxType method
353
-
354
-
355
- /**
356
- * Total size getter
357
- *
358
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
359
- * @return int
360
- * @access public
361
- */
362
- public function getTotalSize() {
363
- return $this->totalSize;
364
- } // getTotalSize method
365
-
366
-
367
- /**
368
- * Box type getter
369
- *
370
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
371
- * @return int
372
- * @access public
373
- */
374
- public function getBoxType() {
375
- return $this->boxType;
376
- } // getBoxType method
377
-
378
-
379
- /**
380
- * Box type string getter
381
- *
382
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
383
- * @return string
384
- * @access public
385
- */
386
- public function getBoxTypeStr() {
387
- return $this->boxTypeStr;
388
- } // getBoxTypeStr method
389
-
390
-
391
- /**
392
- * Data getter
393
- *
394
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
395
- * @return string(binary)
396
- * @access public
397
- */
398
- public function getData() {
399
- return $this->data;
400
- } // getData method
401
-
402
-
403
- /**
404
- * stdClass converter
405
- *
406
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
407
- * @return stdClass
408
- * @access public
409
- */
410
- public function toStdClass() {
411
- $a = new stdClass();
412
- foreach ($this as $propName=>$prop) {
413
- if (($propName != 'children') && ($propName != 'parent') && ($propName != 'boxes')) {
414
- $a->{$propName} = $prop;
415
- }
416
- }
417
- return $a;
418
- } // toStdClass method
419
-
420
-
421
- /**
422
- * String converter
423
- *
424
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
425
- * @return string
426
- * @access public
427
- */
428
- public function toString() {
429
- return '[MP4Info_Box[0x'.dechex($this->boxType).'('.pack('N',$this->boxType).']]';
430
- } // toString method
431
- } // MP4Info_Box class
432
-
433
-
434
- // {{{ Dependencies
435
- include "Box/Container.php";
436
- include "Box/ftyp.php";
437
- include "Box/uuid.php";
438
- include "Box/hdlr.php";
439
- include "Box/tkhd.php";
440
- include "Box/mvhd.php";
441
- include "Box/mdhd.php";
442
- include "Box/meta.php";
443
- include "Box/stsd.php";
444
- include "Box/ilst.php";
445
- include "Box/ilst_sub.php";
446
- // }}} Dependencies
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/lib/MP4Info/Box/Container.php DELETED
@@ -1,99 +0,0 @@
1
- <?php
2
- /**
3
- * MP4Info
4
- *
5
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
6
- * @copyright Copyright (c) 2006-2009 Tommy Lacroix
7
- * @license LGPL version 3, http://www.gnu.org/licenses/lgpl.html
8
- * @package php-mp4info
9
- * @link $HeadURL: https://php-mp4info.googlecode.com/svn/trunk/MP4Info/Box/Container.php $
10
- */
11
-
12
- // ---
13
-
14
- /**
15
- * Generic container box
16
- *
17
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
18
- * @version 1.1.20090611 $Id: Container.php 2 2009-06-11 14:12:31Z lacroix.tommy@gmail.com $
19
- */
20
- class MP4Info_Box_Container extends MP4Info_Box {
21
- /**
22
- * Constructor
23
- *
24
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
25
- * @param int $totalSize
26
- * @param int $boxType
27
- * @param file|string $data
28
- * @param MP4Info_Box $parent
29
- * @return MP4Info_Box_Container
30
- * @access public
31
- * @throws MP4Info_Exception
32
- */
33
- public function __construct($totalSize, $boxType, $data, $parent) {
34
- if (!self::isCompatible($boxType, $parent)) {
35
- throw new MP4Info_Exception('This box isn\'t a container',MP4Info_Exception::CODE_INCOMPATIBLE,$boxType);
36
- }
37
-
38
- // Call ancestor
39
- parent::__construct($totalSize, $boxType, false, $parent);
40
-
41
- // Unpack
42
- if (is_string($data)) {
43
- while ($data != '') {
44
- try {
45
- $box = MP4Info_Box::fromString($data, $this);
46
- if (!$box instanceof MP4Info_Box) {
47
- break;
48
- }
49
- } catch (Exception $e) {
50
- break;
51
- }
52
- }
53
- } else {
54
- do {
55
- try {
56
- $box = MP4Info_Box::fromStream($data, $this);
57
- if (!$box instanceof MP4Info_Box) {
58
- break;
59
- }
60
- } catch (Exception $e) {
61
- break;
62
- }
63
- } while ($box !== false);
64
- }
65
- } // Constructor
66
-
67
-
68
- /**
69
- * Check if block is compatible with class
70
- *
71
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
72
- * @param int $boxType
73
- * @param MP4Info_Box $parent
74
- * @return bool
75
- * @access public
76
- * @static
77
- */
78
- public static function isCompatible($boxType, $parent) {
79
- return ($boxType == 0x6D6F6F76) || // moov
80
- ($boxType == 0x7472616B) || // trak
81
- ($boxType == 0x6d646961) || // mdia
82
- ($boxType == 0x6D696E66) || // minf
83
- ($boxType == 0x7374626c) || // stbl
84
- ($boxType == 0x75647461) || // udta
85
- false;
86
- } // isCompatible method
87
-
88
-
89
- /**
90
- * String converter
91
- *
92
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
93
- * @return string
94
- * @access public
95
- */
96
- public function toString() {
97
- return '[MP4Info_Box_Container['.$this->boxTypeStr.']:'.count($this->children).']';
98
- } // toString method
99
- } // MP4Info_Box_Container method
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/lib/MP4Info/Box/ftyp.php DELETED
@@ -1,231 +0,0 @@
1
- <?php
2
- /**
3
- * MP4Info
4
- *
5
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
6
- * @copyright Copyright (c) 2006-2009 Tommy Lacroix
7
- * @license LGPL version 3, http://www.gnu.org/licenses/lgpl.html
8
- * @package php-mp4info
9
- * @link $HeadURL: https://php-mp4info.googlecode.com/svn/trunk/MP4Info/Box/ftyp.php $
10
- */
11
-
12
- // ---
13
-
14
- /**
15
- * 4.3 File Type Box (FTYP)
16
- *
17
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
18
- * @version 1.1.20090611 $Id: ftyp.php 2 2009-06-11 14:12:31Z lacroix.tommy@gmail.com $
19
- */
20
- class MP4Info_Box_ftyp extends MP4Info_Box {
21
- /**
22
- * Major brand
23
- *
24
- * @var int
25
- */
26
- protected $majorBrand;
27
-
28
- /**
29
- * Minor brand
30
- *
31
- * @var int
32
- */
33
- protected $minorBrand;
34
-
35
- /**
36
- * Compatible brands
37
- *
38
- * @var int[]
39
- */
40
- protected $compatibleBrands;
41
-
42
- /**
43
- * Major Brands' names
44
- *
45
- * @var {n:Str,n:Str,...}
46
- * @static
47
- */
48
- protected static $brandNames = array(
49
- '3g2a' => '3GPP2 Media (.3G2)',
50
- '3ge6' => '3GPP (.3GP) Release 6 MBMS Extended Presentations',
51
- '3ge7' => '3GPP (.3GP) Release 7 MBMS Extended Presentations',
52
- '3gg6' => '3GPP Release 6 General Profile',
53
- '3gp1' => '3GPP Media (.3GP) Release 1 ? (non-existent)',
54
- '3gp2' => '3GPP Media (.3GP) Release 2 ? (non-existent)',
55
- '3gp3' => '3GPP Media (.3GP) Release 3 ? (non-existent)',
56
- '3gp4' => '3GPP Media (.3GP) Release 4',
57
- '3gp5' => '3GPP Media (.3GP) Release 5',
58
- '3gp6' => '3GPP Media (.3GP) Release 6 Basic Profile',
59
- '3gr6' => '3GPP Media (.3GP) Release 6 Progressive Download',
60
- '3gs6' => '3GPP Media (.3GP) Release 6 Streaming Servers',
61
- '3gs7' => '3GPP Media (.3GP) Release 7 Streaming Servers',
62
- 'avc1' => 'MP4 Base w/ AVC ext [ISO 14496-12:2005]',
63
- 'caep' => 'Canon Digital Camera',
64
- 'caqv' => 'Casio Digital Camera',
65
- 'cdes' => 'Convergent Design',
66
- 'f4v' => 'Video for Adobe Flash Player 9+ (.F4V)',
67
- 'f4p' => 'Protected Video for Adobe Flash Player 9+ (.F4P)',
68
- 'f4a' => 'Audio for Adobe Flash Player 9+ (.F4A)',
69
- 'f4b' => 'Audio Book for Adobe Flash Player 9+ (.F4B)',
70
- 'isc2' => 'ISMACryp 2.0 Encrypted File',
71
- 'iso2' => 'MP4 Base Media v2 [ISO 14496-12:2005]',
72
- 'isom' => 'MP4  Base Media v1 [IS0 14496-12:2003]',
73
- 'jp2' => 'JPEG 2000 Image (.JP2) [ISO 15444-1 ?]',
74
- 'jp20' => 'Unknown, from GPAC samples (prob non-existent)',
75
- 'jpm' => 'JPEG 2000 Compound Image (.JPM) [ISO 15444-6]',
76
- 'jpx' => 'JPEG 2000 w/ extensions (.JPX) [ISO 15444-2]',
77
- 'kddi' => '3GPP2 EZmovie for KDDI 3G Cellphones',
78
- 'm4a ' => 'Apple iTunes AAC-LC (.M4A) Audio',
79
- 'm4b ' => 'Apple iTunes AAC-LC (.M4B) Audio Book',
80
- 'm4p ' => 'Apple iTunes AAC-LC (.M4P) AES Protected Audio',
81
- 'm4v ' => 'Apple iTunes Video (.M4V) Video',
82
- 'm4vh' => 'Apple TV (.M4V)',
83
- 'm4vp' => 'Apple iPhone (.M4V)',
84
- 'mj2s' => 'Motion JPEG 2000 [ISO 15444-3] Simple Profile',
85
- 'mjp2' => 'Motion JPEG 2000 [ISO 15444-3] General Profile',
86
- 'mmp4' => 'MPEG-4/3GPP Mobile Profile (.MP4 / .3GP) (for NTT)',
87
- 'mp21' => 'MPEG-21 [ISO/IEC 21000-9]',
88
- 'mp41' => 'MP4 v1 [ISO 14496-1:ch13]',
89
- 'mp42' => 'MP4 v2 [ISO 14496-14]',
90
- 'mp71' => 'MP4 w/ MPEG-7 Metadata [per ISO 14496-12]',
91
- 'mppi' => 'Photo Player, MAF [ISO/IEC 23000-3]',
92
- 'mqt' => 'Sony / Mobile QuickTime (.MQV)',
93
- 'msnv' => 'MPEG-4 (.MP4) for SonyPSP',
94
- 'ndas' => 'MP4 v2 [ISO 14496-14] Nero Digital AAC Audio',
95
- 'ndsc' => 'MPEG-4 (.MP4) Nero Cinema Profile',
96
- 'ndsh' => 'MPEG-4 (.MP4) Nero HDTV Profile',
97
- 'ndsm' => 'MPEG-4 (.MP4) Nero Mobile Profile',
98
- 'ndsp' => 'MPEG-4 (.MP4) Nero Portable Profile',
99
- 'ndss' => 'MPEG-4 (.MP4) Nero Standard Profile',
100
- 'ndxc' => 'H.264/MPEG-4 AVC (.MP4) Nero Cinema Profile',
101
- 'ndxh' => 'H.264/MPEG-4 AVC (.MP4) Nero HDTV Profile',
102
- 'ndxm' => 'H.264/MPEG-4 AVC (.MP4) Nero Mobile Profile',
103
- 'ndxp' => 'H.264/MPEG-4 AVC (.MP4) Nero Portable Profile',
104
- 'ndxs' => 'H.264/MPEG-4 AVC (.MP4) Nero Standard Profile',
105
- 'odcf  ' => 'OMA DCF DRM Format 2.0 (OMA-TS-DRM-DCF-V2_0-20060303-A)',
106
- 'opf2 ' => 'OMA PDCF DRM Format 2.1 (OMA-TS-DRM-DCF-V2_1-20070724-C)',
107
- 'opx2  ' => 'OMA PDCF DRM + XBS extensions (OMA-TS-DRM_XBS-V1_0-20070529-C)',
108
- 'qt  ' => 'Apple QuickTime (.MOV/QT)',
109
- 'sdv' => 'SD Memory Card Video',
110
- );
111
-
112
-
113
- /**
114
- * Constructor
115
- *
116
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
117
- * @param int $totalSize
118
- * @param int $boxType
119
- * @param file|string $data
120
- * @param MP4Info_Box $parent
121
- * @return MP4Info_Box_ftyp
122
- * @access public
123
- * @throws MP4Info_Exception
124
- */
125
- public function __construct($totalSize, $boxType, $data, $parent) {
126
- if (!self::isCompatible($boxType, $parent)) {
127
- throw new MP4Info_Exception('This box isn\'t "ftyp"',MP4Info_Exception::CODE_INCOMPATIBLE,$boxType);
128
- }
129
-
130
- // Call ancestor
131
- parent::__construct($totalSize, $boxType, false, $parent);
132
-
133
- // Get data
134
- $data = self::getDataFrom3rd($data, $totalSize);
135
-
136
- // Unpack
137
- $ar = unpack('NmajorBrand/NminorVersion/N*compatibleBrands',$data);
138
- $compatibleBrands = array();
139
- foreach ($ar as $k=>$v) {
140
- if (substr($k,0,16) == 'compatibleBrands') {
141
- $compatibleBrands[] = $v;
142
- }
143
- }
144
-
145
- // Save properties
146
- $this->majorBrand = $ar['majorBrand'];
147
- $this->minorVersion = $ar['minorVersion'];
148
- $this->compatibleBrands = $compatibleBrands;
149
- } // Constructor
150
-
151
-
152
- /**
153
- * Check if block is compatible with class
154
- *
155
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
156
- * @param int $boxType
157
- * @param MP4Info_Box $parent
158
- * @return bool
159
- * @access public
160
- * @static
161
- */
162
- static function isCompatible($boxType, $parent) {
163
- return $boxType == 0x66747970;
164
- } // isCompatible method
165
-
166
-
167
- /**
168
- * Major brand getter
169
- *
170
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
171
- * @return int
172
- * @access public
173
- */
174
- public function getMajorBrand() {
175
- return $this->majorBrand;
176
- } // getMajorBrand method
177
-
178
-
179
- /**
180
- * Minor version getter
181
- *
182
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
183
- * @return int
184
- * @access public
185
- */
186
- public function getMinorVersion() {
187
- return $this->minorVersion;
188
- } // getMinorVersion method
189
-
190
-
191
- /**
192
- * Compatible brands getter
193
- *
194
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
195
- * @return int[]
196
- * @access public
197
- */
198
- public function getCompatibleBrands() {
199
- return $this->compatibleBrands;
200
- } // getCompatibleBrands method
201
-
202
-
203
- /**
204
- * Convert a brand 32bit code to a string
205
- *
206
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
207
- * @param int $brand
208
- * @return string
209
- * @access public
210
- * @static
211
- */
212
- public static function brandToString($brand) {
213
- if (isset(self::$$brandNames[$brand])) {
214
- return self::$$brandNames[$brand];
215
- } else {
216
- return $brand;
217
- }
218
- } // brandToString method
219
-
220
-
221
- /**
222
- * String converter
223
- *
224
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
225
- * @return string
226
- * @access public
227
- */
228
- public function toString() {
229
- return '[MP4Info_Box_ftyp]';
230
- } // toString method
231
- } // MP4Info_Box_ftyp method
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/lib/MP4Info/Box/hdlr.php DELETED
@@ -1,140 +0,0 @@
1
- <?php
2
- /**
3
- * MP4Info
4
- *
5
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
6
- * @copyright Copyright (c) 2006-2009 Tommy Lacroix
7
- * @license LGPL version 3, http://www.gnu.org/licenses/lgpl.html
8
- * @package php-mp4info
9
- * @link $HeadURL: https://php-mp4info.googlecode.com/svn/trunk/MP4Info/Box/hdlr.php $
10
- */
11
-
12
- // ---
13
-
14
- /**
15
- * 8.9 Handler Reference Box (HDLR)
16
- *
17
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
18
- * @version 1.0.20090601 $Id: hdlr.php 2 2009-06-11 14:12:31Z lacroix.tommy@gmail.com $
19
- * @todo Factor this into a fullbox
20
- */
21
- class MP4Info_Box_hdlr extends MP4Info_Box {
22
- // {{{ Constants
23
- const HANDLER_VIDEO = 'vide';
24
- const HANDLER_SOUND = 'soun';
25
- // }}} Constants
26
-
27
- /**
28
- * Handler type
29
- *
30
- * @var uint32
31
- */
32
- protected $handlerType;
33
-
34
- /**
35
- * Name
36
- *
37
- * @var string
38
- */
39
- protected $name;
40
-
41
- /**
42
- * Timezone
43
- *
44
- * @var int
45
- * @static
46
- */
47
- protected static $timezone = false;
48
-
49
- /**
50
- * Constructor
51
- *
52
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
53
- * @param int $totalSize
54
- * @param int $boxType
55
- * @param file|string $data
56
- * @param MP4Info_Box $parent
57
- * @return MP4Info_Box_hdlr
58
- * @access public
59
- * @throws MP4Info_Exception
60
- */
61
- public function __construct($totalSize, $boxType, $data, $parent) {
62
- if (!self::isCompatible($boxType, $parent)) {
63
- throw new Exception('This box isn\'t "ftyp"');
64
- }
65
-
66
- // Get timezone
67
- if (self::$timezone === false) {
68
- self::$timezone = date('Z');
69
- }
70
-
71
- // Call ancestor
72
- parent::__construct($totalSize,$boxType,'',$parent);
73
-
74
- // Get data
75
- $data = self::getDataFrom3rd($data,$totalSize);
76
-
77
- // Unpack
78
- $ar = unpack('Cversion/C3flags',$data);
79
- if ($ar['version'] == 0) {
80
- // 32 bit
81
- $ar2 = unpack('Nctime/Nmtime/NtimeScale/Nduration',substr($data,4));
82
- $len = 6*4;
83
- } else if ($ar['version'] == 1) {
84
- // 64 bit
85
- $ar2 = unpack('N2ctime/N2mtime/NtimeScale/N2duration',substr($data,4));
86
- $len = 9*4;
87
- } else {
88
- throw new Exception('Unhandled version: '.$ar['version']);
89
- }
90
-
91
- // Save
92
- $this->version = $ar['version'];
93
- $this->flags = $ar['flags1']*65536+$ar['flags1']*256+$ar['flags1']*1;
94
- $this->ctime = date('r',(isset($ar2['ctime']) ? $ar2['ctime'] : $ar2['ctime1'])-2082826800-self::$timezone);
95
- $this->mtime = date('r',(isset($ar2['mtime']) ? $ar2['mtime'] : $ar2['mtime1'])-2082826800-self::$timezone);
96
- $this->timeScale = $ar2['timeScale'];
97
- $this->duration = (isset($ar2['duration']) ? $ar2['duration'] : $ar2['duration1']);
98
- $this->handlerType = substr($data,$len,4);
99
- $this->name = substr($data,$len+8,-1);
100
- } // Constructor
101
-
102
-
103
- /**
104
- * Check if block is compatible with class
105
- *
106
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
107
- * @param int $boxType
108
- * @param MP4Info_Box $parent
109
- * @return bool
110
- * @access public
111
- * @static
112
- */
113
- static public function isCompatible($boxType, $parent) {
114
- return $boxType == 0x68646c72;
115
- } // isCompatible method
116
-
117
-
118
- /**
119
- * Handler type getter
120
- *
121
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
122
- * @return int
123
- * @access public
124
- */
125
- public function getHandlerType() {
126
- return $this->handlerType;
127
- } // getHandlerType method
128
-
129
-
130
- /**
131
- * String converter
132
- *
133
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
134
- * @return string
135
- * @access public
136
- */
137
- public function toString() {
138
- return '[MP4Info_Box_hdlr:'.$this->handlerType.']';
139
- } // toString method
140
- } // MP4Info_Box_hdlr class
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/lib/MP4Info/Box/ilst.php DELETED
@@ -1,122 +0,0 @@
1
- <?php
2
- /**
3
- * MP4Info
4
- *
5
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
6
- * @copyright Copyright (c) 2006-2009 Tommy Lacroix
7
- * @license LGPL version 3, http://www.gnu.org/licenses/lgpl.html
8
- * @package php-mp4info
9
- * @link $HeadURL: https://php-mp4info.googlecode.com/svn/trunk/MP4Info/Box/ilst.php $
10
- */
11
-
12
- // ---
13
-
14
- /**
15
- * x.x ??? (ILST)
16
- *
17
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
18
- * @version 1.0.20090601 $Id: ilst.php 2 2009-06-11 14:12:31Z lacroix.tommy@gmail.com $
19
- */
20
- class MP4Info_Box_ilst extends MP4Info_Box_Container {
21
- /**
22
- * Values
23
- *
24
- * @var {}
25
- * @access protected
26
- */
27
- protected $values = array();
28
-
29
-
30
- /**
31
- * Constructor
32
- *
33
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
34
- * @param int $totalSize
35
- * @param int $boxType
36
- * @param file|string $data
37
- * @param MP4Info_Box $parent
38
- * @return MP4Info_Box_ilst
39
- * @access public
40
- * @throws MP4Info_Exception
41
- */
42
- public function __construct($totalSize, $boxType, $data, $parent) {
43
- if (!self::isCompatible($boxType, $parent)) {
44
- throw new MP4Info_Exception('This box isn\'t "ilst"', MP4Info_Exception::CODE_INCOMPATIBLE, $boxType);
45
- }
46
-
47
- // Call ancestor
48
- parent::__construct($totalSize, $boxType, $data, $parent);
49
- } // Constructor
50
-
51
-
52
- /**
53
- * Check if block is compatible with class
54
- *
55
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
56
- * @param int $boxType
57
- * @param MP4Info_Box $parent
58
- * @return bool
59
- * @access public
60
- * @static
61
- */
62
- static public function isCompatible($boxType, $parent) {
63
- return $boxType == 0x696C7374;
64
- } // isCompatible method
65
-
66
-
67
- /**
68
- * Check if a given key has a value
69
- *
70
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
71
- * @param string $k
72
- * @return bool
73
- * @access public
74
- */
75
- public function hasValue($k) {
76
- return (isset($this->values[$k])) || (isset($this->values[utf8_decode($k)]));
77
- } // hasValue method
78
-
79
-
80
- /**
81
- * Get the value of a given key
82
- *
83
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
84
- * @param string $k
85
- * @return mixed
86
- * @access public
87
- */
88
- public function getValue($k) {
89
- if (isset($this->values[$k])) {
90
- return $this->values[$k];
91
- } else if (isset($this->values[utf8_decode($k)])) {
92
- return $this->values[utf8_decode($k)];
93
- } else {
94
- return false;
95
- }
96
- } // getValue method
97
-
98
-
99
- /**
100
- * Set a value for a given key
101
- *
102
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
103
- * @param string $k
104
- * @param mixed $v
105
- * @access public
106
- */
107
- public function setKeyValue($k,$v) {
108
- $this->values[$k] = $v;
109
- } // setKeyValue method
110
-
111
-
112
- /**
113
- * String converter
114
- *
115
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
116
- * @return string
117
- * @access public
118
- */
119
- public function toString() {
120
- return '[MP4Info_Box_ilst:'.count($this->boxes).']';
121
- } // toString method
122
- } // MP4Info_Box_ilst class
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/lib/MP4Info/Box/ilst_sub.php DELETED
@@ -1,148 +0,0 @@
1
- <?php
2
- /**
3
- * MP4Info
4
- *
5
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
6
- * @copyright Copyright (c) 2006-2009 Tommy Lacroix
7
- * @license LGPL version 3, http://www.gnu.org/licenses/lgpl.html
8
- * @package php-mp4info
9
- * @link $HeadURL: https://php-mp4info.googlecode.com/svn/trunk/MP4Info/Box/ilst_sub.php $
10
- */
11
-
12
- // ---
13
-
14
- /**
15
- * x.x ILST sub blocks (numerous)
16
- *
17
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
18
- * @version 1.1.20090611 $Id: ilst_sub.php 2 2009-06-11 14:12:31Z lacroix.tommy@gmail.com $
19
- */
20
- class MP4Info_Box_ilst_sub extends MP4Info_Box {
21
- /**
22
- * Constructor
23
- *
24
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
25
- * @param int $totalSize
26
- * @param int $boxType
27
- * @param file|string $f
28
- * @param MP4Info_Box $parent
29
- * @return MP4Info_Box_ilst_sub
30
- * @access public
31
- * @throws MP4Info_Exception
32
- */
33
- public function __construct($totalSize, $boxType, $data, $parent) {
34
- if (!$parent instanceof MP4Info_Box_ilst) {
35
- throw new MP4Info_Exception('This box isn\'t "islt" child', MP4Info_Exception::CODE_INCOMPATIBLE, $boxType);
36
- }
37
-
38
- // Call ancestor
39
- parent::__construct($totalSize,$boxType,$data,$parent);
40
-
41
- // Get data
42
- $data = $this->data;
43
-
44
- // Unpack
45
- $type = self::getType($this->boxType);
46
- $ar = unpack('Nlen',$data);
47
- if (substr($data,4,4) == 'data') {
48
- $info = substr($data,8,$ar['len']-8);
49
- switch ($type) {
50
- case 'uint8':
51
- $info = reset(unpack('C',$info));
52
- break;
53
- case 'uint16':
54
- $info = reset(unpack('n',$info));
55
- break;
56
- case 'uint32':
57
- $info = reset(unpack('N',$info));
58
- break;
59
- case 'text':
60
- break;
61
- }
62
- $this->data = $info;
63
- $parent->setKeyValue($this->boxTypeStr, $info);
64
- } else {
65
- throw new MP4Info_Exception('Didn\'t get the "data" code');
66
- }
67
- } // Constructor
68
-
69
-
70
- /**
71
- * Check if block is compatible with class
72
- *
73
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
74
- * @param int $boxType
75
- * @param MP4Info_Box $parent
76
- * @return bool
77
- * @access public
78
- * @static
79
- */
80
- static public function isCompatible($boxType, $parent) {
81
- return ($parent instanceof MP4Info_Box_ilst);
82
- } // isCompatible method
83
-
84
-
85
- /**
86
- * Get sub type
87
- * http://atomicparsley.sourceforge.net/mpeg-4files.html
88
- *
89
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
90
- * @param int $boxType
91
- * @return string
92
- * @access protected
93
- * @static
94
- * @todo The © codes should be chr(...), as utf8 encoding messes things up
95
- */
96
- static protected function getType($boxType) {
97
- switch (pack('N',$boxType)) {
98
- case '©alb':
99
- case '©art':
100
- case 'aART':
101
- case '©cmt':
102
- case '©day':
103
- case '©nam':
104
- case '©gen':
105
- case '©wrt':
106
- case '©too':
107
- case 'cprt':
108
- case '©grp':
109
- case 'catg':
110
- case 'desc':
111
- case '©lyr':
112
- case 'tvnn':
113
- case 'tvsh':
114
- case 'tven':
115
- case 'purd':
116
- return 'text';
117
-
118
- case 'gnre':
119
- case 'trkn':
120
- case 'disk':
121
- case 'tmpo':
122
- case 'cpil':
123
- case 'rtng':
124
- case 'stik':
125
- case 'pcst':
126
- case 'purl':
127
- case 'egid':
128
- case 'tvsn':
129
- case 'tves':
130
- case 'pgap':
131
- return 'uint8';
132
- default:
133
- return '';
134
- }
135
- } // getType method
136
-
137
-
138
- /**
139
- * String converter
140
- *
141
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
142
- * @return string
143
- * @access public
144
- */
145
- public function toString() {
146
- return '[MP4Info_Box_ilst_sub['.$this->boxTypeStr.']:'.$this->getData().']';
147
- } // toString converter
148
- } // MP4Info_Box_ilst_sub class
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/lib/MP4Info/Box/mdhd.php DELETED
@@ -1,146 +0,0 @@
1
- <?php
2
- /**
3
- * MP4Info
4
- *
5
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
6
- * @copyright Copyright (c) 2006-2009 Tommy Lacroix
7
- * @license LGPL version 3, http://www.gnu.org/licenses/lgpl.html
8
- * @package php-mp4info
9
- * @link $HeadURL: https://php-mp4info.googlecode.com/svn/trunk/MP4Info/Box/mdhd.php $
10
- */
11
-
12
- // ---
13
-
14
- /**
15
- * 8.8 Media Header Box (MDHD)
16
- *
17
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
18
- * @version 1.0.20090611 $Id: mdhd.php 2 2009-06-11 14:12:31Z lacroix.tommy@gmail.com $
19
- * @todo Factor this into a fullbox
20
- */
21
- class MP4Info_Box_mdhd extends MP4Info_Box {
22
- /**
23
- * Version
24
- *
25
- * @var int
26
- */
27
- protected $version;
28
-
29
- /**
30
- * Flags
31
- *
32
- * @var int
33
- */
34
- protected $flags;
35
-
36
- /**
37
- * Creation time
38
- *
39
- * @var string
40
- */
41
- protected $ctime;
42
-
43
- /**
44
- * Modification time
45
- *
46
- * @var unknown_type
47
- */
48
- protected $mtime;
49
-
50
- /**
51
- * Time scale
52
- *
53
- * @var int
54
- */
55
- protected $timeScale;
56
-
57
- /**
58
- * Duration
59
- *
60
- * @var int
61
- */
62
- protected $duration;
63
-
64
- /**
65
- * Time zone
66
- *
67
- * @var int
68
- * @static
69
- */
70
- protected static $timezone = false;
71
-
72
-
73
- /**
74
- * Constructor
75
- *
76
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
77
- * @param int $totalSize
78
- * @param int $boxType
79
- * @param file|string $data
80
- * @param MP4Info_Box $parent
81
- * @return MP4Info_Box_mdhd
82
- * @access public
83
- * @throws MP4Info_Exception
84
- */
85
- public function __construct($totalSize, $boxType, $data, $parent) {
86
- if (!self::isCompatible($boxType, $parent)) {
87
- throw new Exception('This box isn\'t "mdhd"');
88
- }
89
-
90
- // Get timezone
91
- if (self::$timezone === false) {
92
- self::$timezone = date('Z');
93
- }
94
-
95
- // Call ancestor
96
- parent::__construct($totalSize, $boxType, '', $parent);
97
-
98
- // Unpack
99
- $ar = unpack('Cversion/C3flags',$data);
100
- if ($ar['version'] == 0) {
101
- // 32 bit
102
- $ar2 = unpack('Nctime/Nmtime/NtimeScale/Nduration/nlanguage/ndummy',substr($data,4));
103
- } else if ($ar['version'] == 1) {
104
- // 64 bit
105
- $ar2 = unpack('N2ctime/N2mtime/NtimeScale/N2duration/nlanguage/ndummy',substr($data,4));
106
- } else {
107
- throw new Exception('Unhandled version: '.$ar['version']);
108
- }
109
-
110
- // Save
111
- $this->version = $ar['version'];
112
- $this->flags = $ar['flags1']*65536+$ar['flags1']*256+$ar['flags1']*1;
113
- $this->ctime = date('r',(isset($ar2['ctime']) ? $ar2['ctime'] : $ar2['ctime1'])-2082826800-self::$timezone);
114
- $this->mtime = date('r',(isset($ar2['mtime']) ? $ar2['mtime'] : $ar2['mtime1'])-2082826800-self::$timezone);
115
- $this->timeScale = $ar2['timeScale'];
116
- $this->duration = (isset($ar2['duration']) ? $ar2['duration'] : $ar2['duration1']);
117
- $this->language = MP4Info_Helper::fromPackedLetters($ar2['language'],1);
118
- } // Constructor
119
-
120
-
121
- /**
122
- * Check if block is compatible with class
123
- *
124
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
125
- * @param int $boxType
126
- * @param MP4Info_Box $parent
127
- * @return bool
128
- * @access public
129
- * @static
130
- */
131
- static function isCompatible($boxType, $parent) {
132
- return $boxType == 0x6d646864;
133
- } // isCompatible method
134
-
135
-
136
- /**
137
- * String converter
138
- *
139
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
140
- * @return string
141
- * @access public
142
- */
143
- public function toString() {
144
- return '[MP4Info_Box_mdhd]';
145
- } // toString method
146
- } // MP4Info_Box_mdhd class
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/lib/MP4Info/Box/meta.php DELETED
@@ -1,69 +0,0 @@
1
- <?php
2
- /**
3
- * MP4Info
4
- *
5
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
6
- * @copyright Copyright (c) 2006-2009 Tommy Lacroix
7
- * @license LGPL version 3, http://www.gnu.org/licenses/lgpl.html
8
- * @package php-mp4info
9
- * @link $HeadURL: https://php-mp4info.googlecode.com/svn/trunk/MP4Info/Box/meta.php $
10
- */
11
-
12
- // ---
13
-
14
- /**
15
- * x.x Meta (META)
16
- *
17
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
18
- * @version 1.0.20090601 $Id: meta.php 2 2009-06-11 14:12:31Z lacroix.tommy@gmail.com $
19
- */
20
- class MP4Info_Box_meta extends MP4Info_Box_container {
21
-
22
- /**
23
- * Constructor
24
- *
25
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
26
- * @param int $totalSize
27
- * @param int $boxType
28
- * @param file|string $f
29
- * @param MP4Info_Box_meta $parent
30
- * @access public
31
- * @throws MP4Info_Exception
32
- */
33
- public function __construct($totalSize, $boxType, $data, $parent=false) {
34
- if (!self::isCompatible($boxType, $parent)) {
35
- throw new MP4Info_Exception('This box isn\'t "meta"',MP4Info_Exception::CODE_INCOMPATIBLE,false,$boxType);
36
- }
37
-
38
- $ar = unpack('Nlen',$data);
39
-
40
- parent::__construct($totalSize, $boxType, substr($data,4,$ar['len']), $parent);
41
- } // Constructor
42
-
43
-
44
- /**
45
- * Check if block is compatible with class
46
- *
47
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
48
- * @param int $boxType
49
- * @param MP4Info_Box $parent
50
- * @return bool
51
- * @access public
52
- * @static
53
- */
54
- static public function isCompatible($boxType, $parent) {
55
- return $boxType == 0x6D657461;
56
- } // isCompatible method
57
-
58
-
59
- /**
60
- * String converter
61
- *
62
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
63
- * @return string
64
- * @access public
65
- */
66
- public function toString() {
67
- return '[MP4Info_Box_meta:'.count($this->boxes).']';
68
- } // toString method
69
- } // MP4Info_Box_meta class
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/lib/MP4Info/Box/mvhd.php DELETED
@@ -1,233 +0,0 @@
1
- <?php
2
- /**
3
- * MP4Info
4
- *
5
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
6
- * @copyright Copyright (c) 2006-2009 Tommy Lacroix
7
- * @license LGPL version 3, http://www.gnu.org/licenses/lgpl.html
8
- * @package php-mp4info
9
- * @link $HeadURL: https://php-mp4info.googlecode.com/svn/trunk/MP4Info/Box/mvhd.php $
10
- */
11
-
12
- // ---
13
-
14
- /**
15
- * x.x Movie Header (MVHD)
16
- *
17
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
18
- * @version 1.0.20090601 $Id: mvhd.php 2 2009-06-11 14:12:31Z lacroix.tommy@gmail.com $
19
- * @todo Factor this into a fullbox
20
- */
21
- class MP4Info_Box_mvhd extends MP4Info_Box {
22
- /**
23
- * Version
24
- *
25
- * @var int
26
- */
27
- protected $version;
28
-
29
- /**
30
- * Flags
31
- *
32
- * @var int
33
- */
34
- protected $flags;
35
-
36
- /**
37
- * Creation time
38
- *
39
- * @var string
40
- */
41
- protected $ctime;
42
-
43
- /**
44
- * Modification time
45
- *
46
- * @var unknown_type
47
- */
48
- protected $mtime;
49
-
50
- /**
51
- * Time scale
52
- *
53
- * @var int
54
- */
55
- protected $timeScale;
56
-
57
- /**
58
- * Duration
59
- *
60
- * @var int
61
- */
62
- protected $duration;
63
-
64
- /**
65
- * Rate
66
- *
67
- * @var int
68
- */
69
- protected $rate;
70
-
71
- /**
72
- * Volume
73
- *
74
- * @var int
75
- */
76
- protected $volume;
77
-
78
- /**
79
- * Time zone
80
- *
81
- * @var int
82
- * @static
83
- */
84
- protected static $timezone = false;
85
-
86
-
87
- /**
88
- * Constructor
89
- *
90
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
91
- * @param int $totalSize
92
- * @param int $boxType
93
- * @param file|string $data
94
- * @param MP4Info_Box $parent
95
- * @return MP4Info_Box_mvhd
96
- * @access public
97
- * @throws MP4Info_Exception
98
- */
99
- public function __construct($totalSize, $boxType, $data, $parent) {
100
- if (!self::isCompatible($boxType)) {
101
- throw new Exception('This box isn\'t "mvhd"');
102
- }
103
-
104
- // Get timezone
105
- if (self::$timezone === false) {
106
- self::$timezone = date('Z');
107
- }
108
-
109
- // Call ancestor's constructor
110
- parent::__construct($totalSize,$boxType,'',$parent);
111
-
112
- // Unpack
113
- $ar = unpack('Cversion/C3flags',$data);
114
- if ($ar['version'] == 0) {
115
- // 32 bit
116
- $ar2 = unpack('Nctime/Nmtime/NtimeScale/Nduration/Nrate/nvolume/ndummy/N2dummy2/N9matrix/N3dummy3/NnextTrack',substr($data,4));
117
- } else if ($ar['version'] == 1) {
118
- // 64 bit
119
- $ar2 = unpack('N2ctime/N2mtime/NtimeScale/N2duration/Nrate/nvolume/ndummy/N2dummy2/N9matrix/N3dummy3/NnextTrack',substr($data,4));
120
- } else {
121
- throw new Exception('Unhandled version: '.$ar['version']);
122
- }
123
-
124
- // Save
125
- $this->version = $ar['version'];
126
- $this->flags = $ar['flags1']*65536+$ar['flags1']*256+$ar['flags1']*1;
127
- $this->ctime = date('r',(isset($ar2['ctime']) ? $ar2['ctime'] : $ar2['ctime1'])-2082826800-self::$timezone);
128
- $this->mtime = date('r',(isset($ar2['mtime']) ? $ar2['mtime'] : $ar2['mtime1'])-2082826800-self::$timezone);
129
- $this->timeScale = $ar2['timeScale'];
130
- $this->duration = (isset($ar2['duration']) ? $ar2['duration'] : $ar2['duration1']);
131
- $this->rate = MP4Info_Helper::fromFixed16($ar2['rate']);
132
- $this->volume = MP4Info_Helper::fromFixed8($ar2['volume']);
133
- } // Constructor
134
-
135
-
136
- /**
137
- * Check if block is compatible with class
138
- *
139
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
140
- * @param int $boxType
141
- * @param MP4Info_Box $parent
142
- * @return bool
143
- * @access public
144
- * @static
145
- */
146
- static public function isCompatible($boxType) {
147
- return $boxType == 0x6D766864;
148
- } // isCompatible method
149
-
150
-
151
- /**
152
- * Creation time getter
153
- *
154
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
155
- * @return int
156
- * @access public
157
- */
158
- public function getCreationTime() {
159
- return $this->ctime;
160
- } // getCreationTime method
161
-
162
-
163
- /**
164
- * Time scale getter
165
- *
166
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
167
- * @return int
168
- * @access public
169
- */
170
- public function getTimeScale() {
171
- return $this->timeScale;
172
- } // getTimeScale method
173
-
174
-
175
- /**
176
- * Duration getter
177
- *
178
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
179
- * @return int
180
- * @access public
181
- */
182
- public function getDuration() {
183
- return $this->duration;
184
- } // getDuration method
185
-
186
-
187
- /**
188
- * Real duration getter
189
- *
190
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
191
- * @return float
192
- * @access public
193
- */
194
- public function getRealDuration() {
195
- return $this->duration/$this->timeScale;
196
- } // getRealDuration method
197
-
198
-
199
- /**
200
- * Rate getter
201
- *
202
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
203
- * @return int
204
- * @access public
205
- */
206
- public function getRate() {
207
- return $this->rate();
208
- } // getRate method
209
-
210
-
211
- /**
212
- * Volume getter
213
- *
214
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
215
- * @return int
216
- * @access public
217
- */
218
- public function getVolume() {
219
- return $this->volume();
220
- } // getVolume method
221
-
222
-
223
- /**
224
- * String converter
225
- *
226
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
227
- * @return string
228
- * @access public
229
- */
230
- public function toString() {
231
- return '[MP4Info_Box_mvhd]';
232
- } // toString method
233
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/lib/MP4Info/Box/stsd.php DELETED
@@ -1,139 +0,0 @@
1
- <?php
2
- /**
3
- * MP4Info
4
- *
5
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
6
- * @copyright Copyright (c) 2006-2009 Tommy Lacroix
7
- * @license LGPL version 3, http://www.gnu.org/licenses/lgpl.html
8
- * @package php-mp4info
9
- * @link $HeadURL: https://php-mp4info.googlecode.com/svn/trunk/MP4Info/Box/stsd.php $
10
- */
11
-
12
- // ---
13
-
14
- /**
15
- * x.x ??? (STSD)
16
- *
17
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
18
- * @version 1.0.20090601 $Id: stsd.php 2 2009-06-11 14:12:31Z lacroix.tommy@gmail.com $
19
- * @todo Factor this into a fullbox
20
- */
21
- class MP4Info_Box_stsd extends MP4Info_Box {
22
- /**
23
- * Version
24
- *
25
- * @var int
26
- */
27
- protected $version;
28
-
29
- /**
30
- * Flags
31
- *
32
- * @var int
33
- */
34
- protected $flags;
35
-
36
- /**
37
- * Count
38
- *
39
- * @var int
40
- */
41
- protected $count;
42
-
43
- /**
44
- * Values
45
- *
46
- * @var string{}
47
- */
48
- protected $values = array();
49
-
50
-
51
- /**
52
- * Constructor
53
- *
54
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
55
- * @param int $totalSize
56
- * @param int $boxType
57
- * @param file|string $data
58
- * @param MP4Info_Box $parent
59
- * @return MP4Info_Box_stsd
60
- * @access public
61
- * @throws MP4Info_Exception
62
- */
63
- public function __construct($totalSize, $boxType, $data, $parent) {
64
- if (!self::isCompatible($boxType, $parent)) {
65
- throw new Exception('This box isn\'t "stsd"');
66
- }
67
-
68
- // Call ancestor
69
- parent::__construct($totalSize,$boxType,'',$parent);
70
-
71
- // Get data
72
- $data = self::getDataFrom3rd($data, $totalSize);
73
-
74
- // Unpack
75
- $ar = unpack('Cversion/C3flags/Ncount',$data);
76
- $this->version = $ar['version'];
77
- $this->flags = $ar['flags1']*65536+$ar['flags1']*256+$ar['flags1']*1;
78
- $this->count = $ar['count'];
79
-
80
- // Unpack SAMPLEDESCRIPTION
81
- $desc = substr($data,8);
82
- for ($i=0;$i<$this->count;$i++) {
83
- $ar = unpack('Nlen',$desc);
84
- $type = substr($desc,4,4);
85
- $info = substr($desc,8,$ar['len']-8);
86
- $desc = substr($desc,$ar['len']);
87
- $this->values[$type] = $info;
88
- }
89
- } // Constructor
90
-
91
-
92
- /**
93
- * Check if block is compatible with class
94
- *
95
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
96
- * @param int $boxType
97
- * @param MP4Info_Box $parent
98
- * @return bool
99
- * @access public
100
- * @static
101
- */
102
- static public function isCompatible($boxType, $parent) {
103
- return $boxType == 0x73747364;
104
- } // isCompatible method
105
-
106
- /**
107
- * Values getter
108
- *
109
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
110
- * @return string{}
111
- * @access public
112
- */
113
- public function getValues() {
114
- return $this->values;
115
- } // getValues method
116
-
117
- /**
118
- * Value getter
119
- *
120
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
121
- * @param string $key
122
- * @return string
123
- * @access public
124
- */
125
- public function getValue($key) {
126
- return isset($this->values[$key]) ? $this->values[$key] : false;
127
- } // getValue method
128
-
129
- /**
130
- * String converter
131
- *
132
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
133
- * @return string
134
- * @access public
135
- */
136
- public function toString() {
137
- return '[MP4Info_Box_stsd:'.implode(',',array_keys($this->values)).']';
138
- } // toString method
139
- } // MP4Info_Box_stsd class
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/lib/MP4Info/Box/tkhd.php DELETED
@@ -1,215 +0,0 @@
1
- <?php
2
- /**
3
- * MP4Info
4
- *
5
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
6
- * @copyright Copyright (c) 2006-2009 Tommy Lacroix
7
- * @license LGPL version 3, http://www.gnu.org/licenses/lgpl.html
8
- * @package php-mp4info
9
- * @link $HeadURL: https://php-mp4info.googlecode.com/svn/trunk/MP4Info/Box/tkhd.php $
10
- */
11
-
12
- // ---
13
-
14
- /**
15
- * x.x Track Header (TKHD)
16
- *
17
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
18
- * @version 1.1.20090611 $Id: tkhd.php 2 2009-06-11 14:12:31Z lacroix.tommy@gmail.com $
19
- * @todo Factor this into a fullbox
20
- */
21
- class MP4Info_Box_tkhd extends MP4Info_Box {
22
- /**
23
- * Version
24
- *
25
- * @var int
26
- */
27
- protected $version;
28
-
29
- /**
30
- * Flags
31
- *
32
- * @var int
33
- */
34
- protected $flags;
35
-
36
- /**
37
- * Creation time
38
- *
39
- * @var string
40
- */
41
- protected $ctime;
42
-
43
- /**
44
- * Modification time
45
- *
46
- * @var unknown_type
47
- */
48
- protected $mtime;
49
-
50
- /**
51
- * Time scale
52
- *
53
- * @var int
54
- */
55
- protected $timeScale;
56
-
57
- /**
58
- * Duration
59
- *
60
- * @var int
61
- */
62
- protected $duration;
63
-
64
- /**
65
- * Layer
66
- *
67
- * @var int
68
- */
69
- protected $layer;
70
-
71
- /**
72
- * Volume
73
- *
74
- * @var float
75
- */
76
- protected $volume;
77
-
78
- /**
79
- * Width
80
- *
81
- * @var float
82
- */
83
- protected $width;
84
-
85
- /**
86
- * Height
87
- *
88
- * @var float
89
- */
90
- protected $height;
91
-
92
- /**
93
- * Time zone
94
- *
95
- * @var int
96
- * @static
97
- */
98
- protected static $timezone = false;
99
-
100
- /**
101
- * Constructor
102
- *
103
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
104
- * @param int $totalSize
105
- * @param int $boxType
106
- * @param file|string $data
107
- * @param MP4Info_Box $parent
108
- * @return MP4Info_Box_tkhd
109
- * @access public
110
- * @throws MP4Info_Exception
111
- */
112
- public function __construct($totalSize, $boxType, $data, $parent=false) {
113
- if (!self::isCompatible($boxType,$parent)) {
114
- throw new Exception('This box isn\'t "tkhd"');
115
- }
116
-
117
- // Get timezone
118
- if (self::$timezone === false) {
119
- self::$timezone = date('Z');
120
- }
121
-
122
- // Call ancestor's constructor
123
- parent::__construct($totalSize,$boxType,'',$parent);
124
-
125
- // Get data
126
- $data = self::getDataFrom3rd($data,$totalSize);
127
-
128
- // Unpack
129
- $ar = unpack('Cversion/C3flags',$data);
130
- if ($ar['version'] == 0) {
131
- // 32 bit
132
- $ar2 = unpack('Nctime/Nmtime/NtrackId/Ndummy/Nduration/N2dummy1/nlayer/naltGroup/nvolume/ndummy2/N9matrix/Nwidth/Nheight',substr($data,4));
133
- } else if ($ar['version'] == 1) {
134
- // 64 bit
135
- $ar2 = unpack('N2ctime/N2mtime/NtrackId/Ndummy/N2duration/N2dummy1/nlayer/naltGroup/nvolume/ndummy2/N9matrix/Nwidth/Nheight',substr($data,4));
136
- } else {
137
- throw new Exception('Unhandled version: '.$ar['version']);
138
- }
139
-
140
- // Save
141
- $this->version = $ar['version'];
142
- $this->flags = $ar['flags1']*65536+$ar['flags1']*256+$ar['flags1']*1;
143
- $this->ctime = date('r',(isset($ar2['ctime']) ? $ar2['ctime'] : $ar2['ctime1'])-2082826800-self::$timezone);
144
- $this->mtime = date('r',(isset($ar2['mtime']) ? $ar2['mtime'] : $ar2['mtime1'])-2082826800-self::$timezone);
145
- $this->trackId = $ar2['trackId'];
146
- $this->duration = (isset($ar2['duration']) ? $ar2['duration'] : $ar2['duration1']);
147
- $this->layer = ($ar2['layer']>32767 ? $ar2['layer']-65536 : $ar2['layer']);
148
- $this->volume = MP4Info_Helper::fromFixed8($ar2['volume']);
149
- $this->width = MP4Info_Helper::fromFixed16($ar2['width']);
150
- $this->height = MP4Info_Helper::fromFixed16($ar2['height']);
151
- } // Constructor
152
-
153
-
154
- /**
155
- * Check if block is compatible with class
156
- *
157
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
158
- * @param int $boxType
159
- * @param MP4Info_Box $parent
160
- * @return bool
161
- * @access public
162
- * @static
163
- */
164
- static public function isCompatible($boxType, $parent) {
165
- return $boxType == 0x746b6864;
166
- } // isCompatible method
167
-
168
-
169
- /**
170
- * Width getter
171
- *
172
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
173
- * @return float
174
- * @access public
175
- */
176
- public function getWidth() {
177
- return $this->width;
178
- } // getWidth method
179
-
180
-
181
- /**
182
- * Height getter
183
- *
184
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
185
- * @return float
186
- * @access public
187
- */
188
- public function getHeight() {
189
- return $this->height;
190
- } // getHeight method
191
-
192
-
193
- /**
194
- * Duration getter
195
- *
196
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
197
- * @return int
198
- * @access public
199
- */
200
- public function getDuration() {
201
- return $this->duration;
202
- } // getDuration method
203
-
204
-
205
- /**
206
- * String converter
207
- *
208
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
209
- * @return string
210
- * @access public
211
- */
212
- public function toString() {
213
- return '[MP4Info_Box_tkhd]';
214
- } // toString method
215
- } // MP4Info_Box_tkhd class
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/lib/MP4Info/Box/uuid.php DELETED
@@ -1,117 +0,0 @@
1
- <?php
2
- /**
3
- * MP4Info
4
- *
5
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
6
- * @copyright Copyright (c) 2006-2009 Tommy Lacroix
7
- * @license LGPL version 3, http://www.gnu.org/licenses/lgpl.html
8
- * @package php-mp4info
9
- * @link $HeadURL: https://php-mp4info.googlecode.com/svn/trunk/MP4Info/Box/uuid.php $
10
- */
11
-
12
- // ---
13
-
14
- /**
15
- * x.x ??? (UUID)
16
- *
17
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
18
- * @version 1.0.20090601 $Id: uuid.php 2 2009-06-11 14:12:31Z lacroix.tommy@gmail.com $
19
- * @todo Limited to XMP meta data... extend
20
- */
21
- class MP4Info_Box_uuid extends MP4Info_Box {
22
- // {{{ Constants
23
- const UUID_XMP_METADATA = 'BE7ACFCB97A942';
24
- // }}} Constants
25
-
26
- /**
27
- * UUID
28
- *
29
- * @var string
30
- */
31
- protected $uuid;
32
-
33
- /**
34
- * XMP data
35
- *
36
- * @var string
37
- */
38
- protected $xmp;
39
-
40
-
41
- /**
42
- * Constructor
43
- *
44
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
45
- * @param int $totalSize
46
- * @param int $boxType
47
- * @param file|string $data
48
- * @param MP4Info_Box $parent
49
- * @return MP4Info_Box_uuid
50
- * @access public
51
- * @throws MP4Info_Exception
52
- */
53
- public function __construct($totalSize, $boxType, $data, $parent) {
54
- if (!self::isCompatible($boxType, $parent)) {
55
- throw new Exception('This box isn\'t "uuid"');
56
- }
57
-
58
- // Call ancestor
59
- parent::__construct($totalSize,$boxType,'',$parent);
60
-
61
- // Unpack
62
- $data = self::getDataFrom3rd($data,$totalSize);
63
- $this->uuid = bin2hex(substr($data,0,16));
64
- $this->xmp = substr($data,16);
65
- } // Constructor
66
-
67
-
68
- /**
69
- * Check if block is compatible with class
70
- *
71
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
72
- * @param int $boxType
73
- * @param MP4Info_Box $parent
74
- * @return bool
75
- * @access public
76
- * @static
77
- */
78
- static public function isCompatible($boxType, $parent) {
79
- return $boxType == 0x75756964;
80
- } // isCompatible method
81
-
82
-
83
- /**
84
- * UUID getter
85
- *
86
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
87
- * @return string
88
- * @access public
89
- */
90
- public function getUUID() {
91
- return strtoupper($this->uuid);
92
- } // getUUID method
93
-
94
-
95
- /**
96
- * XMP Meta data getter
97
- *
98
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
99
- * @return string
100
- * @access public
101
- */
102
- public function getXMPMetaData() {
103
- return (substr(strtoupper($this->uuid),0,14) == self::UUID_XMP_METADATA) ? $this->xmp : false;
104
- } // getXMPMetaData method
105
-
106
-
107
- /**
108
- * String converter
109
- *
110
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
111
- * @return string
112
- * @access public
113
- */
114
- public function toString() {
115
- return '[MP4Info_Box_uuid]';
116
- } // toString method
117
- } // MP4Info_Box_uuid class
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/lib/MP4Info/Exception.php DELETED
@@ -1,61 +0,0 @@
1
- <?php
2
- /**
3
- * MP4Info
4
- *
5
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
6
- * @copyright Copyright (c) 2006-2009 Tommy Lacroix
7
- * @license LGPL version 3, http://www.gnu.org/licenses/lgpl.html
8
- * @package php-mp4info
9
- * @link $HeadURL: https://php-mp4info.googlecode.com/svn/trunk/MP4Info/Exception.php $
10
- */
11
-
12
- // ---
13
-
14
- /**
15
- * MP4Info Exception Class
16
- *
17
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
18
- * @version 1.1.20090611 $Id: Exception.php 2 2009-06-11 14:12:31Z lacroix.tommy@gmail.com $
19
- */
20
-
21
- class MP4Info_Exception extends Exception {
22
- // {{{ Constants
23
- const CODE_UNKNOWN = 0x00;
24
- const CODE_INCOMPATIBLE = 0x01;
25
- // }}} Constants
26
-
27
- /**
28
- * Box type
29
- *
30
- * @var int
31
- */
32
- protected $boxType;
33
-
34
-
35
- /**
36
- * Constructor
37
- *
38
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
39
- * @param string $message
40
- * @param int $code
41
- * @param int $boxType
42
- * @return MP4Info_Exception
43
- * @access public
44
- */
45
- public function __construct($message, $code = 0, $boxType=false) {
46
- parent::__construct($message, $code);
47
- $this->boxType = $boxType;
48
- } // Constructor
49
-
50
-
51
- /**
52
- * Box type getter
53
- *
54
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
55
- * @return int
56
- * @access public
57
- */
58
- public function getBoxType() {
59
- return $this->boxType;
60
- } // getBoxType method
61
- } // MP4Info_Exception class
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/lib/MP4Info/Helper.php DELETED
@@ -1,72 +0,0 @@
1
- <?php
2
- /**
3
- * MP4Info
4
- *
5
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
6
- * @copyright Copyright (c) 2006-2009 Tommy Lacroix
7
- * @license LGPL version 3, http://www.gnu.org/licenses/lgpl.html
8
- * @package php-mp4info
9
- * @link $HeadURL: https://php-mp4info.googlecode.com/svn/trunk/MP4Info/Helper.php $
10
- */
11
-
12
- // ---
13
-
14
- /**
15
- * MP4Info helper functions
16
- *
17
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
18
- * @version 1.0.20090601 $Id: Helper.php 2 2009-06-11 14:12:31Z lacroix.tommy@gmail.com $
19
- */
20
- class MP4Info_Helper {
21
- /**
22
- * Convert a short to a float
23
- *
24
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
25
- * @param int $n
26
- * @return float
27
- * @access public
28
- * @static
29
- */
30
- public static function fromFixed8($n) {
31
- return $n / 256;
32
- } // fromFixed8 method
33
-
34
-
35
- /**
36
- * Convert a long to a float
37
- *
38
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
39
- * @param int $n
40
- * @return float
41
- * @access public
42
- * @static
43
- */
44
- public static function fromFixed16($n) {
45
- return $n / 65536;
46
- } // fromFixed16 method
47
-
48
-
49
- /**
50
- * Convert binary packed (5bit) letters to string
51
- *
52
- * @author Tommy Lacroix <lacroix.tommy@gmail.com>
53
- * @param int $n
54
- * @param int $pad
55
- * @return string
56
- * @access public
57
- * @static
58
- */
59
- public static function fromPackedLetters($n, $pad=1) {
60
- $s = decbin($n);
61
- $s .= str_repeat('0',8-(strlen($s)%8));
62
- $s = substr($s,0,-$pad);
63
- $out = '';
64
- while (strlen($s)>=5) {
65
- $letter = substr($s,0,5);
66
- $nl = bindec($letter) + 0x60;
67
- $out .= chr($nl);
68
- $s = substr($s,5);
69
- }
70
- return $out;
71
- } // fromPackedLetters method
72
- } // MP4Info_Helper class
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/lib/getid3/getid3.lib.php ADDED
@@ -0,0 +1,1317 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /////////////////////////////////////////////////////////////////
3
+ /// getID3() by James Heinrich <info@getid3.org> //
4
+ // available at http://getid3.sourceforge.net //
5
+ // or http://www.getid3.org //
6
+ /////////////////////////////////////////////////////////////////
7
+ // //
8
+ // getid3.lib.php - part of getID3() //
9
+ // See readme.txt for more details //
10
+ // ///
11
+ /////////////////////////////////////////////////////////////////
12
+
13
+
14
+ class getid3_lib
15
+ {
16
+
17
+ static function PrintHexBytes($string, $hex=true, $spaces=true, $htmlencoding='UTF-8') {
18
+ $returnstring = '';
19
+ for ($i = 0; $i < strlen($string); $i++) {
20
+ if ($hex) {
21
+ $returnstring .= str_pad(dechex(ord($string{$i})), 2, '0', STR_PAD_LEFT);
22
+ } else {
23
+ $returnstring .= ' '.(preg_match("#[\x20-\x7E]#", $string{$i}) ? $string{$i} : '�');
24
+ }
25
+ if ($spaces) {
26
+ $returnstring .= ' ';
27
+ }
28
+ }
29
+ if (!empty($htmlencoding)) {
30
+ if ($htmlencoding === true) {
31
+ $htmlencoding = 'UTF-8'; // prior to getID3 v1.9.0 the function's 4th parameter was boolean
32
+ }
33
+ $returnstring = htmlentities($returnstring, ENT_QUOTES, $htmlencoding);
34
+ }
35
+ return $returnstring;
36
+ }
37
+
38
+ static function trunc($floatnumber) {
39
+ // truncates a floating-point number at the decimal point
40
+ // returns int (if possible, otherwise float)
41
+ if ($floatnumber >= 1) {
42
+ $truncatednumber = floor($floatnumber);
43
+ } elseif ($floatnumber <= -1) {
44
+ $truncatednumber = ceil($floatnumber);
45
+ } else {
46
+ $truncatednumber = 0;
47
+ }
48
+ if (getid3_lib::intValueSupported($truncatednumber)) {
49
+ $truncatednumber = (int) $truncatednumber;
50
+ }
51
+ return $truncatednumber;
52
+ }
53
+
54
+
55
+ static function safe_inc(&$variable, $increment=1) {
56
+ if (isset($variable)) {
57
+ $variable += $increment;
58
+ } else {
59
+ $variable = $increment;
60
+ }
61
+ return true;
62
+ }
63
+
64
+ static function CastAsInt($floatnum) {
65
+ // convert to float if not already
66
+ $floatnum = (float) $floatnum;
67
+
68
+ // convert a float to type int, only if possible
69
+ if (getid3_lib::trunc($floatnum) == $floatnum) {
70
+ // it's not floating point
71
+ if (getid3_lib::intValueSupported($floatnum)) {
72
+ // it's within int range
73
+ $floatnum = (int) $floatnum;
74
+ }
75
+ }
76
+ return $floatnum;
77
+ }
78
+
79
+ public static function intValueSupported($num) {
80
+ // check if integers are 64-bit
81
+ static $hasINT64 = null;
82
+ if ($hasINT64 === null) { // 10x faster than is_null()
83
+ $hasINT64 = is_int(pow(2, 31)); // 32-bit int are limited to (2^31)-1
84
+ if (!$hasINT64 && !defined('PHP_INT_MIN')) {
85
+ define('PHP_INT_MIN', ~PHP_INT_MAX);
86
+ }
87
+ }
88
+ // if integers are 64-bit - no other check required
89
+ if ($hasINT64 || (($num <= PHP_INT_MAX) && ($num >= PHP_INT_MIN))) {
90
+ return true;
91
+ }
92
+ return false;
93
+ }
94
+
95
+ static function DecimalizeFraction($fraction) {
96
+ list($numerator, $denominator) = explode('/', $fraction);
97
+ return $numerator / ($denominator ? $denominator : 1);
98
+ }
99
+
100
+
101
+ static function DecimalBinary2Float($binarynumerator) {
102
+ $numerator = getid3_lib::Bin2Dec($binarynumerator);
103
+ $denominator = getid3_lib::Bin2Dec('1'.str_repeat('0', strlen($binarynumerator)));
104
+ return ($numerator / $denominator);
105
+ }
106
+
107
+
108
+ static function NormalizeBinaryPoint($binarypointnumber, $maxbits=52) {
109
+ // http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/binary.html
110
+ if (strpos($binarypointnumber, '.') === false) {
111
+ $binarypointnumber = '0.'.$binarypointnumber;
112
+ } elseif ($binarypointnumber{0} == '.') {
113
+ $binarypointnumber = '0'.$binarypointnumber;
114
+ }
115
+ $exponent = 0;
116
+ while (($binarypointnumber{0} != '1') || (substr($binarypointnumber, 1, 1) != '.')) {
117
+ if (substr($binarypointnumber, 1, 1) == '.') {
118
+ $exponent--;
119
+ $binarypointnumber = substr($binarypointnumber, 2, 1).'.'.substr($binarypointnumber, 3);
120
+ } else {
121
+ $pointpos = strpos($binarypointnumber, '.');
122
+ $exponent += ($pointpos - 1);
123
+ $binarypointnumber = str_replace('.', '', $binarypointnumber);
124
+ $binarypointnumber = $binarypointnumber{0}.'.'.substr($binarypointnumber, 1);
125
+ }
126
+ }
127
+ $binarypointnumber = str_pad(substr($binarypointnumber, 0, $maxbits + 2), $maxbits + 2, '0', STR_PAD_RIGHT);
128
+ return array('normalized'=>$binarypointnumber, 'exponent'=>(int) $exponent);
129
+ }
130
+
131
+
132
+ static function Float2BinaryDecimal($floatvalue) {
133
+ // http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/binary.html
134
+ $maxbits = 128; // to how many bits of precision should the calculations be taken?
135
+ $intpart = getid3_lib::trunc($floatvalue);
136
+ $floatpart = abs($floatvalue - $intpart);
137
+ $pointbitstring = '';
138
+ while (($floatpart != 0) && (strlen($pointbitstring) < $maxbits)) {
139
+ $floatpart *= 2;
140
+ $pointbitstring .= (string) getid3_lib::trunc($floatpart);
141
+ $floatpart -= getid3_lib::trunc($floatpart);
142
+ }
143
+ $binarypointnumber = decbin($intpart).'.'.$pointbitstring;
144
+ return $binarypointnumber;
145
+ }
146
+
147
+
148
+ static function Float2String($floatvalue, $bits) {
149
+ // http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/ieee-expl.html
150
+ switch ($bits) {
151
+ case 32:
152
+ $exponentbits = 8;
153
+ $fractionbits = 23;
154
+ break;
155
+
156
+ case 64:
157
+ $exponentbits = 11;
158
+ $fractionbits = 52;
159
+ break;
160
+
161
+ default:
162
+ return false;
163
+ break;
164
+ }
165
+ if ($floatvalue >= 0) {
166
+ $signbit = '0';
167
+ } else {
168
+ $signbit = '1';
169
+ }
170
+ $normalizedbinary = getid3_lib::NormalizeBinaryPoint(getid3_lib::Float2BinaryDecimal($floatvalue), $fractionbits);
171
+ $biasedexponent = pow(2, $exponentbits - 1) - 1 + $normalizedbinary['exponent']; // (127 or 1023) +/- exponent
172
+ $exponentbitstring = str_pad(decbin($biasedexponent), $exponentbits, '0', STR_PAD_LEFT);
173
+ $fractionbitstring = str_pad(substr($normalizedbinary['normalized'], 2), $fractionbits, '0', STR_PAD_RIGHT);
174
+
175
+ return getid3_lib::BigEndian2String(getid3_lib::Bin2Dec($signbit.$exponentbitstring.$fractionbitstring), $bits % 8, false);
176
+ }
177
+
178
+
179
+ static function LittleEndian2Float($byteword) {
180
+ return getid3_lib::BigEndian2Float(strrev($byteword));
181
+ }
182
+
183
+
184
+ static function BigEndian2Float($byteword) {
185
+ // ANSI/IEEE Standard 754-1985, Standard for Binary Floating Point Arithmetic
186
+ // http://www.psc.edu/general/software/packages/ieee/ieee.html
187
+ // http://www.scri.fsu.edu/~jac/MAD3401/Backgrnd/ieee.html
188
+
189
+ $bitword = getid3_lib::BigEndian2Bin($byteword);
190
+ if (!$bitword) {
191
+ return 0;
192
+ }
193
+ $signbit = $bitword{0};
194
+
195
+ switch (strlen($byteword) * 8) {
196
+ case 32:
197
+ $exponentbits = 8;
198
+ $fractionbits = 23;
199
+ break;
200
+
201
+ case 64:
202
+ $exponentbits = 11;
203
+ $fractionbits = 52;
204
+ break;
205
+
206
+ case 80:
207
+ // 80-bit Apple SANE format
208
+ // http://www.mactech.com/articles/mactech/Vol.06/06.01/SANENormalized/
209
+ $exponentstring = substr($bitword, 1, 15);
210
+ $isnormalized = intval($bitword{16});
211
+ $fractionstring = substr($bitword, 17, 63);
212
+ $exponent = pow(2, getid3_lib::Bin2Dec($exponentstring) - 16383);
213
+ $fraction = $isnormalized + getid3_lib::DecimalBinary2Float($fractionstring);
214
+ $floatvalue = $exponent * $fraction;
215
+ if ($signbit == '1') {
216
+ $floatvalue *= -1;
217
+ }
218
+ return $floatvalue;
219
+ break;
220
+
221
+ default:
222
+ return false;
223
+ break;
224
+ }
225
+ $exponentstring = substr($bitword, 1, $exponentbits);
226
+ $fractionstring = substr($bitword, $exponentbits + 1, $fractionbits);
227
+ $exponent = getid3_lib::Bin2Dec($exponentstring);
228
+ $fraction = getid3_lib::Bin2Dec($fractionstring);
229
+
230
+ if (($exponent == (pow(2, $exponentbits) - 1)) && ($fraction != 0)) {
231
+ // Not a Number
232
+ $floatvalue = false;
233
+ } elseif (($exponent == (pow(2, $exponentbits) - 1)) && ($fraction == 0)) {
234
+ if ($signbit == '1') {
235
+ $floatvalue = '-infinity';
236
+ } else {
237
+ $floatvalue = '+infinity';
238
+ }
239
+ } elseif (($exponent == 0) && ($fraction == 0)) {
240
+ if ($signbit == '1') {
241
+ $floatvalue = -0;
242
+ } else {
243
+ $floatvalue = 0;
244
+ }
245
+ $floatvalue = ($signbit ? 0 : -0);
246
+ } elseif (($exponent == 0) && ($fraction != 0)) {
247
+ // These are 'unnormalized' values
248
+ $floatvalue = pow(2, (-1 * (pow(2, $exponentbits - 1) - 2))) * getid3_lib::DecimalBinary2Float($fractionstring);
249
+ if ($signbit == '1') {
250
+ $floatvalue *= -1;
251
+ }
252
+ } elseif ($exponent != 0) {
253
+ $floatvalue = pow(2, ($exponent - (pow(2, $exponentbits - 1) - 1))) * (1 + getid3_lib::DecimalBinary2Float($fractionstring));
254
+ if ($signbit == '1') {
255
+ $floatvalue *= -1;
256
+ }
257
+ }
258
+ return (float) $floatvalue;
259
+ }
260
+
261
+
262
+ static function BigEndian2Int($byteword, $synchsafe=false, $signed=false) {
263
+ $intvalue = 0;
264
+ $bytewordlen = strlen($byteword);
265
+ if ($bytewordlen == 0) {
266
+ return false;
267
+ }
268
+ for ($i = 0; $i < $bytewordlen; $i++) {
269
+ if ($synchsafe) { // disregard MSB, effectively 7-bit bytes
270
+ //$intvalue = $intvalue | (ord($byteword{$i}) & 0x7F) << (($bytewordlen - 1 - $i) * 7); // faster, but runs into problems past 2^31 on 32-bit systems
271
+ $intvalue += (ord($byteword{$i}) & 0x7F) * pow(2, ($bytewordlen - 1 - $i) * 7);
272
+ } else {
273
+ $intvalue += ord($byteword{$i}) * pow(256, ($bytewordlen - 1 - $i));
274
+ }
275
+ }
276
+ if ($signed && !$synchsafe) {
277
+ // synchsafe ints are not allowed to be signed
278
+ if ($bytewordlen <= PHP_INT_SIZE) {
279
+ $signMaskBit = 0x80 << (8 * ($bytewordlen - 1));
280
+ if ($intvalue & $signMaskBit) {
281
+ $intvalue = 0 - ($intvalue & ($signMaskBit - 1));
282
+ }
283
+ } else {
284
+ throw new Exception('ERROR: Cannot have signed integers larger than '.(8 * PHP_INT_SIZE).'-bits ('.strlen($byteword).') in getid3_lib::BigEndian2Int()');
285
+ break;
286
+ }
287
+ }
288
+ return getid3_lib::CastAsInt($intvalue);
289
+ }
290
+
291
+
292
+ static function LittleEndian2Int($byteword, $signed=false) {
293
+ return getid3_lib::BigEndian2Int(strrev($byteword), false, $signed);
294
+ }
295
+
296
+
297
+ static function BigEndian2Bin($byteword) {
298
+ $binvalue = '';
299
+ $bytewordlen = strlen($byteword);
300
+ for ($i = 0; $i < $bytewordlen; $i++) {
301
+ $binvalue .= str_pad(decbin(ord($byteword{$i})), 8, '0', STR_PAD_LEFT);
302
+ }
303
+ return $binvalue;
304
+ }
305
+
306
+
307
+ static function BigEndian2String($number, $minbytes=1, $synchsafe=false, $signed=false) {
308
+ if ($number < 0) {
309
+ throw new Exception('ERROR: getid3_lib::BigEndian2String() does not support negative numbers');
310
+ }
311
+ $maskbyte = (($synchsafe || $signed) ? 0x7F : 0xFF);
312
+ $intstring = '';
313
+ if ($signed) {
314
+ if ($minbytes > PHP_INT_SIZE) {
315
+ throw new Exception('ERROR: Cannot have signed integers larger than '.(8 * PHP_INT_SIZE).'-bits in getid3_lib::BigEndian2String()');
316
+ }
317
+ $number = $number & (0x80 << (8 * ($minbytes - 1)));
318
+ }
319
+ while ($number != 0) {
320
+ $quotient = ($number / ($maskbyte + 1));
321
+ $intstring = chr(ceil(($quotient - floor($quotient)) * $maskbyte)).$intstring;
322
+ $number = floor($quotient);
323
+ }
324
+ return str_pad($intstring, $minbytes, "\x00", STR_PAD_LEFT);
325
+ }
326
+
327
+
328
+ static function Dec2Bin($number) {
329
+ while ($number >= 256) {
330
+ $bytes[] = (($number / 256) - (floor($number / 256))) * 256;
331
+ $number = floor($number / 256);
332
+ }
333
+ $bytes[] = $number;
334
+ $binstring = '';
335
+ for ($i = 0; $i < count($bytes); $i++) {
336
+ $binstring = (($i == count($bytes) - 1) ? decbin($bytes[$i]) : str_pad(decbin($bytes[$i]), 8, '0', STR_PAD_LEFT)).$binstring;
337
+ }
338
+ return $binstring;
339
+ }
340
+
341
+
342
+ static function Bin2Dec($binstring, $signed=false) {
343
+ $signmult = 1;
344
+ if ($signed) {
345
+ if ($binstring{0} == '1') {
346
+ $signmult = -1;
347
+ }
348
+ $binstring = substr($binstring, 1);
349
+ }
350
+ $decvalue = 0;
351
+ for ($i = 0; $i < strlen($binstring); $i++) {
352
+ $decvalue += ((int) substr($binstring, strlen($binstring) - $i - 1, 1)) * pow(2, $i);
353
+ }
354
+ return getid3_lib::CastAsInt($decvalue * $signmult);
355
+ }
356
+
357
+
358
+ static function Bin2String($binstring) {
359
+ // return 'hi' for input of '0110100001101001'
360
+ $string = '';
361
+ $binstringreversed = strrev($binstring);
362
+ for ($i = 0; $i < strlen($binstringreversed); $i += 8) {
363
+ $string = chr(getid3_lib::Bin2Dec(strrev(substr($binstringreversed, $i, 8)))).$string;
364
+ }
365
+ return $string;
366
+ }
367
+
368
+
369
+ static function LittleEndian2String($number, $minbytes=1, $synchsafe=false) {
370
+ $intstring = '';
371
+ while ($number > 0) {
372
+ if ($synchsafe) {
373
+ $intstring = $intstring.chr($number & 127);
374
+ $number >>= 7;
375
+ } else {
376
+ $intstring = $intstring.chr($number & 255);
377
+ $number >>= 8;
378
+ }
379
+ }
380
+ return str_pad($intstring, $minbytes, "\x00", STR_PAD_RIGHT);
381
+ }
382
+
383
+
384
+ static function array_merge_clobber($array1, $array2) {
385
+ // written by kc�hireability*com
386
+ // taken from http://www.php.net/manual/en/function.array-merge-recursive.php
387
+ if (!is_array($array1) || !is_array($array2)) {
388
+ return false;
389
+ }
390
+ $newarray = $array1;
391
+ foreach ($array2 as $key => $val) {
392
+ if (is_array($val) && isset($newarray[$key]) && is_array($newarray[$key])) {
393
+ $newarray[$key] = getid3_lib::array_merge_clobber($newarray[$key], $val);
394
+ } else {
395
+ $newarray[$key] = $val;
396
+ }
397
+ }
398
+ return $newarray;
399
+ }
400
+
401
+
402
+ static function array_merge_noclobber($array1, $array2) {
403
+ if (!is_array($array1) || !is_array($array2)) {
404
+ return false;
405
+ }
406
+ $newarray = $array1;
407
+ foreach ($array2 as $key => $val) {
408
+ if (is_array($val) && isset($newarray[$key]) && is_array($newarray[$key])) {
409
+ $newarray[$key] = getid3_lib::array_merge_noclobber($newarray[$key], $val);
410
+ } elseif (!isset($newarray[$key])) {
411
+ $newarray[$key] = $val;
412
+ }
413
+ }
414
+ return $newarray;
415
+ }
416
+
417
+
418
+ static function ksort_recursive(&$theArray) {
419
+ ksort($theArray);
420
+ foreach ($theArray as $key => $value) {
421
+ if (is_array($value)) {
422
+ self::ksort_recursive($theArray[$key]);
423
+ }
424
+ }
425
+ return true;
426
+ }
427
+
428
+ static function fileextension($filename, $numextensions=1) {
429
+ if (strstr($filename, '.')) {
430
+ $reversedfilename = strrev($filename);
431
+ $offset = 0;
432
+ for ($i = 0; $i < $numextensions; $i++) {
433
+ $offset = strpos($reversedfilename, '.', $offset + 1);
434
+ if ($offset === false) {
435
+ return '';
436
+ }
437
+ }
438
+ return strrev(substr($reversedfilename, 0, $offset));
439
+ }
440
+ return '';
441
+ }
442
+
443
+
444
+ static function PlaytimeString($seconds) {
445
+ $sign = (($seconds < 0) ? '-' : '');
446
+ $seconds = abs($seconds);
447
+ $H = floor( $seconds / 3600);
448
+ $M = floor(($seconds - (3600 * $H) ) / 60);
449
+ $S = round( $seconds - (3600 * $H) - (60 * $M) );
450
+ return $sign.($H ? $H.':' : '').($H ? str_pad($M, 2, '0', STR_PAD_LEFT) : intval($M)).':'.str_pad($S, 2, 0, STR_PAD_LEFT);
451
+ }
452
+
453
+
454
+ static function DateMac2Unix($macdate) {
455
+ // Macintosh timestamp: seconds since 00:00h January 1, 1904
456
+ // UNIX timestamp: seconds since 00:00h January 1, 1970
457
+ return getid3_lib::CastAsInt($macdate - 2082844800);
458
+ }
459
+
460
+
461
+ static function FixedPoint8_8($rawdata) {
462
+ return getid3_lib::BigEndian2Int(substr($rawdata, 0, 1)) + (float) (getid3_lib::BigEndian2Int(substr($rawdata, 1, 1)) / pow(2, 8));
463
+ }
464
+
465
+
466
+ static function FixedPoint16_16($rawdata) {
467
+ return getid3_lib::BigEndian2Int(substr($rawdata, 0, 2)) + (float) (getid3_lib::BigEndian2Int(substr($rawdata, 2, 2)) / pow(2, 16));
468
+ }
469
+
470
+
471
+ static function FixedPoint2_30($rawdata) {
472
+ $binarystring = getid3_lib::BigEndian2Bin($rawdata);
473
+ return getid3_lib::Bin2Dec(substr($binarystring, 0, 2)) + (float) (getid3_lib::Bin2Dec(substr($binarystring, 2, 30)) / pow(2, 30));
474
+ }
475
+
476
+
477
+ static function CreateDeepArray($ArrayPath, $Separator, $Value) {
478
+ // assigns $Value to a nested array path:
479
+ // $foo = getid3_lib::CreateDeepArray('/path/to/my', '/', 'file.txt')
480
+ // is the same as:
481
+ // $foo = array('path'=>array('to'=>'array('my'=>array('file.txt'))));
482
+ // or
483
+ // $foo['path']['to']['my'] = 'file.txt';
484
+ while ($ArrayPath && ($ArrayPath{0} == $Separator)) {
485
+ $ArrayPath = substr($ArrayPath, 1);
486
+ }
487
+ if (($pos = strpos($ArrayPath, $Separator)) !== false) {
488
+ $ReturnedArray[substr($ArrayPath, 0, $pos)] = getid3_lib::CreateDeepArray(substr($ArrayPath, $pos + 1), $Separator, $Value);
489
+ } else {
490
+ $ReturnedArray[$ArrayPath] = $Value;
491
+ }
492
+ return $ReturnedArray;
493
+ }
494
+
495
+ static function array_max($arraydata, $returnkey=false) {
496
+ $maxvalue = false;
497
+ $maxkey = false;
498
+ foreach ($arraydata as $key => $value) {
499
+ if (!is_array($value)) {
500
+ if ($value > $maxvalue) {
501
+ $maxvalue = $value;
502
+ $maxkey = $key;
503
+ }
504
+ }
505
+ }
506
+ return ($returnkey ? $maxkey : $maxvalue);
507
+ }
508
+
509
+ static function array_min($arraydata, $returnkey=false) {
510
+ $minvalue = false;
511
+ $minkey = false;
512
+ foreach ($arraydata as $key => $value) {
513
+ if (!is_array($value)) {
514
+ if ($value > $minvalue) {
515
+ $minvalue = $value;
516
+ $minkey = $key;
517
+ }
518
+ }
519
+ }
520
+ return ($returnkey ? $minkey : $minvalue);
521
+ }
522
+
523
+ static function XML2array($XMLstring) {
524
+ if (function_exists('simplexml_load_string')) {
525
+ if (function_exists('get_object_vars')) {
526
+ $XMLobject = simplexml_load_string($XMLstring);
527
+ return self::SimpleXMLelement2array($XMLobject);
528
+ }
529
+ }
530
+ return false;
531
+ }
532
+
533
+ static function SimpleXMLelement2array($XMLobject) {
534
+ if (!is_object($XMLobject) && !is_array($XMLobject)) {
535
+ return $XMLobject;
536
+ }
537
+ $XMLarray = (is_object($XMLobject) ? get_object_vars($XMLobject) : $XMLobject);
538
+ foreach ($XMLarray as $key => $value) {
539
+ $XMLarray[$key] = self::SimpleXMLelement2array($value);
540
+ }
541
+ return $XMLarray;
542
+ }
543
+
544
+
545
+ // Allan Hansen <ah�artemis*dk>
546
+ // getid3_lib::md5_data() - returns md5sum for a file from startuing position to absolute end position
547
+ static function hash_data($file, $offset, $end, $algorithm) {
548
+ static $tempdir = '';
549
+ if (!getid3_lib::intValueSupported($end)) {
550
+ return false;
551
+ }
552
+ switch ($algorithm) {
553
+ case 'md5':
554
+ $hash_function = 'md5_file';
555
+ $unix_call = 'md5sum';
556
+ $windows_call = 'md5sum.exe';
557
+ $hash_length = 32;
558
+ break;
559
+
560
+ case 'sha1':
561
+ $hash_function = 'sha1_file';
562
+ $unix_call = 'sha1sum';
563
+ $windows_call = 'sha1sum.exe';
564
+ $hash_length = 40;
565
+ break;
566
+
567
+ default:
568
+ throw new Exception('Invalid algorithm ('.$algorithm.') in getid3_lib::hash_data()');
569
+ break;
570
+ }
571
+ $size = $end - $offset;
572
+ while (true) {
573
+ if (GETID3_OS_ISWINDOWS) {
574
+
575
+ // It seems that sha1sum.exe for Windows only works on physical files, does not accept piped data
576
+ // Fall back to create-temp-file method:
577
+ if ($algorithm == 'sha1') {
578
+ break;
579
+ }
580
+
581
+ $RequiredFiles = array('cygwin1.dll', 'head.exe', 'tail.exe', $windows_call);
582
+ foreach ($RequiredFiles as $required_file) {
583
+ if (!is_readable(GETID3_HELPERAPPSDIR.$required_file)) {
584
+ // helper apps not available - fall back to old method
585
+ break;
586
+ }
587
+ }
588
+ $commandline = GETID3_HELPERAPPSDIR.'head.exe -c '.$end.' "'.escapeshellarg(str_replace('/', DIRECTORY_SEPARATOR, $file)).'" | ';
589
+ $commandline .= GETID3_HELPERAPPSDIR.'tail.exe -c '.$size.' | ';
590
+ $commandline .= GETID3_HELPERAPPSDIR.$windows_call;
591
+
592
+ } else {
593
+
594
+ $commandline = 'head -c'.$end.' '.escapeshellarg($file).' | ';
595
+ $commandline .= 'tail -c'.$size.' | ';
596
+ $commandline .= $unix_call;
597
+
598
+ }
599
+ if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) {
600
+ //throw new Exception('PHP running in Safe Mode - backtick operator not available, using slower non-system-call '.$algorithm.' algorithm');
601
+ break;
602
+ }
603
+ return substr(`$commandline`, 0, $hash_length);
604
+ }
605
+
606
+ if (empty($tempdir)) {
607
+ // yes this is ugly, feel free to suggest a better way
608
+ require_once(dirname(__FILE__).'/getid3.php');
609
+ $getid3_temp = new getID3();
610
+ $tempdir = $getid3_temp->tempdir;
611
+ unset($getid3_temp);
612
+ }
613
+ // try to create a temporary file in the system temp directory - invalid dirname should force to system temp dir
614
+ if (($data_filename = tempnam($tempdir, 'gI3')) === false) {
615
+ // can't find anywhere to create a temp file, just fail
616
+ return false;
617
+ }
618
+
619
+ // Init
620
+ $result = false;
621
+
622
+ // copy parts of file
623
+ try {
624
+ getid3_lib::CopyFileParts($file, $data_filename, $offset, $end - $offset);
625
+ $result = $hash_function($data_filename);
626
+ } catch (Exception $e) {
627
+ throw new Exception('getid3_lib::CopyFileParts() failed in getid_lib::hash_data(): '.$e->getMessage());
628
+ }
629
+ unlink($data_filename);
630
+ return $result;
631
+ }
632
+
633
+ static function CopyFileParts($filename_source, $filename_dest, $offset, $length) {
634
+ if (!getid3_lib::intValueSupported($offset + $length)) {
635
+ throw new Exception('cannot copy file portion, it extends beyond the '.round(PHP_INT_MAX / 1073741824).'GB limit');
636
+ }
637
+ if (is_readable($filename_source) && is_file($filename_source) && ($fp_src = fopen($filename_source, 'rb'))) {
638
+ if (($fp_dest = fopen($filename_dest, 'wb'))) {
639
+ if (fseek($fp_src, $offset, SEEK_SET) == 0) {
640
+ $byteslefttowrite = $length;
641
+ while (($byteslefttowrite > 0) && ($buffer = fread($fp_src, min($byteslefttowrite, getID3::FREAD_BUFFER_SIZE)))) {
642
+ $byteswritten = fwrite($fp_dest, $buffer, $byteslefttowrite);
643
+ $byteslefttowrite -= $byteswritten;
644
+ }
645
+ return true;
646
+ } else {
647
+ throw new Exception('failed to seek to offset '.$offset.' in '.$filename_source);
648
+ }
649
+ fclose($fp_dest);
650
+ } else {
651
+ throw new Exception('failed to create file for writing '.$filename_dest);
652
+ }
653
+ fclose($fp_src);
654
+ } else {
655
+ throw new Exception('failed to open file for reading '.$filename_source);
656
+ }
657
+ return false;
658
+ }
659
+
660
+ static function iconv_fallback_int_utf8($charval) {
661
+ if ($charval < 128) {
662
+ // 0bbbbbbb
663
+ $newcharstring = chr($charval);
664
+ } elseif ($charval < 2048) {
665
+ // 110bbbbb 10bbbbbb
666
+ $newcharstring = chr(($charval >> 6) | 0xC0);
667
+ $newcharstring .= chr(($charval & 0x3F) | 0x80);
668
+ } elseif ($charval < 65536) {
669
+ // 1110bbbb 10bbbbbb 10bbbbbb
670
+ $newcharstring = chr(($charval >> 12) | 0xE0);
671
+ $newcharstring .= chr(($charval >> 6) | 0xC0);
672
+ $newcharstring .= chr(($charval & 0x3F) | 0x80);
673
+ } else {
674
+ // 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb
675
+ $newcharstring = chr(($charval >> 18) | 0xF0);
676
+ $newcharstring .= chr(($charval >> 12) | 0xC0);
677
+ $newcharstring .= chr(($charval >> 6) | 0xC0);
678
+ $newcharstring .= chr(($charval & 0x3F) | 0x80);
679
+ }
680
+ return $newcharstring;
681
+ }
682
+
683
+ // ISO-8859-1 => UTF-8
684
+ static function iconv_fallback_iso88591_utf8($string, $bom=false) {
685
+ if (function_exists('utf8_encode')) {
686
+ return utf8_encode($string);
687
+ }
688
+ // utf8_encode() unavailable, use getID3()'s iconv_fallback() conversions (possibly PHP is compiled without XML support)
689
+ $newcharstring = '';
690
+ if ($bom) {
691
+ $newcharstring .= "\xEF\xBB\xBF";
692
+ }
693
+ for ($i = 0; $i < strlen($string); $i++) {
694
+ $charval = ord($string{$i});
695
+ $newcharstring .= getid3_lib::iconv_fallback_int_utf8($charval);
696
+ }
697
+ return $newcharstring;
698
+ }
699
+
700
+ // ISO-8859-1 => UTF-16BE
701
+ static function iconv_fallback_iso88591_utf16be($string, $bom=false) {
702
+ $newcharstring = '';
703
+ if ($bom) {
704
+ $newcharstring .= "\xFE\xFF";
705
+ }
706
+ for ($i = 0; $i < strlen($string); $i++) {
707
+ $newcharstring .= "\x00".$string{$i};
708
+ }
709
+ return $newcharstring;
710
+ }
711
+
712
+ // ISO-8859-1 => UTF-16LE
713
+ static function iconv_fallback_iso88591_utf16le($string, $bom=false) {
714
+ $newcharstring = '';
715
+ if ($bom) {
716
+ $newcharstring .= "\xFF\xFE";
717
+ }
718
+ for ($i = 0; $i < strlen($string); $i++) {
719
+ $newcharstring .= $string{$i}."\x00";
720
+ }
721
+ return $newcharstring;
722
+ }
723
+
724
+ // ISO-8859-1 => UTF-16LE (BOM)
725
+ static function iconv_fallback_iso88591_utf16($string) {
726
+ return getid3_lib::iconv_fallback_iso88591_utf16le($string, true);
727
+ }
728
+
729
+ // UTF-8 => ISO-8859-1
730
+ static function iconv_fallback_utf8_iso88591($string) {
731
+ if (function_exists('utf8_decode')) {
732
+ return utf8_decode($string);
733
+ }
734
+ // utf8_decode() unavailable, use getID3()'s iconv_fallback() conversions (possibly PHP is compiled without XML support)
735
+ $newcharstring = '';
736
+ $offset = 0;
737
+ $stringlength = strlen($string);
738
+ while ($offset < $stringlength) {
739
+ if ((ord($string{$offset}) | 0x07) == 0xF7) {
740
+ // 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb
741
+ $charval = ((ord($string{($offset + 0)}) & 0x07) << 18) &
742
+ ((ord($string{($offset + 1)}) & 0x3F) << 12) &
743
+ ((ord($string{($offset + 2)}) & 0x3F) << 6) &
744
+ (ord($string{($offset + 3)}) & 0x3F);
745
+ $offset += 4;
746
+ } elseif ((ord($string{$offset}) | 0x0F) == 0xEF) {
747
+ // 1110bbbb 10bbbbbb 10bbbbbb
748
+ $charval = ((ord($string{($offset + 0)}) & 0x0F) << 12) &
749
+ ((ord($string{($offset + 1)}) & 0x3F) << 6) &
750
+ (ord($string{($offset + 2)}) & 0x3F);
751
+ $offset += 3;
752
+ } elseif ((ord($string{$offset}) | 0x1F) == 0xDF) {
753
+ // 110bbbbb 10bbbbbb
754
+ $charval = ((ord($string{($offset + 0)}) & 0x1F) << 6) &
755
+ (ord($string{($offset + 1)}) & 0x3F);
756
+ $offset += 2;
757
+ } elseif ((ord($string{$offset}) | 0x7F) == 0x7F) {
758
+ // 0bbbbbbb
759
+ $charval = ord($string{$offset});
760
+ $offset += 1;
761
+ } else {
762
+ // error? throw some kind of warning here?
763
+ $charval = false;
764
+ $offset += 1;
765
+ }
766
+ if ($charval !== false) {
767
+ $newcharstring .= (($charval < 256) ? chr($charval) : '?');
768
+ }
769
+ }
770
+ return $newcharstring;
771
+ }
772
+
773
+ // UTF-8 => UTF-16BE
774
+ static function iconv_fallback_utf8_utf16be($string, $bom=false) {
775
+ $newcharstring = '';
776
+ if ($bom) {
777
+ $newcharstring .= "\xFE\xFF";
778
+ }
779
+ $offset = 0;
780
+ $stringlength = strlen($string);
781
+ while ($offset < $stringlength) {
782
+ if ((ord($string{$offset}) | 0x07) == 0xF7) {
783
+ // 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb
784
+ $charval = ((ord($string{($offset + 0)}) & 0x07) << 18) &
785
+ ((ord($string{($offset + 1)}) & 0x3F) << 12) &
786
+ ((ord($string{($offset + 2)}) & 0x3F) << 6) &
787
+ (ord($string{($offset + 3)}) & 0x3F);
788
+ $offset += 4;
789
+ } elseif ((ord($string{$offset}) | 0x0F) == 0xEF) {
790
+ // 1110bbbb 10bbbbbb 10bbbbbb
791
+ $charval = ((ord($string{($offset + 0)}) & 0x0F) << 12) &
792
+ ((ord($string{($offset + 1)}) & 0x3F) << 6) &
793
+ (ord($string{($offset + 2)}) & 0x3F);
794
+ $offset += 3;
795
+ } elseif ((ord($string{$offset}) | 0x1F) == 0xDF) {
796
+ // 110bbbbb 10bbbbbb
797
+ $charval = ((ord($string{($offset + 0)}) & 0x1F) << 6) &
798
+ (ord($string{($offset + 1)}) & 0x3F);
799
+ $offset += 2;
800
+ } elseif ((ord($string{$offset}) | 0x7F) == 0x7F) {
801
+ // 0bbbbbbb
802
+ $charval = ord($string{$offset});
803
+ $offset += 1;
804
+ } else {
805
+ // error? throw some kind of warning here?
806
+ $charval = false;
807
+ $offset += 1;
808
+ }
809
+ if ($charval !== false) {
810
+ $newcharstring .= (($charval < 65536) ? getid3_lib::BigEndian2String($charval, 2) : "\x00".'?');
811
+ }
812
+ }
813
+ return $newcharstring;
814
+ }
815
+
816
+ // UTF-8 => UTF-16LE
817
+ static function iconv_fallback_utf8_utf16le($string, $bom=false) {
818
+ $newcharstring = '';
819
+ if ($bom) {
820
+ $newcharstring .= "\xFF\xFE";
821
+ }
822
+ $offset = 0;
823
+ $stringlength = strlen($string);
824
+ while ($offset < $stringlength) {
825
+ if ((ord($string{$offset}) | 0x07) == 0xF7) {
826
+ // 11110bbb 10bbbbbb 10bbbbbb 10bbbbbb
827
+ $charval = ((ord($string{($offset + 0)}) & 0x07) << 18) &
828
+ ((ord($string{($offset + 1)}) & 0x3F) << 12) &
829
+ ((ord($string{($offset + 2)}) & 0x3F) << 6) &
830
+ (ord($string{($offset + 3)}) & 0x3F);
831
+ $offset += 4;
832
+ } elseif ((ord($string{$offset}) | 0x0F) == 0xEF) {
833
+ // 1110bbbb 10bbbbbb 10bbbbbb
834
+ $charval = ((ord($string{($offset + 0)}) & 0x0F) << 12) &
835
+ ((ord($string{($offset + 1)}) & 0x3F) << 6) &
836
+ (ord($string{($offset + 2)}) & 0x3F);
837
+ $offset += 3;
838
+ } elseif ((ord($string{$offset}) | 0x1F) == 0xDF) {
839
+ // 110bbbbb 10bbbbbb
840
+ $charval = ((ord($string{($offset + 0)}) & 0x1F) << 6) &
841
+ (ord($string{($offset + 1)}) & 0x3F);
842
+ $offset += 2;
843
+ } elseif ((ord($string{$offset}) | 0x7F) == 0x7F) {
844
+ // 0bbbbbbb
845
+ $charval = ord($string{$offset});
846
+ $offset += 1;
847
+ } else {
848
+ // error? maybe throw some warning here?
849
+ $charval = false;
850
+ $offset += 1;
851
+ }
852
+ if ($charval !== false) {
853
+ $newcharstring .= (($charval < 65536) ? getid3_lib::LittleEndian2String($charval, 2) : '?'."\x00");
854
+ }
855
+ }
856
+ return $newcharstring;
857
+ }
858
+
859
+ // UTF-8 => UTF-16LE (BOM)
860
+ static function iconv_fallback_utf8_utf16($string) {
861
+ return getid3_lib::iconv_fallback_utf8_utf16le($string, true);
862
+ }
863
+
864
+ // UTF-16BE => UTF-8
865
+ static function iconv_fallback_utf16be_utf8($string) {
866
+ if (substr($string, 0, 2) == "\xFE\xFF") {
867
+ // strip BOM
868
+ $string = substr($string, 2);
869
+ }
870
+ $newcharstring = '';
871
+ for ($i = 0; $i < strlen($string); $i += 2) {
872
+ $charval = getid3_lib::BigEndian2Int(substr($string, $i, 2));
873
+ $newcharstring .= getid3_lib::iconv_fallback_int_utf8($charval);
874
+ }
875
+ return $newcharstring;
876
+ }
877
+
878
+ // UTF-16LE => UTF-8
879
+ static function iconv_fallback_utf16le_utf8($string) {
880
+ if (substr($string, 0, 2) == "\xFF\xFE") {
881
+ // strip BOM
882
+ $string = substr($string, 2);
883
+ }
884
+ $newcharstring = '';
885
+ for ($i = 0; $i < strlen($string); $i += 2) {
886
+ $charval = getid3_lib::LittleEndian2Int(substr($string, $i, 2));
887
+ $newcharstring .= getid3_lib::iconv_fallback_int_utf8($charval);
888
+ }
889
+ return $newcharstring;
890
+ }
891
+
892
+ // UTF-16BE => ISO-8859-1
893
+ static function iconv_fallback_utf16be_iso88591($string) {
894
+ if (substr($string, 0, 2) == "\xFE\xFF") {
895
+ // strip BOM
896
+ $string = substr($string, 2);
897
+ }
898
+ $newcharstring = '';
899
+ for ($i = 0; $i < strlen($string); $i += 2) {
900
+ $charval = getid3_lib::BigEndian2Int(substr($string, $i, 2));
901
+ $newcharstring .= (($charval < 256) ? chr($charval) : '?');
902
+ }
903
+ return $newcharstring;
904
+ }
905
+
906
+ // UTF-16LE => ISO-8859-1
907
+ static function iconv_fallback_utf16le_iso88591($string) {
908
+ if (substr($string, 0, 2) == "\xFF\xFE") {
909
+ // strip BOM
910
+ $string = substr($string, 2);
911
+ }
912
+ $newcharstring = '';
913
+ for ($i = 0; $i < strlen($string); $i += 2) {
914
+ $charval = getid3_lib::LittleEndian2Int(substr($string, $i, 2));
915
+ $newcharstring .= (($charval < 256) ? chr($charval) : '?');
916
+ }
917
+ return $newcharstring;
918
+ }
919
+
920
+ // UTF-16 (BOM) => ISO-8859-1
921
+ static function iconv_fallback_utf16_iso88591($string) {
922
+ $bom = substr($string, 0, 2);
923
+ if ($bom == "\xFE\xFF") {
924
+ return getid3_lib::iconv_fallback_utf16be_iso88591(substr($string, 2));
925
+ } elseif ($bom == "\xFF\xFE") {
926
+ return getid3_lib::iconv_fallback_utf16le_iso88591(substr($string, 2));
927
+ }
928
+ return $string;
929
+ }
930
+
931
+ // UTF-16 (BOM) => UTF-8
932
+ static function iconv_fallback_utf16_utf8($string) {
933
+ $bom = substr($string, 0, 2);
934
+ if ($bom == "\xFE\xFF") {
935
+ return getid3_lib::iconv_fallback_utf16be_utf8(substr($string, 2));
936
+ } elseif ($bom == "\xFF\xFE") {
937
+ return getid3_lib::iconv_fallback_utf16le_utf8(substr($string, 2));
938
+ }
939
+ return $string;
940
+ }
941
+
942
+ static function iconv_fallback($in_charset, $out_charset, $string) {
943
+
944
+ if ($in_charset == $out_charset) {
945
+ return $string;
946
+ }
947
+
948
+ // iconv() availble
949
+ if (function_exists('iconv')) {
950
+ if ($converted_string = @iconv($in_charset, $out_charset.'//TRANSLIT', $string)) {
951
+ switch ($out_charset) {
952
+ case 'ISO-8859-1':
953
+ $converted_string = rtrim($converted_string, "\x00");
954
+ break;
955
+ }
956
+ return $converted_string;
957
+ }
958
+
959
+ // iconv() may sometimes fail with "illegal character in input string" error message
960
+ // and return an empty string, but returning the unconverted string is more useful
961
+ return $string;
962
+ }
963
+
964
+
965
+ // iconv() not available
966
+ static $ConversionFunctionList = array();
967
+ if (empty($ConversionFunctionList)) {
968
+ $ConversionFunctionList['ISO-8859-1']['UTF-8'] = 'iconv_fallback_iso88591_utf8';
969
+ $ConversionFunctionList['ISO-8859-1']['UTF-16'] = 'iconv_fallback_iso88591_utf16';
970
+ $ConversionFunctionList['ISO-8859-1']['UTF-16BE'] = 'iconv_fallback_iso88591_utf16be';
971
+ $ConversionFunctionList['ISO-8859-1']['UTF-16LE'] = 'iconv_fallback_iso88591_utf16le';
972
+ $ConversionFunctionList['UTF-8']['ISO-8859-1'] = 'iconv_fallback_utf8_iso88591';
973
+ $ConversionFunctionList['UTF-8']['UTF-16'] = 'iconv_fallback_utf8_utf16';
974
+ $ConversionFunctionList['UTF-8']['UTF-16BE'] = 'iconv_fallback_utf8_utf16be';
975
+ $ConversionFunctionList['UTF-8']['UTF-16LE'] = 'iconv_fallback_utf8_utf16le';
976
+ $ConversionFunctionList['UTF-16']['ISO-8859-1'] = 'iconv_fallback_utf16_iso88591';
977
+ $ConversionFunctionList['UTF-16']['UTF-8'] = 'iconv_fallback_utf16_utf8';
978
+ $ConversionFunctionList['UTF-16LE']['ISO-8859-1'] = 'iconv_fallback_utf16le_iso88591';
979
+ $ConversionFunctionList['UTF-16LE']['UTF-8'] = 'iconv_fallback_utf16le_utf8';
980
+ $ConversionFunctionList['UTF-16BE']['ISO-8859-1'] = 'iconv_fallback_utf16be_iso88591';
981
+ $ConversionFunctionList['UTF-16BE']['UTF-8'] = 'iconv_fallback_utf16be_utf8';
982
+ }
983
+ if (isset($ConversionFunctionList[strtoupper($in_charset)][strtoupper($out_charset)])) {
984
+ $ConversionFunction = $ConversionFunctionList[strtoupper($in_charset)][strtoupper($out_charset)];
985
+ return getid3_lib::$ConversionFunction($string);
986
+ }
987
+ throw new Exception('PHP does not have iconv() support - cannot convert from '.$in_charset.' to '.$out_charset);
988
+ }
989
+
990
+
991
+ static function MultiByteCharString2HTML($string, $charset='ISO-8859-1') {
992
+ $string = (string) $string; // in case trying to pass a numeric (float, int) string, would otherwise return an empty string
993
+ $HTMLstring = '';
994
+
995
+ switch ($charset) {
996
+ case '1251':
997
+ case '1252':
998
+ case '866':
999
+ case '932':
1000
+ case '936':
1001
+ case '950':
1002
+ case 'BIG5':
1003
+ case 'BIG5-HKSCS':
1004
+ case 'cp1251':
1005
+ case 'cp1252':
1006
+ case 'cp866':
1007
+ case 'EUC-JP':
1008
+ case 'EUCJP':
1009
+ case 'GB2312':
1010
+ case 'ibm866':
1011
+ case 'ISO-8859-1':
1012
+ case 'ISO-8859-15':
1013
+ case 'ISO8859-1':
1014
+ case 'ISO8859-15':
1015
+ case 'KOI8-R':
1016
+ case 'koi8-ru':
1017
+ case 'koi8r':
1018
+ case 'Shift_JIS':
1019
+ case 'SJIS':
1020
+ case 'win-1251':
1021
+ case 'Windows-1251':
1022
+ case 'Windows-1252':
1023
+ $HTMLstring = htmlentities($string, ENT_COMPAT, $charset);
1024
+ break;
1025
+
1026
+ case 'UTF-8':
1027
+ $strlen = strlen($string);
1028
+ for ($i = 0; $i < $strlen; $i++) {
1029
+ $char_ord_val = ord($string{$i});
1030
+ $charval = 0;
1031
+ if ($char_ord_val < 0x80) {
1032
+ $charval = $char_ord_val;
1033
+ } elseif ((($char_ord_val & 0xF0) >> 4) == 0x0F && $i+3 < $strlen) {
1034
+ $charval = (($char_ord_val & 0x07) << 18);
1035
+ $charval += ((ord($string{++$i}) & 0x3F) << 12);
1036
+ $charval += ((ord($string{++$i}) & 0x3F) << 6);
1037
+ $charval += (ord($string{++$i}) & 0x3F);
1038
+ } elseif ((($char_ord_val & 0xE0) >> 5) == 0x07 && $i+2 < $strlen) {
1039
+ $charval = (($char_ord_val & 0x0F) << 12);
1040
+ $charval += ((ord($string{++$i}) & 0x3F) << 6);
1041
+ $charval += (ord($string{++$i}) & 0x3F);
1042
+ } elseif ((($char_ord_val & 0xC0) >> 6) == 0x03 && $i+1 < $strlen) {
1043
+ $charval = (($char_ord_val & 0x1F) << 6);
1044
+ $charval += (ord($string{++$i}) & 0x3F);
1045
+ }
1046
+ if (($charval >= 32) && ($charval <= 127)) {
1047
+ $HTMLstring .= htmlentities(chr($charval));
1048
+ } else {
1049
+ $HTMLstring .= '&#'.$charval.';';
1050
+ }
1051
+ }
1052
+ break;
1053
+
1054
+ case 'UTF-16LE':
1055
+ for ($i = 0; $i < strlen($string); $i += 2) {
1056
+ $charval = getid3_lib::LittleEndian2Int(substr($string, $i, 2));
1057
+ if (($charval >= 32) && ($charval <= 127)) {
1058
+ $HTMLstring .= chr($charval);
1059
+ } else {
1060
+ $HTMLstring .= '&#'.$charval.';';
1061
+ }
1062
+ }
1063
+ break;
1064
+
1065
+ case 'UTF-16BE':
1066
+ for ($i = 0; $i < strlen($string); $i += 2) {
1067
+ $charval = getid3_lib::BigEndian2Int(substr($string, $i, 2));
1068
+ if (($charval >= 32) && ($charval <= 127)) {
1069
+ $HTMLstring .= chr($charval);
1070
+ } else {
1071
+ $HTMLstring .= '&#'.$charval.';';
1072
+ }
1073
+ }
1074
+ break;
1075
+
1076
+ default:
1077
+ $HTMLstring = 'ERROR: Character set "'.$charset.'" not supported in MultiByteCharString2HTML()';
1078
+ break;
1079
+ }
1080
+ return $HTMLstring;
1081
+ }
1082
+
1083
+
1084
+
1085
+ static function RGADnameLookup($namecode) {
1086
+ static $RGADname = array();
1087
+ if (empty($RGADname)) {
1088
+ $RGADname[0] = 'not set';
1089
+ $RGADname[1] = 'Track Gain Adjustment';
1090
+ $RGADname[2] = 'Album Gain Adjustment';
1091
+ }
1092
+
1093
+ return (isset($RGADname[$namecode]) ? $RGADname[$namecode] : '');
1094
+ }
1095
+
1096
+
1097
+ static function RGADoriginatorLookup($originatorcode) {
1098
+ static $RGADoriginator = array();
1099
+ if (empty($RGADoriginator)) {
1100
+ $RGADoriginator[0] = 'unspecified';
1101
+ $RGADoriginator[1] = 'pre-set by artist/producer/mastering engineer';
1102
+ $RGADoriginator[2] = 'set by user';
1103
+ $RGADoriginator[3] = 'determined automatically';
1104
+ }
1105
+
1106
+ return (isset($RGADoriginator[$originatorcode]) ? $RGADoriginator[$originatorcode] : '');
1107
+ }
1108
+
1109
+
1110
+ static function RGADadjustmentLookup($rawadjustment, $signbit) {
1111
+ $adjustment = $rawadjustment / 10;
1112
+ if ($signbit == 1) {
1113
+ $adjustment *= -1;
1114
+ }
1115
+ return (float) $adjustment;
1116
+ }
1117
+
1118
+
1119
+ static function RGADgainString($namecode, $originatorcode, $replaygain) {
1120
+ if ($replaygain < 0) {
1121
+ $signbit = '1';
1122
+ } else {
1123
+ $signbit = '0';
1124
+ }
1125
+ $storedreplaygain = intval(round($replaygain * 10));
1126
+ $gainstring = str_pad(decbin($namecode), 3, '0', STR_PAD_LEFT);
1127
+ $gainstring .= str_pad(decbin($originatorcode), 3, '0', STR_PAD_LEFT);
1128
+ $gainstring .= $signbit;
1129
+ $gainstring .= str_pad(decbin($storedreplaygain), 9, '0', STR_PAD_LEFT);
1130
+
1131
+ return $gainstring;
1132
+ }
1133
+
1134
+ static function RGADamplitude2dB($amplitude) {
1135
+ return 20 * log10($amplitude);
1136
+ }
1137
+
1138
+
1139
+ static function GetDataImageSize($imgData, &$imageinfo) {
1140
+ static $tempdir = '';
1141
+ if (empty($tempdir)) {
1142
+ // yes this is ugly, feel free to suggest a better way
1143
+ require_once(dirname(__FILE__).'/getid3.php');
1144
+ $getid3_temp = new getID3();
1145
+ $tempdir = $getid3_temp->tempdir;
1146
+ unset($getid3_temp);
1147
+ }
1148
+ $GetDataImageSize = false;
1149
+ if ($tempfilename = tempnam($tempdir, 'gI3')) {
1150
+ if (is_writable($tempfilename) && is_file($tempfilename) && ($tmp = fopen($tempfilename, 'wb'))) {
1151
+ fwrite($tmp, $imgData);
1152
+ fclose($tmp);
1153
+ $GetDataImageSize = @GetImageSize($tempfilename, $imageinfo);
1154
+ }
1155
+ unlink($tempfilename);
1156
+ }
1157
+ return $GetDataImageSize;
1158
+ }
1159
+
1160
+ static function ImageTypesLookup($imagetypeid) {
1161
+ static $ImageTypesLookup = array();
1162
+ if (empty($ImageTypesLookup)) {
1163
+ $ImageTypesLookup[1] = 'gif';
1164
+ $ImageTypesLookup[2] = 'jpeg';
1165
+ $ImageTypesLookup[3] = 'png';
1166
+ $ImageTypesLookup[4] = 'swf';
1167
+ $ImageTypesLookup[5] = 'psd';
1168
+ $ImageTypesLookup[6] = 'bmp';
1169
+ $ImageTypesLookup[7] = 'tiff (little-endian)';
1170
+ $ImageTypesLookup[8] = 'tiff (big-endian)';
1171
+ $ImageTypesLookup[9] = 'jpc';
1172
+ $ImageTypesLookup[10] = 'jp2';
1173
+ $ImageTypesLookup[11] = 'jpx';
1174
+ $ImageTypesLookup[12] = 'jb2';
1175
+ $ImageTypesLookup[13] = 'swc';
1176
+ $ImageTypesLookup[14] = 'iff';
1177
+ }
1178
+ return (isset($ImageTypesLookup[$imagetypeid]) ? $ImageTypesLookup[$imagetypeid] : '');
1179
+ }
1180
+
1181
+ static function CopyTagsToComments(&$ThisFileInfo) {
1182
+
1183
+ // Copy all entries from ['tags'] into common ['comments']
1184
+ if (!empty($ThisFileInfo['tags'])) {
1185
+ foreach ($ThisFileInfo['tags'] as $tagtype => $tagarray) {
1186
+ foreach ($tagarray as $tagname => $tagdata) {
1187
+ foreach ($tagdata as $key => $value) {
1188
+ if (!empty($value)) {
1189
+ if (empty($ThisFileInfo['comments'][$tagname])) {
1190
+
1191
+ // fall through and append value
1192
+
1193
+ } elseif ($tagtype == 'id3v1') {
1194
+
1195
+ $newvaluelength = strlen(trim($value));
1196
+ foreach ($ThisFileInfo['comments'][$tagname] as $existingkey => $existingvalue) {
1197
+ $oldvaluelength = strlen(trim($existingvalue));
1198
+ if (($newvaluelength <= $oldvaluelength) && (substr($existingvalue, 0, $newvaluelength) == trim($value))) {
1199
+ // new value is identical but shorter-than (or equal-length to) one already in comments - skip
1200
+ break 2;
1201
+ }
1202
+ }
1203
+
1204
+ } elseif (!is_array($value)) {
1205
+
1206
+ $newvaluelength = strlen(trim($value));
1207
+ foreach ($ThisFileInfo['comments'][$tagname] as $existingkey => $existingvalue) {
1208
+ $oldvaluelength = strlen(trim($existingvalue));
1209
+ if (($newvaluelength > $oldvaluelength) && (substr(trim($value), 0, strlen($existingvalue)) == $existingvalue)) {
1210
+ $ThisFileInfo['comments'][$tagname][$existingkey] = trim($value);
1211
+ break 2;
1212
+ }
1213
+ }
1214
+
1215
+ }
1216
+ if (is_array($value) || empty($ThisFileInfo['comments'][$tagname]) || !in_array(trim($value), $ThisFileInfo['comments'][$tagname])) {
1217
+ $value = (is_string($value) ? trim($value) : $value);
1218
+ $ThisFileInfo['comments'][$tagname][] = $value;
1219
+ }
1220
+ }
1221
+ }
1222
+ }
1223
+ }
1224
+
1225
+ // Copy to ['comments_html']
1226
+ foreach ($ThisFileInfo['comments'] as $field => $values) {
1227
+ if ($field == 'picture') {
1228
+ // pictures can take up a lot of space, and we don't need multiple copies of them
1229
+ // let there be a single copy in [comments][picture], and not elsewhere
1230
+ continue;
1231
+ }
1232
+ foreach ($values as $index => $value) {
1233
+ if (is_array($value)) {
1234
+ $ThisFileInfo['comments_html'][$field][$index] = $value;
1235
+ } else {
1236
+ $ThisFileInfo['comments_html'][$field][$index] = str_replace('&#0;', '', getid3_lib::MultiByteCharString2HTML($value, $ThisFileInfo['encoding']));
1237
+ }
1238
+ }
1239
+ }
1240
+ }
1241
+ return true;
1242
+ }
1243
+
1244
+
1245
+ static function EmbeddedLookup($key, $begin, $end, $file, $name) {
1246
+
1247
+ // Cached
1248
+ static $cache;
1249
+ if (isset($cache[$file][$name])) {
1250
+ return (isset($cache[$file][$name][$key]) ? $cache[$file][$name][$key] : '');
1251
+ }
1252
+
1253
+ // Init
1254
+ $keylength = strlen($key);
1255
+ $line_count = $end - $begin - 7;
1256
+
1257
+ // Open php file
1258
+ $fp = fopen($file, 'r');
1259
+
1260
+ // Discard $begin lines
1261
+ for ($i = 0; $i < ($begin + 3); $i++) {
1262
+ fgets($fp, 1024);
1263
+ }
1264
+
1265
+ // Loop thru line
1266
+ while (0 < $line_count--) {
1267
+
1268
+ // Read line
1269
+ $line = ltrim(fgets($fp, 1024), "\t ");
1270
+
1271
+ // METHOD A: only cache the matching key - less memory but slower on next lookup of not-previously-looked-up key
1272
+ //$keycheck = substr($line, 0, $keylength);
1273
+ //if ($key == $keycheck) {
1274
+ // $cache[$file][$name][$keycheck] = substr($line, $keylength + 1);
1275
+ // break;
1276
+ //}
1277
+
1278
+ // METHOD B: cache all keys in this lookup - more memory but faster on next lookup of not-previously-looked-up key
1279
+ //$cache[$file][$name][substr($line, 0, $keylength)] = trim(substr($line, $keylength + 1));
1280
+ $explodedLine = explode("\t", $line, 2);
1281
+ $ThisKey = (isset($explodedLine[0]) ? $explodedLine[0] : '');
1282
+ $ThisValue = (isset($explodedLine[1]) ? $explodedLine[1] : '');
1283
+ $cache[$file][$name][$ThisKey] = trim($ThisValue);
1284
+ }
1285
+
1286
+ // Close and return
1287
+ fclose($fp);
1288
+ return (isset($cache[$file][$name][$key]) ? $cache[$file][$name][$key] : '');
1289
+ }
1290
+
1291
+ static function IncludeDependency($filename, $sourcefile, $DieOnFailure=false) {
1292
+ global $GETID3_ERRORARRAY;
1293
+
1294
+ if (file_exists($filename)) {
1295
+ if (include_once($filename)) {
1296
+ return true;
1297
+ } else {
1298
+ $diemessage = basename($sourcefile).' depends on '.$filename.', which has errors';
1299
+ }
1300
+ } else {
1301
+ $diemessage = basename($sourcefile).' depends on '.$filename.', which is missing';
1302
+ }
1303
+ if ($DieOnFailure) {
1304
+ throw new Exception($diemessage);
1305
+ } else {
1306
+ $GETID3_ERRORARRAY[] = $diemessage;
1307
+ }
1308
+ return false;
1309
+ }
1310
+
1311
+ public static function trimNullByte($string) {
1312
+ return trim($string, "\x00");
1313
+ }
1314
+
1315
+ }
1316
+
1317
+ ?>
includes/lib/getid3/getid3.php ADDED
@@ -0,0 +1,1744 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /////////////////////////////////////////////////////////////////
3
+ /// getID3() by James Heinrich <info@getid3.org> //
4
+ // available at http://getid3.sourceforge.net //
5
+ // or http://www.getid3.org //
6
+ /////////////////////////////////////////////////////////////////
7
+ // //
8
+ // Please see readme.txt for more information //
9
+ // ///
10
+ /////////////////////////////////////////////////////////////////
11
+
12
+ // attempt to define temp dir as something flexible but reliable
13
+ $temp_dir = ini_get('upload_tmp_dir');
14
+ if ($temp_dir && (!is_dir($temp_dir) || !is_readable($temp_dir))) {
15
+ $temp_dir = '';
16
+ }
17
+ if (!$temp_dir && function_exists('sys_get_temp_dir')) {
18
+ // PHP v5.2.1+
19
+ // sys_get_temp_dir() may give inaccessible temp dir, e.g. with open_basedir on virtual hosts
20
+ $temp_dir = sys_get_temp_dir();
21
+ }
22
+ $temp_dir = realpath($temp_dir);
23
+ $open_basedir = ini_get('open_basedir');
24
+ if ($open_basedir) {
25
+ // e.g. "/var/www/vhosts/getid3.org/httpdocs/:/tmp/"
26
+ $temp_dir = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $temp_dir);
27
+ $open_basedir = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $open_basedir);
28
+ if (substr($temp_dir, -1, 1) != DIRECTORY_SEPARATOR) {
29
+ $temp_dir .= DIRECTORY_SEPARATOR;
30
+ }
31
+ $found_valid_tempdir = false;
32
+ $open_basedirs = explode(':', $open_basedir);
33
+ foreach ($open_basedirs as $basedir) {
34
+ if (substr($basedir, -1, 1) != DIRECTORY_SEPARATOR) {
35
+ $basedir .= DIRECTORY_SEPARATOR;
36
+ }
37
+ if (preg_match('#^'.preg_quote($basedir).'#', $temp_dir)) {
38
+ $found_valid_tempdir = true;
39
+ break;
40
+ }
41
+ }
42
+ if (!$found_valid_tempdir) {
43
+ $temp_dir = '';
44
+ }
45
+ unset($open_basedirs, $found_valid_tempdir, $basedir);
46
+ }
47
+ if (!$temp_dir) {
48
+ $temp_dir = '*'; // invalid directory name should force tempnam() to use system default temp dir
49
+ }
50
+ // $temp_dir = '/something/else/'; // feel free to override temp dir here if it works better for your system
51
+ define('GETID3_TEMP_DIR', $temp_dir);
52
+ unset($open_basedir, $temp_dir);
53
+
54
+
55
+ // define a constant rather than looking up every time it is needed
56
+ if (!defined('GETID3_OS_ISWINDOWS')) {
57
+ if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') {
58
+ define('GETID3_OS_ISWINDOWS', true);
59
+ } else {
60
+ define('GETID3_OS_ISWINDOWS', false);
61
+ }
62
+ }
63
+
64
+ // Get base path of getID3() - ONCE
65
+ if (!defined('GETID3_INCLUDEPATH')) {
66
+ foreach (get_included_files() as $key => $val) {
67
+ if (basename($val) == 'getid3.php') {
68
+ define('GETID3_INCLUDEPATH', dirname($val).DIRECTORY_SEPARATOR);
69
+ break;
70
+ }
71
+ }
72
+ }
73
+
74
+ // End: Defines
75
+
76
+
77
+ class getID3
78
+ {
79
+ // public: Settings
80
+ public $encoding = 'UTF-8'; // CASE SENSITIVE! - i.e. (must be supported by iconv()). Examples: ISO-8859-1 UTF-8 UTF-16 UTF-16BE
81
+ public $encoding_id3v1 = 'ISO-8859-1'; // Should always be 'ISO-8859-1', but some tags may be written in other encodings such as 'EUC-CN' or 'CP1252'
82
+
83
+ // public: Optional tag checks - disable for speed.
84
+ public $option_tag_id3v1 = true; // Read and process ID3v1 tags
85
+ public $option_tag_id3v2 = true; // Read and process ID3v2 tags
86
+ public $option_tag_lyrics3 = true; // Read and process Lyrics3 tags
87
+ public $option_tag_apetag = true; // Read and process APE tags
88
+ public $option_tags_process = true; // Copy tags to root key 'tags' and encode to $this->encoding
89
+ public $option_tags_html = true; // Copy tags to root key 'tags_html' properly translated from various encodings to HTML entities
90
+
91
+ // public: Optional tag/comment calucations
92
+ public $option_extra_info = true; // Calculate additional info such as bitrate, channelmode etc
93
+
94
+ // public: Optional handling of embedded attachments (e.g. images)
95
+ public $option_save_attachments = true; // defaults to true (ATTACHMENTS_INLINE) for backward compatibility
96
+
97
+ // public: Optional calculations
98
+ public $option_md5_data = false; // Get MD5 sum of data part - slow
99
+ public $option_md5_data_source = false; // Use MD5 of source file if availble - only FLAC and OptimFROG
100
+ public $option_sha1_data = false; // Get SHA1 sum of data part - slow
101
+ public $option_max_2gb_check = null; // Check whether file is larger than 2GB and thus not supported by 32-bit PHP (null: auto-detect based on PHP_INT_MAX)
102
+
103
+ // public: Read buffer size in bytes
104
+ public $option_fread_buffer_size = 32768;
105
+
106
+ // Public variables
107
+ public $filename; // Filename of file being analysed.
108
+ public $fp; // Filepointer to file being analysed.
109
+ public $info; // Result array.
110
+
111
+ // Protected variables
112
+ protected $startup_error = '';
113
+ protected $startup_warning = '';
114
+ protected $memory_limit = 0;
115
+
116
+ const VERSION = '1.9.3-20111213';
117
+ const FREAD_BUFFER_SIZE = 32768;
118
+ var $tempdir = GETID3_TEMP_DIR;
119
+
120
+ const ATTACHMENTS_NONE = false;
121
+ const ATTACHMENTS_INLINE = true;
122
+
123
+ // public: constructor
124
+ public function __construct() {
125
+
126
+ // Check for PHP version
127
+ $required_php_version = '5.0.5';
128
+ if (version_compare(PHP_VERSION, $required_php_version, '<')) {
129
+ $this->startup_error .= 'getID3() requires PHP v'.$required_php_version.' or higher - you are running v'.PHP_VERSION;
130
+ return false;
131
+ }
132
+
133
+ // Check memory
134
+ $this->memory_limit = ini_get('memory_limit');
135
+ if (preg_match('#([0-9]+)M#i', $this->memory_limit, $matches)) {
136
+ // could be stored as "16M" rather than 16777216 for example
137
+ $this->memory_limit = $matches[1] * 1048576;
138
+ } elseif (preg_match('#([0-9]+)G#i', $this->memory_limit, $matches)) { // The 'G' modifier is available since PHP 5.1.0
139
+ // could be stored as "2G" rather than 2147483648 for example
140
+ $this->memory_limit = $matches[1] * 1073741824;
141
+ }
142
+ if ($this->memory_limit <= 0) {
143
+ // memory limits probably disabled
144
+ } elseif ($this->memory_limit <= 4194304) {
145
+ $this->startup_error .= 'PHP has less than 4MB available memory and will very likely run out. Increase memory_limit in php.ini';
146
+ } elseif ($this->memory_limit <= 12582912) {
147
+ $this->startup_warning .= 'PHP has less than 12MB available memory and might run out if all modules are loaded. Increase memory_limit in php.ini';
148
+ }
149
+
150
+ // Check safe_mode off
151
+ if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) {
152
+ $this->warning('WARNING: Safe mode is on, shorten support disabled, md5data/sha1data for ogg vorbis disabled, ogg vorbos/flac tag writing disabled.');
153
+ }
154
+
155
+ if (intval(ini_get('mbstring.func_overload')) > 0) {
156
+ $this->warning('WARNING: php.ini contains "mbstring.func_overload = '.ini_get('mbstring.func_overload').'", this may break things.');
157
+ }
158
+
159
+ // Check for magic_quotes_runtime
160
+ if (function_exists('get_magic_quotes_runtime')) {
161
+ if (get_magic_quotes_runtime()) {
162
+ return $this->startup_error('magic_quotes_runtime must be disabled before running getID3(). Surround getid3 block by set_magic_quotes_runtime(0) and set_magic_quotes_runtime(1).');
163
+ }
164
+ }
165
+
166
+ // Check for magic_quotes_gpc
167
+ if (function_exists('magic_quotes_gpc')) {
168
+ if (get_magic_quotes_gpc()) {
169
+ return $this->startup_error('magic_quotes_gpc must be disabled before running getID3(). Surround getid3 block by set_magic_quotes_gpc(0) and set_magic_quotes_gpc(1).');
170
+ }
171
+ }
172
+
173
+ // Load support library
174
+ if (!include_once(GETID3_INCLUDEPATH.'getid3.lib.php')) {
175
+ $this->startup_error .= 'getid3.lib.php is missing or corrupt';
176
+ }
177
+
178
+ if ($this->option_max_2gb_check === null) {
179
+ $this->option_max_2gb_check = (PHP_INT_MAX <= 2147483647);
180
+ }
181
+
182
+
183
+ // Needed for Windows only:
184
+ // Define locations of helper applications for Shorten, VorbisComment, MetaFLAC
185
+ // as well as other helper functions such as head, tail, md5sum, etc
186
+ // This path cannot contain spaces, but the below code will attempt to get the
187
+ // 8.3-equivalent path automatically
188
+ // IMPORTANT: This path must include the trailing slash
189
+ if (GETID3_OS_ISWINDOWS && !defined('GETID3_HELPERAPPSDIR')) {
190
+
191
+ $helperappsdir = GETID3_INCLUDEPATH.'..'.DIRECTORY_SEPARATOR.'helperapps'; // must not have any space in this path
192
+
193
+ if (!is_dir($helperappsdir)) {
194
+ $this->startup_warning .= '"'.$helperappsdir.'" cannot be defined as GETID3_HELPERAPPSDIR because it does not exist';
195
+ } elseif (strpos(realpath($helperappsdir), ' ') !== false) {
196
+ $DirPieces = explode(DIRECTORY_SEPARATOR, realpath($helperappsdir));
197
+ $path_so_far = array();
198
+ foreach ($DirPieces as $key => $value) {
199
+ if (strpos($value, ' ') !== false) {
200
+ if (!empty($path_so_far)) {
201
+ $commandline = 'dir /x '.escapeshellarg(implode(DIRECTORY_SEPARATOR, $path_so_far));
202
+ $dir_listing = `$commandline`;
203
+ $lines = explode("\n", $dir_listing);
204
+ foreach ($lines as $line) {
205
+ $line = trim($line);
206
+ if (preg_match('#^([0-9/]{10}) +([0-9:]{4,5}( [AP]M)?) +(<DIR>|[0-9,]+) +([^ ]{0,11}) +(.+)$#', $line, $matches)) {
207
+ list($dummy, $date, $time, $ampm, $filesize, $shortname, $filename) = $matches;
208
+ if ((strtoupper($filesize) == '<DIR>') && (strtolower($filename) == strtolower($value))) {
209
+ $value = $shortname;
210
+ }
211
+ }
212
+ }
213
+ } else {
214
+ $this->startup_warning .= 'GETID3_HELPERAPPSDIR must not have any spaces in it - use 8dot3 naming convention if neccesary. You can run "dir /x" from the commandline to see the correct 8.3-style names.';
215
+ }
216
+ }
217
+ $path_so_far[] = $value;
218
+ }
219
+ $helperappsdir = implode(DIRECTORY_SEPARATOR, $path_so_far);
220
+ }
221
+ define('GETID3_HELPERAPPSDIR', $helperappsdir.DIRECTORY_SEPARATOR);
222
+ }
223
+
224
+ return true;
225
+ }
226
+
227
+ public function version() {
228
+ return self::VERSION;
229
+ }
230
+
231
+ public function fread_buffer_size() {
232
+ return $this->option_fread_buffer_size;
233
+ }
234
+
235
+
236
+ // public: setOption
237
+ function setOption($optArray) {
238
+ if (!is_array($optArray) || empty($optArray)) {
239
+ return false;
240
+ }
241
+ foreach ($optArray as $opt => $val) {
242
+ if (isset($this->$opt) === false) {
243
+ continue;
244
+ }
245
+ $this->$opt = $val;
246
+ }
247
+ return true;
248
+ }
249
+
250
+
251
+ public function openfile($filename) {
252
+ try {
253
+ if (!empty($this->startup_error)) {
254
+ throw new getid3_exception($this->startup_error);
255
+ }
256
+ if (!empty($this->startup_warning)) {
257
+ $this->warning($this->startup_warning);
258
+ }
259
+
260
+ // init result array and set parameters
261
+ $this->filename = $filename;
262
+ $this->info = array();
263
+ $this->info['GETID3_VERSION'] = $this->version();
264
+ $this->info['php_memory_limit'] = $this->memory_limit;
265
+
266
+ // remote files not supported
267
+ if (preg_match('/^(ht|f)tp:\/\//', $filename)) {
268
+ throw new getid3_exception('Remote files are not supported - please copy the file locally first');
269
+ }
270
+
271
+ $filename = str_replace('/', DIRECTORY_SEPARATOR, $filename);
272
+ $filename = preg_replace('#(.+)'.preg_quote(DIRECTORY_SEPARATOR).'{2,}#U', '\1'.DIRECTORY_SEPARATOR, $filename);
273
+
274
+ // open local file
275
+ if (is_readable($filename) && is_file($filename) && ($this->fp = fopen($filename, 'rb'))) {
276
+ // great
277
+ } else {
278
+ throw new getid3_exception('Could not open "'.$filename.'" (does not exist, or is not a file)');
279
+ }
280
+
281
+ $this->info['filesize'] = filesize($filename);
282
+ // set redundant parameters - might be needed in some include file
283
+ $this->info['filename'] = basename($filename);
284
+ $this->info['filepath'] = str_replace('\\', '/', realpath(dirname($filename)));
285
+ $this->info['filenamepath'] = $this->info['filepath'].'/'.$this->info['filename'];
286
+
287
+
288
+ // option_max_2gb_check
289
+ if ($this->option_max_2gb_check) {
290
+ // PHP (32-bit all, and 64-bit Windows) doesn't support integers larger than 2^31 (~2GB)
291
+ // filesize() simply returns (filesize % (pow(2, 32)), no matter the actual filesize
292
+ // ftell() returns 0 if seeking to the end is beyond the range of unsigned integer
293
+ $fseek = fseek($this->fp, 0, SEEK_END);
294
+ if (($fseek < 0) || (($this->info['filesize'] != 0) && (ftell($this->fp) == 0)) ||
295
+ ($this->info['filesize'] < 0) ||
296
+ (ftell($this->fp) < 0)) {
297
+ $real_filesize = false;
298
+ if (GETID3_OS_ISWINDOWS) {
299
+ $commandline = 'dir /-C "'.str_replace('/', DIRECTORY_SEPARATOR, $filename).'"';
300
+ $dir_output = `$commandline`;
301
+ if (preg_match('#1 File\(s\)[ ]+([0-9]+) bytes#i', $dir_output, $matches)) {
302
+ $real_filesize = (float) $matches[1];
303
+ }
304
+ } else {
305
+ $commandline = 'ls -o -g -G --time-style=long-iso '.escapeshellarg($filename);
306
+ $dir_output = `$commandline`;
307
+ if (preg_match('#([0-9]+) ([0-9]{4}-[0-9]{2}\-[0-9]{2} [0-9]{2}:[0-9]{2}) '.str_replace('#', '\\#', preg_quote($filename)).'$#', $dir_output, $matches)) {
308
+ $real_filesize = (float) $matches[1];
309
+ }
310
+ }
311
+ if ($real_filesize === false) {
312
+ unset($this->info['filesize']);
313
+ fclose($this->fp);
314
+ throw new getid3_exception('Unable to determine actual filesize. File is most likely larger than '.round(PHP_INT_MAX / 1073741824).'GB and is not supported by PHP.');
315
+ } elseif (getid3_lib::intValueSupported($real_filesize)) {
316
+ unset($this->info['filesize']);
317
+ fclose($this->fp);
318
+ throw new getid3_exception('PHP seems to think the file is larger than '.round(PHP_INT_MAX / 1073741824).'GB, but filesystem reports it as '.number_format($real_filesize, 3).'GB, please report to info@getid3.org');
319
+ }
320
+ $this->info['filesize'] = $real_filesize;
321
+ $this->error('File is larger than '.round(PHP_INT_MAX / 1073741824).'GB (filesystem reports it as '.number_format($real_filesize, 3).'GB) and is not properly supported by PHP.');
322
+ }
323
+ }
324
+
325
+ // set more parameters
326
+ $this->info['avdataoffset'] = 0;
327
+ $this->info['avdataend'] = $this->info['filesize'];
328
+ $this->info['fileformat'] = ''; // filled in later
329
+ $this->info['audio']['dataformat'] = ''; // filled in later, unset if not used
330
+ $this->info['video']['dataformat'] = ''; // filled in later, unset if not used
331
+ $this->info['tags'] = array(); // filled in later, unset if not used
332
+ $this->info['error'] = array(); // filled in later, unset if not used
333
+ $this->info['warning'] = array(); // filled in later, unset if not used
334
+ $this->info['comments'] = array(); // filled in later, unset if not used
335
+ $this->info['encoding'] = $this->encoding; // required by id3v2 and iso modules - can be unset at the end if desired
336
+
337
+ return true;
338
+
339
+ } catch (Exception $e) {
340
+ $this->error($e->getMessage());
341
+ }
342
+ return false;
343
+ }
344
+
345
+ // public: analyze file
346
+ function analyze($filename) {
347
+ try {
348
+ if (!$this->openfile($filename)) {
349
+ return $this->info;
350
+ }
351
+
352
+ // Handle tags
353
+ foreach (array('id3v2'=>'id3v2', 'id3v1'=>'id3v1', 'apetag'=>'ape', 'lyrics3'=>'lyrics3') as $tag_name => $tag_key) {
354
+ $option_tag = 'option_tag_'.$tag_name;
355
+ if ($this->$option_tag) {
356
+ $this->include_module('tag.'.$tag_name);
357
+ try {
358
+ $tag_class = 'getid3_'.$tag_name;
359
+ $tag = new $tag_class($this);
360
+ $tag->Analyze();
361
+ }
362
+ catch (getid3_exception $e) {
363
+ throw $e;
364
+ }
365
+ }
366
+ }
367
+ if (isset($this->info['id3v2']['tag_offset_start'])) {
368
+ $this->info['avdataoffset'] = max($this->info['avdataoffset'], $this->info['id3v2']['tag_offset_end']);
369
+ }
370
+ foreach (array('id3v1'=>'id3v1', 'apetag'=>'ape', 'lyrics3'=>'lyrics3') as $tag_name => $tag_key) {
371
+ if (isset($this->info[$tag_key]['tag_offset_start'])) {
372
+ $this->info['avdataend'] = min($this->info['avdataend'], $this->info[$tag_key]['tag_offset_start']);
373
+ }
374
+ }
375
+
376
+ // ID3v2 detection (NOT parsing), even if ($this->option_tag_id3v2 == false) done to make fileformat easier
377
+ if (!$this->option_tag_id3v2) {
378
+ fseek($this->fp, 0, SEEK_SET);
379
+ $header = fread($this->fp, 10);
380
+ if ((substr($header, 0, 3) == 'ID3') && (strlen($header) == 10)) {
381
+ $this->info['id3v2']['header'] = true;
382
+ $this->info['id3v2']['majorversion'] = ord($header{3});
383
+ $this->info['id3v2']['minorversion'] = ord($header{4});
384
+ $this->info['avdataoffset'] += getid3_lib::BigEndian2Int(substr($header, 6, 4), 1) + 10; // length of ID3v2 tag in 10-byte header doesn't include 10-byte header length
385
+ }
386
+ }
387
+
388
+ // read 32 kb file data
389
+ fseek($this->fp, $this->info['avdataoffset'], SEEK_SET);
390
+ $formattest = fread($this->fp, 32774);
391
+
392
+ // determine format
393
+ $determined_format = $this->GetFileFormat($formattest, $filename);
394
+
395
+ // unable to determine file format
396
+ if (!$determined_format) {
397
+ fclose($this->fp);
398
+ return $this->error('unable to determine file format');
399
+ }
400
+
401
+ // check for illegal ID3 tags
402
+ if (isset($determined_format['fail_id3']) && (in_array('id3v1', $this->info['tags']) || in_array('id3v2', $this->info['tags']))) {
403
+ if ($determined_format['fail_id3'] === 'ERROR') {
404
+ fclose($this->fp);
405
+ return $this->error('ID3 tags not allowed on this file type.');
406
+ } elseif ($determined_format['fail_id3'] === 'WARNING') {
407
+ $this->warning('ID3 tags not allowed on this file type.');
408
+ }
409
+ }
410
+
411
+ // check for illegal APE tags
412
+ if (isset($determined_format['fail_ape']) && in_array('ape', $this->info['tags'])) {
413
+ if ($determined_format['fail_ape'] === 'ERROR') {
414
+ fclose($this->fp);
415
+ return $this->error('APE tags not allowed on this file type.');
416
+ } elseif ($determined_format['fail_ape'] === 'WARNING') {
417
+ $this->warning('APE tags not allowed on this file type.');
418
+ }
419
+ }
420
+
421
+ // set mime type
422
+ $this->info['mime_type'] = $determined_format['mime_type'];
423
+
424
+ // supported format signature pattern detected, but module deleted
425
+ if (!file_exists(GETID3_INCLUDEPATH.$determined_format['include'])) {
426
+ fclose($this->fp);
427
+ return $this->error('Format not supported, module "'.$determined_format['include'].'" was removed.');
428
+ }
429
+
430
+ // module requires iconv support
431
+ // Check encoding/iconv support
432
+ if (!empty($determined_format['iconv_req']) && !function_exists('iconv') && !in_array($this->encoding, array('ISO-8859-1', 'UTF-8', 'UTF-16LE', 'UTF-16BE', 'UTF-16'))) {
433
+ $errormessage = 'iconv() support is required for this module ('.$determined_format['include'].') for encodings other than ISO-8859-1, UTF-8, UTF-16LE, UTF16-BE, UTF-16. ';
434
+ if (GETID3_OS_ISWINDOWS) {
435
+ $errormessage .= 'PHP does not have iconv() support. Please enable php_iconv.dll in php.ini, and copy iconv.dll from c:/php/dlls to c:/windows/system32';
436
+ } else {
437
+ $errormessage .= 'PHP is not compiled with iconv() support. Please recompile with the --with-iconv switch';
438
+ }
439
+ return $this->error($errormessage);
440
+ }
441
+
442
+ // include module
443
+ include_once(GETID3_INCLUDEPATH.$determined_format['include']);
444
+
445
+ // instantiate module class
446
+ $class_name = 'getid3_'.$determined_format['module'];
447
+ if (!class_exists($class_name)) {
448
+ return $this->error('Format not supported, module "'.$determined_format['include'].'" is corrupt.');
449
+ }
450
+ //if (isset($determined_format['option'])) {
451
+ // //$class = new $class_name($this->fp, $this->info, $determined_format['option']);
452
+ //} else {
453
+ //$class = new $class_name($this->fp, $this->info);
454
+ $class = new $class_name($this);
455
+ //}
456
+
457
+ if (!empty($determined_format['set_inline_attachments'])) {
458
+ $class->inline_attachments = $this->option_save_attachments;
459
+ }
460
+ $class->Analyze();
461
+
462
+ unset($class);
463
+
464
+ // close file
465
+ fclose($this->fp);
466
+
467
+ // process all tags - copy to 'tags' and convert charsets
468
+ if ($this->option_tags_process) {
469
+ $this->HandleAllTags();
470
+ }
471
+
472
+ // perform more calculations
473
+ if ($this->option_extra_info) {
474
+ $this->ChannelsBitratePlaytimeCalculations();
475
+ $this->CalculateCompressionRatioVideo();
476
+ $this->CalculateCompressionRatioAudio();
477
+ $this->CalculateReplayGain();
478
+ $this->ProcessAudioStreams();
479
+ }
480
+
481
+ // get the MD5 sum of the audio/video portion of the file - without ID3/APE/Lyrics3/etc header/footer tags
482
+ if ($this->option_md5_data) {
483
+ // do not cald md5_data if md5_data_source is present - set by flac only - future MPC/SV8 too
484
+ if (!$this->option_md5_data_source || empty($this->info['md5_data_source'])) {
485
+ $this->getHashdata('md5');
486
+ }
487
+ }
488
+
489
+ // get the SHA1 sum of the audio/video portion of the file - without ID3/APE/Lyrics3/etc header/footer tags
490
+ if ($this->option_sha1_data) {
491
+ $this->getHashdata('sha1');
492
+ }
493
+
494
+ // remove undesired keys
495
+ $this->CleanUp();
496
+
497
+ } catch (Exception $e) {
498
+ $this->error('Caught exception: '.$e->getMessage());
499
+ }
500
+
501
+ // return info array
502
+ return $this->info;
503
+ }
504
+
505
+
506
+ // private: error handling
507
+ function error($message) {
508
+ $this->CleanUp();
509
+ if (!isset($this->info['error'])) {
510
+ $this->info['error'] = array();
511
+ }
512
+ $this->info['error'][] = $message;
513
+ return $this->info;
514
+ }
515
+
516
+
517
+ // private: warning handling
518
+ function warning($message) {
519
+ $this->info['warning'][] = $message;
520
+ return true;
521
+ }
522
+
523
+
524
+ // private: CleanUp
525
+ function CleanUp() {
526
+
527
+ // remove possible empty keys
528
+ $AVpossibleEmptyKeys = array('dataformat', 'bits_per_sample', 'encoder_options', 'streams', 'bitrate');
529
+ foreach ($AVpossibleEmptyKeys as $dummy => $key) {
530
+ if (empty($this->info['audio'][$key]) && isset($this->info['audio'][$key])) {
531
+ unset($this->info['audio'][$key]);
532
+ }
533
+ if (empty($this->info['video'][$key]) && isset($this->info['video'][$key])) {
534
+ unset($this->info['video'][$key]);
535
+ }
536
+ }
537
+
538
+ // remove empty root keys
539
+ if (!empty($this->info)) {
540
+ foreach ($this->info as $key => $value) {
541
+ if (empty($this->info[$key]) && ($this->info[$key] !== 0) && ($this->info[$key] !== '0')) {
542
+ unset($this->info[$key]);
543
+ }
544
+ }
545
+ }
546
+
547
+ // remove meaningless entries from unknown-format files
548
+ if (empty($this->info['fileformat'])) {
549
+ if (isset($this->info['avdataoffset'])) {
550
+ unset($this->info['avdataoffset']);
551
+ }
552
+ if (isset($this->info['avdataend'])) {
553
+ unset($this->info['avdataend']);
554
+ }
555
+ }
556
+
557
+ // remove possible duplicated identical entries
558
+ if (!empty($this->info['error'])) {
559
+ $this->info['error'] = array_values(array_unique($this->info['error']));
560
+ }
561
+ if (!empty($this->info['warning'])) {
562
+ $this->info['warning'] = array_values(array_unique($this->info['warning']));
563
+ }
564
+
565
+ // remove "global variable" type keys
566
+ unset($this->info['php_memory_limit']);
567
+
568
+ return true;
569
+ }
570
+
571
+
572
+ // return array containing information about all supported formats
573
+ function GetFileFormatArray() {
574
+ static $format_info = array();
575
+ if (empty($format_info)) {
576
+ $format_info = array(
577
+
578
+ // Audio formats
579
+
580
+ // AC-3 - audio - Dolby AC-3 / Dolby Digital
581
+ 'ac3' => array(
582
+ 'pattern' => '^\x0B\x77',
583
+ 'group' => 'audio',
584
+ 'module' => 'ac3',
585
+ 'mime_type' => 'audio/ac3',
586
+ ),
587
+
588
+ // AAC - audio - Advanced Audio Coding (AAC) - ADIF format
589
+ 'adif' => array(
590
+ 'pattern' => '^ADIF',
591
+ 'group' => 'audio',
592
+ 'module' => 'aac',
593
+ 'mime_type' => 'application/octet-stream',
594
+ 'fail_ape' => 'WARNING',
595
+ ),
596
+
597
+
598
+ // AA - audio - Audible Audiobook
599
+ 'adts' => array(
600
+ 'pattern' => '^.{4}\x57\x90\x75\x36',
601
+ 'group' => 'audio',
602
+ 'module' => 'aa',
603
+ 'mime_type' => 'audio/audible ',
604
+ ),
605
+
606
+ // AAC - audio - Advanced Audio Coding (AAC) - ADTS format (very similar to MP3)
607
+ 'adts' => array(
608
+ 'pattern' => '^\xFF[\xF0-\xF1\xF8-\xF9]',
609
+ 'group' => 'audio',
610
+ 'module' => 'aac',
611
+ 'mime_type' => 'application/octet-stream',
612
+ 'fail_ape' => 'WARNING',
613
+ ),
614
+
615
+
616
+ // AU - audio - NeXT/Sun AUdio (AU)
617
+ 'au' => array(
618
+ 'pattern' => '^\.snd',
619
+ 'group' => 'audio',
620
+ 'module' => 'au',
621
+ 'mime_type' => 'audio/basic',
622
+ ),
623
+
624
+ // AVR - audio - Audio Visual Research
625
+ 'avr' => array(
626
+ 'pattern' => '^2BIT',
627
+ 'group' => 'audio',
628
+ 'module' => 'avr',
629
+ 'mime_type' => 'application/octet-stream',
630
+ ),
631
+
632
+ // BONK - audio - Bonk v0.9+
633
+ 'bonk' => array(
634
+ 'pattern' => '^\x00(BONK|INFO|META| ID3)',
635
+ 'group' => 'audio',
636
+ 'module' => 'bonk',
637
+ 'mime_type' => 'audio/xmms-bonk',
638
+ ),
639
+
640
+ // DSS - audio - Digital Speech Standard
641
+ 'dss' => array(
642
+ 'pattern' => '^[\x02-\x03]dss',
643
+ 'group' => 'audio',
644
+ 'module' => 'dss',
645
+ 'mime_type' => 'application/octet-stream',
646
+ ),
647
+
648
+ // DTS - audio - Dolby Theatre System
649
+ 'dts' => array(
650
+ 'pattern' => '^\x7F\xFE\x80\x01',
651
+ 'group' => 'audio',
652
+ 'module' => 'dts',
653
+ 'mime_type' => 'audio/dts',
654
+ ),
655
+
656
+ // FLAC - audio - Free Lossless Audio Codec
657
+ 'flac' => array(
658
+ 'pattern' => '^fLaC',
659
+ 'group' => 'audio',
660
+ 'module' => 'flac',
661
+ 'mime_type' => 'audio/x-flac',
662
+ 'set_inline_attachments' => true,
663
+ ),
664
+
665
+ // LA - audio - Lossless Audio (LA)
666
+ 'la' => array(
667
+ 'pattern' => '^LA0[2-4]',
668
+ 'group' => 'audio',
669
+ 'module' => 'la',
670
+ 'mime_type' => 'application/octet-stream',
671
+ ),
672
+
673
+ // LPAC - audio - Lossless Predictive Audio Compression (LPAC)
674
+ 'lpac' => array(
675
+ 'pattern' => '^LPAC',
676
+ 'group' => 'audio',
677
+ 'module' => 'lpac',
678
+ 'mime_type' => 'application/octet-stream',
679
+ ),
680
+
681
+ // MIDI - audio - MIDI (Musical Instrument Digital Interface)
682
+ 'midi' => array(
683
+ 'pattern' => '^MThd',
684
+ 'group' => 'audio',
685
+ 'module' => 'midi',
686
+ 'mime_type' => 'audio/midi',
687
+ ),
688
+
689
+ // MAC - audio - Monkey's Audio Compressor
690
+ 'mac' => array(
691
+ 'pattern' => '^MAC ',
692
+ 'group' => 'audio',
693
+ 'module' => 'monkey',
694
+ 'mime_type' => 'application/octet-stream',
695
+ ),
696
+
697
+ // has been known to produce false matches in random files (e.g. JPEGs), leave out until more precise matching available
698
+ // // MOD - audio - MODule (assorted sub-formats)
699
+ // 'mod' => array(
700
+ // 'pattern' => '^.{1080}(M\\.K\\.|M!K!|FLT4|FLT8|[5-9]CHN|[1-3][0-9]CH)',
701
+ // 'group' => 'audio',
702
+ // 'module' => 'mod',
703
+ // 'option' => 'mod',
704
+ // 'mime_type' => 'audio/mod',
705
+ // ),
706
+
707
+ // MOD - audio - MODule (Impulse Tracker)
708
+ 'it' => array(
709
+ 'pattern' => '^IMPM',
710
+ 'group' => 'audio',
711
+ 'module' => 'mod',
712
+ //'option' => 'it',
713
+ 'mime_type' => 'audio/it',
714
+ ),
715
+
716
+ // MOD - audio - MODule (eXtended Module, various sub-formats)
717
+ 'xm' => array(
718
+ 'pattern' => '^Extended Module',
719
+ 'group' => 'audio',
720
+ 'module' => 'mod',
721
+ //'option' => 'xm',
722
+ 'mime_type' => 'audio/xm',
723
+ ),
724
+
725
+ // MOD - audio - MODule (ScreamTracker)
726
+ 's3m' => array(
727
+ 'pattern' => '^.{44}SCRM',
728
+ 'group' => 'audio',
729
+ 'module' => 'mod',
730
+ //'option' => 's3m',
731
+ 'mime_type' => 'audio/s3m',
732
+ ),
733
+
734
+ // MPC - audio - Musepack / MPEGplus
735
+ 'mpc' => array(
736
+ 'pattern' => '^(MPCK|MP\+|[\x00\x01\x10\x11\x40\x41\x50\x51\x80\x81\x90\x91\xC0\xC1\xD0\xD1][\x20-37][\x00\x20\x40\x60\x80\xA0\xC0\xE0])',
737
+ 'group' => 'audio',
738
+ 'module' => 'mpc',
739
+ 'mime_type' => 'audio/x-musepack',
740
+ ),
741
+
742
+ // MP3 - audio - MPEG-audio Layer 3 (very similar to AAC-ADTS)
743
+ 'mp3' => array(
744
+ 'pattern' => '^\xFF[\xE2-\xE7\xF2-\xF7\xFA-\xFF][\x00-\x0B\x10-\x1B\x20-\x2B\x30-\x3B\x40-\x4B\x50-\x5B\x60-\x6B\x70-\x7B\x80-\x8B\x90-\x9B\xA0-\xAB\xB0-\xBB\xC0-\xCB\xD0-\xDB\xE0-\xEB\xF0-\xFB]',
745
+ 'group' => 'audio',
746
+ 'module' => 'mp3',
747
+ 'mime_type' => 'audio/mpeg',
748
+ ),
749
+
750
+ // OFR - audio - OptimFROG
751
+ 'ofr' => array(
752
+ 'pattern' => '^(\*RIFF|OFR)',
753
+ 'group' => 'audio',
754
+ 'module' => 'optimfrog',
755
+ 'mime_type' => 'application/octet-stream',
756
+ ),
757
+
758
+ // RKAU - audio - RKive AUdio compressor
759
+ 'rkau' => array(
760
+ 'pattern' => '^RKA',
761
+ 'group' => 'audio',
762
+ 'module' => 'rkau',
763
+ 'mime_type' => 'application/octet-stream',
764
+ ),
765
+
766
+ // SHN - audio - Shorten
767
+ 'shn' => array(
768
+ 'pattern' => '^ajkg',
769
+ 'group' => 'audio',
770
+ 'module' => 'shorten',
771
+ 'mime_type' => 'audio/xmms-shn',
772
+ 'fail_id3' => 'ERROR',
773
+ 'fail_ape' => 'ERROR',
774
+ ),
775
+
776
+ // TTA - audio - TTA Lossless Audio Compressor (http://tta.corecodec.org)
777
+ 'tta' => array(
778
+ 'pattern' => '^TTA', // could also be '^TTA(\x01|\x02|\x03|2|1)'
779
+ 'group' => 'audio',
780
+ 'module' => 'tta',
781
+ 'mime_type' => 'application/octet-stream',
782
+ ),
783
+
784
+ // VOC - audio - Creative Voice (VOC)
785
+ 'voc' => array(
786
+ 'pattern' => '^Creative Voice File',
787
+ 'group' => 'audio',
788
+ 'module' => 'voc',
789
+ 'mime_type' => 'audio/voc',
790
+ ),
791
+
792
+ // VQF - audio - transform-domain weighted interleave Vector Quantization Format (VQF)
793
+ 'vqf' => array(
794
+ 'pattern' => '^TWIN',
795
+ 'group' => 'audio',
796
+ 'module' => 'vqf',
797
+ 'mime_type' => 'application/octet-stream',
798
+ ),
799
+
800
+ // WV - audio - WavPack (v4.0+)
801
+ 'wv' => array(
802
+ 'pattern' => '^wvpk',
803
+ 'group' => 'audio',
804
+ 'module' => 'wavpack',
805
+ 'mime_type' => 'application/octet-stream',
806
+ ),
807
+
808
+
809
+ // Audio-Video formats
810
+
811
+ // ASF - audio/video - Advanced Streaming Format, Windows Media Video, Windows Media Audio
812
+ 'asf' => array(
813
+ 'pattern' => '^\x30\x26\xB2\x75\x8E\x66\xCF\x11\xA6\xD9\x00\xAA\x00\x62\xCE\x6C',
814
+ 'group' => 'audio-video',
815
+ 'module' => 'asf',
816
+ 'mime_type' => 'video/x-ms-asf',
817
+ 'iconv_req' => false,
818
+ ),
819
+
820
+ // BINK - audio/video - Bink / Smacker
821
+ 'bink' => array(
822
+ 'pattern' => '^(BIK|SMK)',
823
+ 'group' => 'audio-video',
824
+ 'module' => 'bink',
825
+ 'mime_type' => 'application/octet-stream',
826
+ ),
827
+
828
+ // FLV - audio/video - FLash Video
829
+ 'flv' => array(
830
+ 'pattern' => '^FLV\x01',
831
+ 'group' => 'audio-video',
832
+ 'module' => 'flv',
833
+ 'mime_type' => 'video/x-flv',
834
+ ),
835
+
836
+ // MKAV - audio/video - Mastroka
837
+ 'matroska' => array(
838
+ 'pattern' => '^\x1A\x45\xDF\xA3',
839
+ 'group' => 'audio-video',
840
+ 'module' => 'matroska',
841
+ 'mime_type' => 'video/x-matroska', // may also be audio/x-matroska
842
+ 'set_inline_attachments' => true,
843
+ ),
844
+
845
+ // MPEG - audio/video - MPEG (Moving Pictures Experts Group)
846
+ 'mpeg' => array(
847
+ 'pattern' => '^\x00\x00\x01(\xBA|\xB3)',
848
+ 'group' => 'audio-video',
849
+ 'module' => 'mpeg',
850
+ 'mime_type' => 'video/mpeg',
851
+ ),
852
+
853
+ // NSV - audio/video - Nullsoft Streaming Video (NSV)
854
+ 'nsv' => array(
855
+ 'pattern' => '^NSV[sf]',
856
+ 'group' => 'audio-video',
857
+ 'module' => 'nsv',
858
+ 'mime_type' => 'application/octet-stream',
859
+ ),
860
+
861
+ // Ogg - audio/video - Ogg (Ogg-Vorbis, Ogg-FLAC, Speex, Ogg-Theora(*), Ogg-Tarkin(*))
862
+ 'ogg' => array(
863
+ 'pattern' => '^OggS',
864
+ 'group' => 'audio',
865
+ 'module' => 'ogg',
866
+ 'mime_type' => 'application/ogg',
867
+ 'fail_id3' => 'WARNING',
868
+ 'fail_ape' => 'WARNING',
869
+ 'set_inline_attachments' => true,
870
+ ),
871
+
872
+ // QT - audio/video - Quicktime
873
+ 'quicktime' => array(
874
+ 'pattern' => '^.{4}(cmov|free|ftyp|mdat|moov|pnot|skip|wide)',
875
+ 'group' => 'audio-video',
876
+ 'module' => 'quicktime',
877
+ 'mime_type' => 'video/quicktime',
878
+ ),
879
+
880
+ // RIFF - audio/video - Resource Interchange File Format (RIFF) / WAV / AVI / CD-audio / SDSS = renamed variant used by SmartSound QuickTracks (www.smartsound.com) / FORM = Audio Interchange File Format (AIFF)
881
+ 'riff' => array(
882
+ 'pattern' => '^(RIFF|SDSS|FORM)',
883
+ 'group' => 'audio-video',
884
+ 'module' => 'riff',
885
+ 'mime_type' => 'audio/x-wave',
886
+ 'fail_ape' => 'WARNING',
887
+ ),
888
+
889
+ // Real - audio/video - RealAudio, RealVideo
890
+ 'real' => array(
891
+ 'pattern' => '^(\\.RMF|\\.ra)',
892
+ 'group' => 'audio-video',
893
+ 'module' => 'real',
894
+ 'mime_type' => 'audio/x-realaudio',
895
+ ),
896
+
897
+ // SWF - audio/video - ShockWave Flash
898
+ 'swf' => array(
899
+ 'pattern' => '^(F|C)WS',
900
+ 'group' => 'audio-video',
901
+ 'module' => 'swf',
902
+ 'mime_type' => 'application/x-shockwave-flash',
903
+ ),
904
+
905
+
906
+ // Still-Image formats
907
+
908
+ // BMP - still image - Bitmap (Windows, OS/2; uncompressed, RLE8, RLE4)
909
+ 'bmp' => array(
910
+ 'pattern' => '^BM',
911
+ 'group' => 'graphic',
912
+ 'module' => 'bmp',
913
+ 'mime_type' => 'image/bmp',
914
+ 'fail_id3' => 'ERROR',
915
+ 'fail_ape' => 'ERROR',
916
+ ),
917
+
918
+ // GIF - still image - Graphics Interchange Format
919
+ 'gif' => array(
920
+ 'pattern' => '^GIF',
921
+ 'group' => 'graphic',
922
+ 'module' => 'gif',
923
+ 'mime_type' => 'image/gif',
924
+ 'fail_id3' => 'ERROR',
925
+ 'fail_ape' => 'ERROR',
926
+ ),
927
+
928
+ // JPEG - still image - Joint Photographic Experts Group (JPEG)
929
+ 'jpg' => array(
930
+ 'pattern' => '^\xFF\xD8\xFF',
931
+ 'group' => 'graphic',
932
+ 'module' => 'jpg',
933
+ 'mime_type' => 'image/jpeg',
934
+ 'fail_id3' => 'ERROR',
935
+ 'fail_ape' => 'ERROR',
936
+ ),
937
+
938
+ // PCD - still image - Kodak Photo CD
939
+ 'pcd' => array(
940
+ 'pattern' => '^.{2048}PCD_IPI\x00',
941
+ 'group' => 'graphic',
942
+ 'module' => 'pcd',
943
+ 'mime_type' => 'image/x-photo-cd',
944
+ 'fail_id3' => 'ERROR',
945
+ 'fail_ape' => 'ERROR',
946
+ ),
947
+
948
+
949
+ // PNG - still image - Portable Network Graphics (PNG)
950
+ 'png' => array(
951
+ 'pattern' => '^\x89\x50\x4E\x47\x0D\x0A\x1A\x0A',
952
+ 'group' => 'graphic',
953
+ 'module' => 'png',
954
+ 'mime_type' => 'image/png',
955
+ 'fail_id3' => 'ERROR',
956
+ 'fail_ape' => 'ERROR',
957
+ ),
958
+
959
+
960
+ // SVG - still image - Scalable Vector Graphics (SVG)
961
+ 'svg' => array(
962
+ 'pattern' => '(<!DOCTYPE svg PUBLIC |xmlns="http:\/\/www\.w3\.org\/2000\/svg")',
963
+ 'group' => 'graphic',
964
+ 'module' => 'svg',
965
+ 'mime_type' => 'image/svg+xml',
966
+ 'fail_id3' => 'ERROR',
967
+ 'fail_ape' => 'ERROR',
968
+ ),
969
+
970
+
971
+ // TIFF - still image - Tagged Information File Format (TIFF)
972
+ 'tiff' => array(
973
+ 'pattern' => '^(II\x2A\x00|MM\x00\x2A)',
974
+ 'group' => 'graphic',
975
+ 'module' => 'tiff',
976
+ 'mime_type' => 'image/tiff',
977
+ 'fail_id3' => 'ERROR',
978
+ 'fail_ape' => 'ERROR',
979
+ ),
980
+
981
+
982
+ // EFAX - still image - eFax (TIFF derivative)
983
+ 'bmp' => array(
984
+ 'pattern' => '^\xDC\xFE',
985
+ 'group' => 'graphic',
986
+ 'module' => 'efax',
987
+ 'mime_type' => 'image/efax',
988
+ 'fail_id3' => 'ERROR',
989
+ 'fail_ape' => 'ERROR',
990
+ ),
991
+
992
+
993
+ // Data formats
994
+
995
+ // ISO - data - International Standards Organization (ISO) CD-ROM Image
996
+ 'iso' => array(
997
+ 'pattern' => '^.{32769}CD001',
998
+ 'group' => 'misc',
999
+ 'module' => 'iso',
1000
+ 'mime_type' => 'application/octet-stream',
1001
+ 'fail_id3' => 'ERROR',
1002
+ 'fail_ape' => 'ERROR',
1003
+ 'iconv_req' => false,
1004
+ ),
1005
+
1006
+ // RAR - data - RAR compressed data
1007
+ 'rar' => array(
1008
+ 'pattern' => '^Rar\!',
1009
+ 'group' => 'archive',
1010
+ 'module' => 'rar',
1011
+ 'mime_type' => 'application/octet-stream',
1012
+ 'fail_id3' => 'ERROR',
1013
+ 'fail_ape' => 'ERROR',
1014
+ ),
1015
+
1016
+ // SZIP - audio/data - SZIP compressed data
1017
+ 'szip' => array(
1018
+ 'pattern' => '^SZ\x0A\x04',
1019
+ 'group' => 'archive',
1020
+ 'module' => 'szip',
1021
+ 'mime_type' => 'application/octet-stream',
1022
+ 'fail_id3' => 'ERROR',
1023
+ 'fail_ape' => 'ERROR',
1024
+ ),
1025
+
1026
+ // TAR - data - TAR compressed data
1027
+ 'tar' => array(
1028
+ 'pattern' => '^.{100}[0-9\x20]{7}\x00[0-9\x20]{7}\x00[0-9\x20]{7}\x00[0-9\x20\x00]{12}[0-9\x20\x00]{12}',
1029
+ 'group' => 'archive',
1030
+ 'module' => 'tar',
1031
+ 'mime_type' => 'application/x-tar',
1032
+ 'fail_id3' => 'ERROR',
1033
+ 'fail_ape' => 'ERROR',
1034
+ ),
1035
+
1036
+ // GZIP - data - GZIP compressed data
1037
+ 'gz' => array(
1038
+ 'pattern' => '^\x1F\x8B\x08',
1039
+ 'group' => 'archive',
1040
+ 'module' => 'gzip',
1041
+ 'mime_type' => 'application/x-gzip',
1042
+ 'fail_id3' => 'ERROR',
1043
+ 'fail_ape' => 'ERROR',
1044
+ ),
1045
+
1046
+ // ZIP - data - ZIP compressed data
1047
+ 'zip' => array(
1048
+ 'pattern' => '^PK\x03\x04',
1049
+ 'group' => 'archive',
1050
+ 'module' => 'zip',
1051
+ 'mime_type' => 'application/zip',
1052
+ 'fail_id3' => 'ERROR',
1053
+ 'fail_ape' => 'ERROR',
1054
+ ),
1055
+
1056
+
1057
+ // Misc other formats
1058
+
1059
+ // PAR2 - data - Parity Volume Set Specification 2.0
1060
+ 'par2' => array (
1061
+ 'pattern' => '^PAR2\x00PKT',
1062
+ 'group' => 'misc',
1063
+ 'module' => 'par2',
1064
+ 'mime_type' => 'application/octet-stream',
1065
+ 'fail_id3' => 'ERROR',
1066
+ 'fail_ape' => 'ERROR',
1067
+ ),
1068
+
1069
+ // PDF - data - Portable Document Format
1070
+ 'pdf' => array(
1071
+ 'pattern' => '^\x25PDF',
1072
+ 'group' => 'misc',
1073
+ 'module' => 'pdf',
1074
+ 'mime_type' => 'application/pdf',
1075
+ 'fail_id3' => 'ERROR',
1076
+ 'fail_ape' => 'ERROR',
1077
+ ),
1078
+
1079
+ // MSOFFICE - data - ZIP compressed data
1080
+ 'msoffice' => array(
1081
+ 'pattern' => '^\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1', // D0CF11E == DOCFILE == Microsoft Office Document
1082
+ 'group' => 'misc',
1083
+ 'module' => 'msoffice',
1084
+ 'mime_type' => 'application/octet-stream',
1085
+ 'fail_id3' => 'ERROR',
1086
+ 'fail_ape' => 'ERROR',
1087
+ ),
1088
+
1089
+ // CUE - data - CUEsheet (index to single-file disc images)
1090
+ 'cue' => array(
1091
+ 'pattern' => '', // empty pattern means cannot be automatically detected, will fall through all other formats and match based on filename and very basic file contents
1092
+ 'group' => 'misc',
1093
+ 'module' => 'cue',
1094
+ 'mime_type' => 'application/octet-stream',
1095
+ ),
1096
+
1097
+ );
1098
+ }
1099
+
1100
+ return $format_info;
1101
+ }
1102
+
1103
+
1104
+
1105
+ function GetFileFormat(&$filedata, $filename='') {
1106
+ // this function will determine the format of a file based on usually
1107
+ // the first 2-4 bytes of the file (8 bytes for PNG, 16 bytes for JPG,
1108
+ // and in the case of ISO CD image, 6 bytes offset 32kb from the start
1109
+ // of the file).
1110
+
1111
+ // Identify file format - loop through $format_info and detect with reg expr
1112
+ foreach ($this->GetFileFormatArray() as $format_name => $info) {
1113
+ // The /s switch on preg_match() forces preg_match() NOT to treat
1114
+ // newline (0x0A) characters as special chars but do a binary match
1115
+ if (!empty($info['pattern']) && preg_match('#'.$info['pattern'].'#s', $filedata)) {
1116
+ $info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php';
1117
+ return $info;
1118
+ }
1119
+ }
1120
+
1121
+
1122
+ if (preg_match('#\.mp[123a]$#i', $filename)) {
1123
+ // Too many mp3 encoders on the market put gabage in front of mpeg files
1124
+ // use assume format on these if format detection failed
1125
+ $GetFileFormatArray = $this->GetFileFormatArray();
1126
+ $info = $GetFileFormatArray['mp3'];
1127
+ $info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php';
1128
+ return $info;
1129
+ } elseif (preg_match('/\.cue$/i', $filename) && preg_match('#FILE "[^"]+" (BINARY|MOTOROLA|AIFF|WAVE|MP3)#', $filedata)) {
1130
+ // there's not really a useful consistent "magic" at the beginning of .cue files to identify them
1131
+ // so until I think of something better, just go by filename if all other format checks fail
1132
+ // and verify there's at least one instance of "TRACK xx AUDIO" in the file
1133
+ $GetFileFormatArray = $this->GetFileFormatArray();
1134
+ $info = $GetFileFormatArray['cue'];
1135
+ $info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php';
1136
+ return $info;
1137
+ }
1138
+
1139
+ return false;
1140
+ }
1141
+
1142
+
1143
+ // converts array to $encoding charset from $this->encoding
1144
+ function CharConvert(&$array, $encoding) {
1145
+
1146
+ // identical encoding - end here
1147
+ if ($encoding == $this->encoding) {
1148
+ return;
1149
+ }
1150
+
1151
+ // loop thru array
1152
+ foreach ($array as $key => $value) {
1153
+
1154
+ // go recursive
1155
+ if (is_array($value)) {
1156
+ $this->CharConvert($array[$key], $encoding);
1157
+ }
1158
+
1159
+ // convert string
1160
+ elseif (is_string($value)) {
1161
+ $array[$key] = trim(getid3_lib::iconv_fallback($encoding, $this->encoding, $value));
1162
+ }
1163
+ }
1164
+ }
1165
+
1166
+
1167
+ function HandleAllTags() {
1168
+
1169
+ // key name => array (tag name, character encoding)
1170
+ static $tags;
1171
+ if (empty($tags)) {
1172
+ $tags = array(
1173
+ 'asf' => array('asf' , 'UTF-16LE'),
1174
+ 'midi' => array('midi' , 'ISO-8859-1'),
1175
+ 'nsv' => array('nsv' , 'ISO-8859-1'),
1176
+ 'ogg' => array('vorbiscomment' , 'UTF-8'),
1177
+ 'png' => array('png' , 'UTF-8'),
1178
+ 'tiff' => array('tiff' , 'ISO-8859-1'),
1179
+ 'quicktime' => array('quicktime' , 'UTF-8'),
1180
+ 'real' => array('real' , 'ISO-8859-1'),
1181
+ 'vqf' => array('vqf' , 'ISO-8859-1'),
1182
+ 'zip' => array('zip' , 'ISO-8859-1'),
1183
+ 'riff' => array('riff' , 'ISO-8859-1'),
1184
+ 'lyrics3' => array('lyrics3' , 'ISO-8859-1'),
1185
+ 'id3v1' => array('id3v1' , $this->encoding_id3v1),
1186
+ 'id3v2' => array('id3v2' , 'UTF-8'), // not according to the specs (every frame can have a different encoding), but getID3() force-converts all encodings to UTF-8
1187
+ 'ape' => array('ape' , 'UTF-8'),
1188
+ 'cue' => array('cue' , 'ISO-8859-1'),
1189
+ 'matroska' => array('matroska' , 'UTF-8'),
1190
+ );
1191
+ }
1192
+
1193
+ // loop through comments array
1194
+ foreach ($tags as $comment_name => $tagname_encoding_array) {
1195
+ list($tag_name, $encoding) = $tagname_encoding_array;
1196
+
1197
+ // fill in default encoding type if not already present
1198
+ if (isset($this->info[$comment_name]) && !isset($this->info[$comment_name]['encoding'])) {
1199
+ $this->info[$comment_name]['encoding'] = $encoding;
1200
+ }
1201
+
1202
+ // copy comments if key name set
1203
+ if (!empty($this->info[$comment_name]['comments'])) {
1204
+
1205
+ foreach ($this->info[$comment_name]['comments'] as $tag_key => $valuearray) {
1206
+ foreach ($valuearray as $key => $value) {
1207
+ if (is_string($value)) {
1208
+ $value = trim($value, " \r\n\t"); // do not trim nulls from $value!! Unicode characters will get mangled if trailing nulls are removed!
1209
+ }
1210
+ if ($value) {
1211
+ $this->info['tags'][trim($tag_name)][trim($tag_key)][] = $value;
1212
+ }
1213
+ }
1214
+ }
1215
+
1216
+ if (!isset($this->info['tags'][$tag_name])) {
1217
+ // comments are set but contain nothing but empty strings, so skip
1218
+ continue;
1219
+ }
1220
+
1221
+ if ($this->option_tags_html) {
1222
+ foreach ($this->info['tags'][$tag_name] as $tag_key => $valuearray) {
1223
+ foreach ($valuearray as $key => $value) {
1224
+ if (is_string($value)) {
1225
+ //$this->info['tags_html'][$tag_name][$tag_key][$key] = getid3_lib::MultiByteCharString2HTML($value, $encoding);
1226
+ $this->info['tags_html'][$tag_name][$tag_key][$key] = str_replace('&#0;', '', trim(getid3_lib::MultiByteCharString2HTML($value, $encoding)));
1227
+ } else {
1228
+ $this->info['tags_html'][$tag_name][$tag_key][$key] = $value;
1229
+ }
1230
+ }
1231
+ }
1232
+ }
1233
+
1234
+ $this->CharConvert($this->info['tags'][$tag_name], $encoding); // only copy gets converted!
1235
+ }
1236
+
1237
+ }
1238
+
1239
+ // pictures can take up a lot of space, and we don't need multiple copies of them
1240
+ // let there be a single copy in [comments][picture], and not elsewhere
1241
+ if (!empty($this->info['tags'])) {
1242
+ $unset_keys = array('tags', 'tags_html');
1243
+ foreach ($this->info['tags'] as $tagtype => $tagarray) {
1244
+ foreach ($tagarray as $tagname => $tagdata) {
1245
+ if ($tagname == 'picture') {
1246
+ foreach ($tagdata as $key => $tagarray) {
1247
+ $this->info['comments']['picture'][] = $tagarray;
1248
+ if (isset($tagarray['data']) && isset($tagarray['image_mime'])) {
1249
+ if (isset($this->info['tags'][$tagtype][$tagname][$key])) {
1250
+ unset($this->info['tags'][$tagtype][$tagname][$key]);
1251
+ }
1252
+ if (isset($this->info['tags_html'][$tagtype][$tagname][$key])) {
1253
+ unset($this->info['tags_html'][$tagtype][$tagname][$key]);
1254
+ }
1255
+ }
1256
+ }
1257
+ }
1258
+ }
1259
+ foreach ($unset_keys as $unset_key) {
1260
+ // remove possible empty keys from (e.g. [tags][id3v2][picture])
1261
+ if (empty($this->info[$unset_key][$tagtype]['picture'])) {
1262
+ unset($this->info[$unset_key][$tagtype]['picture']);
1263
+ }
1264
+ if (empty($this->info[$unset_key][$tagtype])) {
1265
+ unset($this->info[$unset_key][$tagtype]);
1266
+ }
1267
+ if (empty($this->info[$unset_key])) {
1268
+ unset($this->info[$unset_key]);
1269
+ }
1270
+ }
1271
+ // remove duplicate copy of picture data from (e.g. [id3v2][comments][picture])
1272
+ if (isset($this->info[$tagtype]['comments']['picture'])) {
1273
+ unset($this->info[$tagtype]['comments']['picture']);
1274
+ }
1275
+ if (empty($this->info[$tagtype]['comments'])) {
1276
+ unset($this->info[$tagtype]['comments']);
1277
+ }
1278
+ if (empty($this->info[$tagtype])) {
1279
+ unset($this->info[$tagtype]);
1280
+ }
1281
+ }
1282
+ }
1283
+ return true;
1284
+ }
1285
+
1286
+
1287
+ function getHashdata($algorithm) {
1288
+ switch ($algorithm) {
1289
+ case 'md5':
1290
+ case 'sha1':
1291
+ break;
1292
+
1293
+ default:
1294
+ return $this->error('bad algorithm "'.$algorithm.'" in getHashdata()');
1295
+ break;
1296
+ }
1297
+
1298
+ if (!empty($this->info['fileformat']) && !empty($this->info['dataformat']) && ($this->info['fileformat'] == 'ogg') && ($this->info['audio']['dataformat'] == 'vorbis')) {
1299
+
1300
+ // We cannot get an identical md5_data value for Ogg files where the comments
1301
+ // span more than 1 Ogg page (compared to the same audio data with smaller
1302
+ // comments) using the normal getID3() method of MD5'ing the data between the
1303
+ // end of the comments and the end of the file (minus any trailing tags),
1304
+ // because the page sequence numbers of the pages that the audio data is on
1305
+ // do not match. Under normal circumstances, where comments are smaller than
1306
+ // the nominal 4-8kB page size, then this is not a problem, but if there are
1307
+ // very large comments, the only way around it is to strip off the comment
1308
+ // tags with vorbiscomment and MD5 that file.
1309
+ // This procedure must be applied to ALL Ogg files, not just the ones with
1310
+ // comments larger than 1 page, because the below method simply MD5's the
1311
+ // whole file with the comments stripped, not just the portion after the
1312
+ // comments block (which is the standard getID3() method.
1313
+
1314
+ // The above-mentioned problem of comments spanning multiple pages and changing
1315
+ // page sequence numbers likely happens for OggSpeex and OggFLAC as well, but
1316
+ // currently vorbiscomment only works on OggVorbis files.
1317
+
1318
+ if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) {
1319
+
1320
+ $this->warning('Failed making system call to vorbiscomment.exe - '.$algorithm.'_data is incorrect - error returned: PHP running in Safe Mode (backtick operator not available)');
1321
+ $this->info[$algorithm.'_data'] = false;
1322
+
1323
+ } else {
1324
+
1325
+ // Prevent user from aborting script
1326
+ $old_abort = ignore_user_abort(true);
1327
+
1328
+ // Create empty file
1329
+ $empty = tempnam(GETID3_TEMP_DIR, 'getID3');
1330
+ touch($empty);
1331
+
1332
+ // Use vorbiscomment to make temp file without comments
1333
+ $temp = tempnam(GETID3_TEMP_DIR, 'getID3');
1334
+ $file = $this->info['filenamepath'];
1335
+
1336
+ if (GETID3_OS_ISWINDOWS) {
1337
+
1338
+ if (file_exists(GETID3_HELPERAPPSDIR.'vorbiscomment.exe')) {
1339
+
1340
+ $commandline = '"'.GETID3_HELPERAPPSDIR.'vorbiscomment.exe" -w -c "'.$empty.'" "'.$file.'" "'.$temp.'"';
1341
+ $VorbisCommentError = `$commandline`;
1342
+
1343
+ } else {
1344
+
1345
+ $VorbisCommentError = 'vorbiscomment.exe not found in '.GETID3_HELPERAPPSDIR;
1346
+
1347
+ }
1348
+
1349
+ } else {
1350
+
1351
+ $commandline = 'vorbiscomment -w -c "'.$empty.'" "'.$file.'" "'.$temp.'" 2>&1';
1352
+ $commandline = 'vorbiscomment -w -c '.escapeshellarg($empty).' '.escapeshellarg($file).' '.escapeshellarg($temp).' 2>&1';
1353
+ $VorbisCommentError = `$commandline`;
1354
+
1355
+ }
1356
+
1357
+ if (!empty($VorbisCommentError)) {
1358
+
1359
+ $this->info['warning'][] = 'Failed making system call to vorbiscomment(.exe) - '.$algorithm.'_data will be incorrect. If vorbiscomment is unavailable, please download from http://www.vorbis.com/download.psp and put in the getID3() directory. Error returned: '.$VorbisCommentError;
1360
+ $this->info[$algorithm.'_data'] = false;
1361
+
1362
+ } else {
1363
+
1364
+ // Get hash of newly created file
1365
+ switch ($algorithm) {
1366
+ case 'md5':
1367
+ $this->info[$algorithm.'_data'] = md5_file($temp);
1368
+ break;
1369
+
1370
+ case 'sha1':
1371
+ $this->info[$algorithm.'_data'] = sha1_file($temp);
1372
+ break;
1373
+ }
1374
+ }
1375
+
1376
+ // Clean up
1377
+ unlink($empty);
1378
+ unlink($temp);
1379
+
1380
+ // Reset abort setting
1381
+ ignore_user_abort($old_abort);
1382
+
1383
+ }
1384
+
1385
+ } else {
1386
+
1387
+ if (!empty($this->info['avdataoffset']) || (isset($this->info['avdataend']) && ($this->info['avdataend'] < $this->info['filesize']))) {
1388
+
1389
+ // get hash from part of file
1390
+ $this->info[$algorithm.'_data'] = getid3_lib::hash_data($this->info['filenamepath'], $this->info['avdataoffset'], $this->info['avdataend'], $algorithm);
1391
+
1392
+ } else {
1393
+
1394
+ // get hash from whole file
1395
+ switch ($algorithm) {
1396
+ case 'md5':
1397
+ $this->info[$algorithm.'_data'] = md5_file($this->info['filenamepath']);
1398
+ break;
1399
+
1400
+ case 'sha1':
1401
+ $this->info[$algorithm.'_data'] = sha1_file($this->info['filenamepath']);
1402
+ break;
1403
+ }
1404
+ }
1405
+
1406
+ }
1407
+ return true;
1408
+ }
1409
+
1410
+
1411
+ function ChannelsBitratePlaytimeCalculations() {
1412
+
1413
+ // set channelmode on audio
1414
+ if (!empty($this->info['audio']['channelmode']) || !isset($this->info['audio']['channels'])) {
1415
+ // ignore
1416
+ } elseif ($this->info['audio']['channels'] == 1) {
1417
+ $this->info['audio']['channelmode'] = 'mono';
1418
+ } elseif ($this->info['audio']['channels'] == 2) {
1419
+ $this->info['audio']['channelmode'] = 'stereo';
1420
+ }
1421
+
1422
+ // Calculate combined bitrate - audio + video
1423
+ $CombinedBitrate = 0;
1424
+ $CombinedBitrate += (isset($this->info['audio']['bitrate']) ? $this->info['audio']['bitrate'] : 0);
1425
+ $CombinedBitrate += (isset($this->info['video']['bitrate']) ? $this->info['video']['bitrate'] : 0);
1426
+ if (($CombinedBitrate > 0) && empty($this->info['bitrate'])) {
1427
+ $this->info['bitrate'] = $CombinedBitrate;
1428
+ }
1429
+ //if ((isset($this->info['video']) && !isset($this->info['video']['bitrate'])) || (isset($this->info['audio']) && !isset($this->info['audio']['bitrate']))) {
1430
+ // // for example, VBR MPEG video files cannot determine video bitrate:
1431
+ // // should not set overall bitrate and playtime from audio bitrate only
1432
+ // unset($this->info['bitrate']);
1433
+ //}
1434
+
1435
+ // video bitrate undetermined, but calculable
1436
+ if (isset($this->info['video']['dataformat']) && $this->info['video']['dataformat'] && (!isset($this->info['video']['bitrate']) || ($this->info['video']['bitrate'] == 0))) {
1437
+ // if video bitrate not set
1438
+ if (isset($this->info['audio']['bitrate']) && ($this->info['audio']['bitrate'] > 0) && ($this->info['audio']['bitrate'] == $this->info['bitrate'])) {
1439
+ // AND if audio bitrate is set to same as overall bitrate
1440
+ if (isset($this->info['playtime_seconds']) && ($this->info['playtime_seconds'] > 0)) {
1441
+ // AND if playtime is set
1442
+ if (isset($this->info['avdataend']) && isset($this->info['avdataoffset'])) {
1443
+ // AND if AV data offset start/end is known
1444
+ // THEN we can calculate the video bitrate
1445
+ $this->info['bitrate'] = round((($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['playtime_seconds']);
1446
+ $this->info['video']['bitrate'] = $this->info['bitrate'] - $this->info['audio']['bitrate'];
1447
+ }
1448
+ }
1449
+ }
1450
+ }
1451
+
1452
+ if ((!isset($this->info['playtime_seconds']) || ($this->info['playtime_seconds'] <= 0)) && !empty($this->info['bitrate'])) {
1453
+ $this->info['playtime_seconds'] = (($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['bitrate'];
1454
+ }
1455
+
1456
+ if (!isset($this->info['bitrate']) && !empty($this->info['playtime_seconds'])) {
1457
+ $this->info['bitrate'] = (($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['playtime_seconds'];
1458
+ }
1459
+ if (isset($this->info['bitrate']) && empty($this->info['audio']['bitrate']) && empty($this->info['video']['bitrate'])) {
1460
+ if (isset($this->info['audio']['dataformat']) && empty($this->info['video']['resolution_x'])) {
1461
+ // audio only
1462
+ $this->info['audio']['bitrate'] = $this->info['bitrate'];
1463
+ } elseif (isset($this->info['video']['resolution_x']) && empty($this->info['audio']['dataformat'])) {
1464
+ // video only
1465
+ $this->info['video']['bitrate'] = $this->info['bitrate'];
1466
+ }
1467
+ }
1468
+
1469
+ // Set playtime string
1470
+ if (!empty($this->info['playtime_seconds']) && empty($this->info['playtime_string'])) {
1471
+ $this->info['playtime_string'] = getid3_lib::PlaytimeString($this->info['playtime_seconds']);
1472
+ }
1473
+ }
1474
+
1475
+
1476
+ function CalculateCompressionRatioVideo() {
1477
+ if (empty($this->info['video'])) {
1478
+ return false;
1479
+ }
1480
+ if (empty($this->info['video']['resolution_x']) || empty($this->info['video']['resolution_y'])) {
1481
+ return false;
1482
+ }
1483
+ if (empty($this->info['video']['bits_per_sample'])) {
1484
+ return false;
1485
+ }
1486
+
1487
+ switch ($this->info['video']['dataformat']) {
1488
+ case 'bmp':
1489
+ case 'gif':
1490
+ case 'jpeg':
1491
+ case 'jpg':
1492
+ case 'png':
1493
+ case 'tiff':
1494
+ $FrameRate = 1;
1495
+ $PlaytimeSeconds = 1;
1496
+ $BitrateCompressed = $this->info['filesize'] * 8;
1497
+ break;
1498
+
1499
+ default:
1500
+ if (!empty($this->info['video']['frame_rate'])) {
1501
+ $FrameRate = $this->info['video']['frame_rate'];
1502
+ } else {
1503
+ return false;
1504
+ }
1505
+ if (!empty($this->info['playtime_seconds'])) {
1506
+ $PlaytimeSeconds = $this->info['playtime_seconds'];
1507
+ } else {
1508
+ return false;
1509
+ }
1510
+ if (!empty($this->info['video']['bitrate'])) {
1511
+ $BitrateCompressed = $this->info['video']['bitrate'];
1512
+ } else {
1513
+ return false;
1514
+ }
1515
+ break;
1516
+ }
1517
+ $BitrateUncompressed = $this->info['video']['resolution_x'] * $this->info['video']['resolution_y'] * $this->info['video']['bits_per_sample'] * $FrameRate;
1518
+
1519
+ $this->info['video']['compression_ratio'] = $BitrateCompressed / $BitrateUncompressed;
1520
+ return true;
1521
+ }
1522
+
1523
+
1524
+ function CalculateCompressionRatioAudio() {
1525
+ if (empty($this->info['audio']['bitrate']) || empty($this->info['audio']['channels']) || empty($this->info['audio']['sample_rate'])) {
1526
+ return false;
1527
+ }
1528
+ $this->info['audio']['compression_ratio'] = $this->info['audio']['bitrate'] / ($this->info['audio']['channels'] * $this->info['audio']['sample_rate'] * (!empty($this->info['audio']['bits_per_sample']) ? $this->info['audio']['bits_per_sample'] : 16));
1529
+
1530
+ if (!empty($this->info['audio']['streams'])) {
1531
+ foreach ($this->info['audio']['streams'] as $streamnumber => $streamdata) {
1532
+ if (!empty($streamdata['bitrate']) && !empty($streamdata['channels']) && !empty($streamdata['sample_rate'])) {
1533
+ $this->info['audio']['streams'][$streamnumber]['compression_ratio'] = $streamdata['bitrate'] / ($streamdata['channels'] * $streamdata['sample_rate'] * (!empty($streamdata['bits_per_sample']) ? $streamdata['bits_per_sample'] : 16));
1534
+ }
1535
+ }
1536
+ }
1537
+ return true;
1538
+ }
1539
+
1540
+
1541
+ function CalculateReplayGain() {
1542
+ if (isset($this->info['replay_gain'])) {
1543
+ if (!isset($this->info['replay_gain']['reference_volume'])) {
1544
+ $this->info['replay_gain']['reference_volume'] = (double) 89.0;
1545
+ }
1546
+ if (isset($this->info['replay_gain']['track']['adjustment'])) {
1547
+ $this->info['replay_gain']['track']['volume'] = $this->info['replay_gain']['reference_volume'] - $this->info['replay_gain']['track']['adjustment'];
1548
+ }
1549
+ if (isset($this->info['replay_gain']['album']['adjustment'])) {
1550
+ $this->info['replay_gain']['album']['volume'] = $this->info['replay_gain']['reference_volume'] - $this->info['replay_gain']['album']['adjustment'];
1551
+ }
1552
+
1553
+ if (isset($this->info['replay_gain']['track']['peak'])) {
1554
+ $this->info['replay_gain']['track']['max_noclip_gain'] = 0 - getid3_lib::RGADamplitude2dB($this->info['replay_gain']['track']['peak']);
1555
+ }
1556
+ if (isset($this->info['replay_gain']['album']['peak'])) {
1557
+ $this->info['replay_gain']['album']['max_noclip_gain'] = 0 - getid3_lib::RGADamplitude2dB($this->info['replay_gain']['album']['peak']);
1558
+ }
1559
+ }
1560
+ return true;
1561
+ }
1562
+
1563
+ function ProcessAudioStreams() {
1564
+ if (!empty($this->info['audio']['bitrate']) || !empty($this->info['audio']['channels']) || !empty($this->info['audio']['sample_rate'])) {
1565
+ if (!isset($this->info['audio']['streams'])) {
1566
+ foreach ($this->info['audio'] as $key => $value) {
1567
+ if ($key != 'streams') {
1568
+ $this->info['audio']['streams'][0][$key] = $value;
1569
+ }
1570
+ }
1571
+ }
1572
+ }
1573
+ return true;
1574
+ }
1575
+
1576
+ function getid3_tempnam() {
1577
+ return tempnam($this->tempdir, 'gI3');
1578
+ }
1579
+
1580
+
1581
+ public function saveAttachment(&$ThisFileInfoIndex, $filename, $offset, $length) {
1582
+ try {
1583
+ if (!getid3_lib::intValueSupported($offset + $length)) {
1584
+ throw new Exception('cannot extract attachment, it extends beyond the '.round(PHP_INT_MAX / 1073741824).'GB limit');
1585
+ }
1586
+
1587
+ // do not extract at all
1588
+ if ($this->option_save_attachments === getID3::ATTACHMENTS_NONE) {
1589
+
1590
+ unset($ThisFileInfoIndex); // do not set any
1591
+
1592
+ // extract to return array
1593
+ } elseif ($this->option_save_attachments === getID3::ATTACHMENTS_INLINE) {
1594
+
1595
+ // get whole data in one pass, till it is anyway stored in memory
1596
+ $ThisFileInfoIndex = file_get_contents($this->info['filenamepath'], false, null, $offset, $length);
1597
+ if (($ThisFileInfoIndex === false) || (strlen($ThisFileInfoIndex) != $length)) { // verify
1598
+ throw new Exception('failed to read attachment data');
1599
+ }
1600
+
1601
+ // assume directory path is given
1602
+ } else {
1603
+
1604
+ $dir = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->option_save_attachments), DIRECTORY_SEPARATOR);
1605
+ // check supplied directory
1606
+ if (!is_dir($dir) || !is_writable($dir)) {
1607
+ throw new Exception('getID3::saveAttachment() -- supplied path ('.$dir.') does not exist, or is not writable');
1608
+ }
1609
+
1610
+ // set up destination path
1611
+ $dest = $dir.DIRECTORY_SEPARATOR.$filename;
1612
+
1613
+ // optimize speed if read buffer size is configured to be large enough
1614
+ // here stream_copy_to_stream() may also be used. need to do speed-compare tests
1615
+ if ($length <= $this->fread_buffer_size()) {
1616
+ $data = file_get_contents($this->info['filenamepath'], false, null, $offset, $length);
1617
+ if (($data === false) || (strlen($data) != $length)) { // verify
1618
+ throw new Exception('failed to read attachment data');
1619
+ }
1620
+ if (!file_put_contents($dest, $data)) {
1621
+ throw new Exception('failed to create file '.$dest);
1622
+ }
1623
+ } else {
1624
+ // optimization not available - copy data in loop
1625
+ // here stream_copy_to_stream() shouldn't be used because it's internal read buffer may be larger than ours!
1626
+ getid3_lib::CopyFileParts($this->info['filenamepath'], $dest, $offset, $length);
1627
+ }
1628
+ $ThisFileInfoIndex = $dest;
1629
+ }
1630
+
1631
+ } catch (Exception $e) {
1632
+
1633
+ unset($ThisFileInfoIndex); // do not set any is case of error
1634
+ $this->warning('Failed to extract attachment '.$filename.': '.$e->getMessage());
1635
+ return false;
1636
+
1637
+ }
1638
+ return true;
1639
+ }
1640
+
1641
+
1642
+ public function include_module($name) {
1643
+ //if (!file_exists($this->include_path.'module.'.$name.'.php')) {
1644
+ if (!file_exists(GETID3_INCLUDEPATH.'module.'.$name.'.php')) {
1645
+ throw new getid3_exception('Required module.'.$name.'.php is missing.');
1646
+ }
1647
+ include_once(GETID3_INCLUDEPATH.'module.'.$name.'.php');
1648
+ return true;
1649
+ }
1650
+
1651
+ }
1652
+
1653
+
1654
+ abstract class getid3_handler
1655
+ {
1656
+ protected $getid3; // pointer
1657
+
1658
+ protected $data_string_flag = false; // analyzing filepointer or string
1659
+ protected $data_string; // string to analyze
1660
+ protected $data_string_position = 0; // seek position in string
1661
+
1662
+
1663
+ public function __construct(getID3 $getid3) {
1664
+ $this->getid3 = $getid3;
1665
+ }
1666
+
1667
+
1668
+ // Analyze from file pointer
1669
+ abstract public function Analyze();
1670
+
1671
+
1672
+ // Analyze from string instead
1673
+ public function AnalyzeString(&$string) {
1674
+ // Enter string mode
1675
+ $this->data_string_flag = true;
1676
+ $this->data_string = $string;
1677
+
1678
+ // Save info
1679
+ $saved_avdataoffset = $this->getid3->info['avdataoffset'];
1680
+ $saved_avdataend = $this->getid3->info['avdataend'];
1681
+ $saved_filesize = $this->getid3->info['filesize'];
1682
+
1683
+ // Reset some info
1684
+ $this->getid3->info['avdataoffset'] = 0;
1685
+ $this->getid3->info['avdataend'] = $this->getid3->info['filesize'] = strlen($string);
1686
+
1687
+ // Analyze
1688
+ $this->Analyze();
1689
+
1690
+ // Restore some info
1691
+ $this->getid3->info['avdataoffset'] = $saved_avdataoffset;
1692
+ $this->getid3->info['avdataend'] = $saved_avdataend;
1693
+ $this->getid3->info['filesize'] = $saved_filesize;
1694
+
1695
+ // Exit string mode
1696
+ $this->data_string_flag = false;
1697
+ }
1698
+
1699
+
1700
+ protected function ftell() {
1701
+ if ($this->data_string_flag) {
1702
+ return $this->data_string_position;
1703
+ }
1704
+ return ftell($this->getid3->fp);
1705
+ }
1706
+
1707
+
1708
+ protected function fread($bytes) {
1709
+ if ($this->data_string_flag) {
1710
+ $this->data_string_position += $bytes;
1711
+ return substr($this->data_string, $this->data_string_position - $bytes, $bytes);
1712
+ }
1713
+ return fread($this->getid3->fp, $bytes);
1714
+ }
1715
+
1716
+
1717
+ protected function fseek($bytes, $whence = SEEK_SET) {
1718
+ if ($this->data_string_flag) {
1719
+ switch ($whence) {
1720
+ case SEEK_SET:
1721
+ $this->data_string_position = $bytes;
1722
+ return;
1723
+
1724
+ case SEEK_CUR:
1725
+ $this->data_string_position += $bytes;
1726
+ return;
1727
+
1728
+ case SEEK_END:
1729
+ $this->data_string_position = strlen($this->data_string) + $bytes;
1730
+ return;
1731
+ }
1732
+ }
1733
+ return fseek($this->getid3->fp, $bytes, $whence);
1734
+ }
1735
+
1736
+ }
1737
+
1738
+
1739
+ class getid3_exception extends Exception
1740
+ {
1741
+ public $message;
1742
+ }
1743
+
1744
+ ?>
includes/lib/getid3/module.audio-video.mpeg.php ADDED
@@ -0,0 +1,299 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /////////////////////////////////////////////////////////////////
3
+ /// getID3() by James Heinrich <info@getid3.org> //
4
+ // available at http://getid3.sourceforge.net //
5
+ // or http://www.getid3.org //
6
+ /////////////////////////////////////////////////////////////////
7
+ // See readme.txt for more details //
8
+ /////////////////////////////////////////////////////////////////
9
+ // //
10
+ // module.audio-video.mpeg.php //
11
+ // module for analyzing MPEG files //
12
+ // dependencies: module.audio.mp3.php //
13
+ // ///
14
+ /////////////////////////////////////////////////////////////////
15
+
16
+ getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.mp3.php', __FILE__, true);
17
+
18
+ define('GETID3_MPEG_VIDEO_PICTURE_START', "\x00\x00\x01\x00");
19
+ define('GETID3_MPEG_VIDEO_USER_DATA_START', "\x00\x00\x01\xB2");
20
+ define('GETID3_MPEG_VIDEO_SEQUENCE_HEADER', "\x00\x00\x01\xB3");
21
+ define('GETID3_MPEG_VIDEO_SEQUENCE_ERROR', "\x00\x00\x01\xB4");
22
+ define('GETID3_MPEG_VIDEO_EXTENSION_START', "\x00\x00\x01\xB5");
23
+ define('GETID3_MPEG_VIDEO_SEQUENCE_END', "\x00\x00\x01\xB7");
24
+ define('GETID3_MPEG_VIDEO_GROUP_START', "\x00\x00\x01\xB8");
25
+ define('GETID3_MPEG_AUDIO_START', "\x00\x00\x01\xC0");
26
+
27
+
28
+ class getid3_mpeg extends getid3_handler
29
+ {
30
+
31
+ function Analyze() {
32
+ $info = &$this->getid3->info;
33
+
34
+ if ($info['avdataend'] <= $info['avdataoffset']) {
35
+ $info['error'][] = '"avdataend" ('.$info['avdataend'].') is unexpectedly less-than-or-equal-to "avdataoffset" ('.$info['avdataoffset'].')';
36
+ return false;
37
+ }
38
+ $info['fileformat'] = 'mpeg';
39
+ fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
40
+ $MPEGstreamData = fread($this->getid3->fp, min(100000, $info['avdataend'] - $info['avdataoffset']));
41
+ $MPEGstreamDataLength = strlen($MPEGstreamData);
42
+
43
+ $foundVideo = true;
44
+ $VideoChunkOffset = 0;
45
+ while (substr($MPEGstreamData, $VideoChunkOffset++, 4) !== GETID3_MPEG_VIDEO_SEQUENCE_HEADER) {
46
+ if ($VideoChunkOffset >= $MPEGstreamDataLength) {
47
+ $foundVideo = false;
48
+ break;
49
+ }
50
+ }
51
+ if ($foundVideo) {
52
+
53
+ // Start code 32 bits
54
+ // horizontal frame size 12 bits
55
+ // vertical frame size 12 bits
56
+ // pixel aspect ratio 4 bits
57
+ // frame rate 4 bits
58
+ // bitrate 18 bits
59
+ // marker bit 1 bit
60
+ // VBV buffer size 10 bits
61
+ // constrained parameter flag 1 bit
62
+ // intra quant. matrix flag 1 bit
63
+ // intra quant. matrix values 512 bits (present if matrix flag == 1)
64
+ // non-intra quant. matrix flag 1 bit
65
+ // non-intra quant. matrix values 512 bits (present if matrix flag == 1)
66
+
67
+ $info['video']['dataformat'] = 'mpeg';
68
+
69
+ $VideoChunkOffset += (strlen(GETID3_MPEG_VIDEO_SEQUENCE_HEADER) - 1);
70
+
71
+ $FrameSizeDWORD = getid3_lib::BigEndian2Int(substr($MPEGstreamData, $VideoChunkOffset, 3));
72
+ $VideoChunkOffset += 3;
73
+
74
+ $AspectRatioFrameRateDWORD = getid3_lib::BigEndian2Int(substr($MPEGstreamData, $VideoChunkOffset, 1));
75
+ $VideoChunkOffset += 1;
76
+
77
+ $assortedinformation = getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $VideoChunkOffset, 4));
78
+ $VideoChunkOffset += 4;
79
+
80
+ $info['mpeg']['video']['raw']['framesize_horizontal'] = ($FrameSizeDWORD & 0xFFF000) >> 12; // 12 bits for horizontal frame size
81
+ $info['mpeg']['video']['raw']['framesize_vertical'] = ($FrameSizeDWORD & 0x000FFF); // 12 bits for vertical frame size
82
+ $info['mpeg']['video']['raw']['pixel_aspect_ratio'] = ($AspectRatioFrameRateDWORD & 0xF0) >> 4;
83
+ $info['mpeg']['video']['raw']['frame_rate'] = ($AspectRatioFrameRateDWORD & 0x0F);
84
+
85
+ $info['mpeg']['video']['framesize_horizontal'] = $info['mpeg']['video']['raw']['framesize_horizontal'];
86
+ $info['mpeg']['video']['framesize_vertical'] = $info['mpeg']['video']['raw']['framesize_vertical'];
87
+
88
+ $info['mpeg']['video']['pixel_aspect_ratio'] = $this->MPEGvideoAspectRatioLookup($info['mpeg']['video']['raw']['pixel_aspect_ratio']);
89
+ $info['mpeg']['video']['pixel_aspect_ratio_text'] = $this->MPEGvideoAspectRatioTextLookup($info['mpeg']['video']['raw']['pixel_aspect_ratio']);
90
+ $info['mpeg']['video']['frame_rate'] = $this->MPEGvideoFramerateLookup($info['mpeg']['video']['raw']['frame_rate']);
91
+
92
+ $info['mpeg']['video']['raw']['bitrate'] = getid3_lib::Bin2Dec(substr($assortedinformation, 0, 18));
93
+ $info['mpeg']['video']['raw']['marker_bit'] = (bool) getid3_lib::Bin2Dec(substr($assortedinformation, 18, 1));
94
+ $info['mpeg']['video']['raw']['vbv_buffer_size'] = getid3_lib::Bin2Dec(substr($assortedinformation, 19, 10));
95
+ $info['mpeg']['video']['raw']['constrained_param_flag'] = (bool) getid3_lib::Bin2Dec(substr($assortedinformation, 29, 1));
96
+ $info['mpeg']['video']['raw']['intra_quant_flag'] = (bool) getid3_lib::Bin2Dec(substr($assortedinformation, 30, 1));
97
+ if ($info['mpeg']['video']['raw']['intra_quant_flag']) {
98
+
99
+ // read 512 bits
100
+ $info['mpeg']['video']['raw']['intra_quant'] = getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $VideoChunkOffset, 64));
101
+ $VideoChunkOffset += 64;
102
+
103
+ $info['mpeg']['video']['raw']['non_intra_quant_flag'] = (bool) getid3_lib::Bin2Dec(substr($info['mpeg']['video']['raw']['intra_quant'], 511, 1));
104
+ $info['mpeg']['video']['raw']['intra_quant'] = getid3_lib::Bin2Dec(substr($assortedinformation, 31, 1)).substr(getid3_lib::BigEndian2Bin(substr($MPEGstreamData, $VideoChunkOffset, 64)), 0, 511);
105
+
106
+ if ($info['mpeg']['video']['raw']['non_intra_quant_flag']) {
107
+ $info['mpeg']['video']['raw']['non_intra_quant'] = substr($MPEGstreamData, $VideoChunkOffset, 64);
108
+ $VideoChunkOffset += 64;
109
+ }
110
+
111
+ } else {
112
+
113
+ $info['mpeg']['video']['raw']['non_intra_quant_flag'] = (bool) getid3_lib::Bin2Dec(substr($assortedinformation, 31, 1));
114
+ if ($info['mpeg']['video']['raw']['non_intra_quant_flag']) {
115
+ $info['mpeg']['video']['raw']['non_intra_quant'] = substr($MPEGstreamData, $VideoChunkOffset, 64);
116
+ $VideoChunkOffset += 64;
117
+ }
118
+
119
+ }
120
+
121
+ if ($info['mpeg']['video']['raw']['bitrate'] == 0x3FFFF) { // 18 set bits
122
+
123
+ $info['warning'][] = 'This version of getID3() ['.$this->getid3->version().'] cannot determine average bitrate of VBR MPEG video files';
124
+ $info['mpeg']['video']['bitrate_mode'] = 'vbr';
125
+
126
+ } else {
127
+
128
+ $info['mpeg']['video']['bitrate'] = $info['mpeg']['video']['raw']['bitrate'] * 400;
129
+ $info['mpeg']['video']['bitrate_mode'] = 'cbr';
130
+ $info['video']['bitrate'] = $info['mpeg']['video']['bitrate'];
131
+
132
+ }
133
+
134
+ $info['video']['resolution_x'] = $info['mpeg']['video']['framesize_horizontal'];
135
+ $info['video']['resolution_y'] = $info['mpeg']['video']['framesize_vertical'];
136
+ $info['video']['frame_rate'] = $info['mpeg']['video']['frame_rate'];
137
+ $info['video']['bitrate_mode'] = $info['mpeg']['video']['bitrate_mode'];
138
+ $info['video']['pixel_aspect_ratio'] = $info['mpeg']['video']['pixel_aspect_ratio'];
139
+ $info['video']['lossless'] = false;
140
+ $info['video']['bits_per_sample'] = 24;
141
+
142
+ } else {
143
+
144
+ $info['error'][] = 'Could not find start of video block in the first 100,000 bytes (or before end of file) - this might not be an MPEG-video file?';
145
+
146
+ }
147
+
148
+ //0x000001B3 begins the sequence_header of every MPEG video stream.
149
+ //But in MPEG-2, this header must immediately be followed by an
150
+ //extension_start_code (0x000001B5) with a sequence_extension ID (1).
151
+ //(This extension contains all the additional MPEG-2 stuff.)
152
+ //MPEG-1 doesn't have this extension, so that's a sure way to tell the
153
+ //difference between MPEG-1 and MPEG-2 video streams.
154
+
155
+ if (substr($MPEGstreamData, $VideoChunkOffset, 4) == GETID3_MPEG_VIDEO_EXTENSION_START) {
156
+ $info['video']['codec'] = 'MPEG-2';
157
+ } else {
158
+ $info['video']['codec'] = 'MPEG-1';
159
+ }
160
+
161
+
162
+ $AudioChunkOffset = 0;
163
+ while (true) {
164
+ while (substr($MPEGstreamData, $AudioChunkOffset++, 4) !== GETID3_MPEG_AUDIO_START) {
165
+ if ($AudioChunkOffset >= $MPEGstreamDataLength) {
166
+ break 2;
167
+ }
168
+ }
169
+
170
+ $getid3_temp = new getID3();
171
+ $getid3_temp->openfile($this->getid3->filename);
172
+ $getid3_temp->info = $info;
173
+ $getid3_mp3 = new getid3_mp3($getid3_temp);
174
+ for ($i = 0; $i <= 7; $i++) {
175
+ // some files have the MPEG-audio header 8 bytes after the end of the $00 $00 $01 $C0 signature, some have it up to 13 bytes (or more?) after
176
+ // I have no idea why or what the difference is, so this is a stupid hack.
177
+ // If anybody has any better idea of what's going on, please let me know - info@getid3.org
178
+ fseek($getid3_temp->fp, ftell($this->getid3->fp), SEEK_SET);
179
+ $getid3_temp->info = $info; // only overwrite real data if valid header found
180
+ if ($getid3_mp3->decodeMPEGaudioHeader(($AudioChunkOffset + 3) + 8 + $i, $getid3_temp->info, false)) {
181
+ $info = $getid3_temp->info;
182
+ $info['audio']['bitrate_mode'] = 'cbr';
183
+ $info['audio']['lossless'] = false;
184
+ unset($getid3_temp, $getid3_mp3);
185
+ break 2;
186
+ }
187
+ }
188
+ unset($getid3_temp, $getid3_mp3);
189
+ }
190
+
191
+ // Temporary hack to account for interleaving overhead:
192
+ if (!empty($info['video']['bitrate']) && !empty($info['audio']['bitrate'])) {
193
+ $info['playtime_seconds'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / ($info['video']['bitrate'] + $info['audio']['bitrate']);
194
+
195
+ // Interleaved MPEG audio/video files have a certain amount of overhead that varies
196
+ // by both video and audio bitrates, and not in any sensible, linear/logarithmic patter
197
+ // Use interpolated lookup tables to approximately guess how much is overhead, because
198
+ // playtime is calculated as filesize / total-bitrate
199
+ $info['playtime_seconds'] *= $this->MPEGsystemNonOverheadPercentage($info['video']['bitrate'], $info['audio']['bitrate']);
200
+
201
+ //switch ($info['video']['bitrate']) {
202
+ // case('5000000'):
203
+ // $multiplier = 0.93292642112380355828048824319889;
204
+ // break;
205
+ // case('5500000'):
206
+ // $multiplier = 0.93582895375200989965359777343219;
207
+ // break;
208
+ // case('6000000'):
209
+ // $multiplier = 0.93796247714820932532911373859139;
210
+ // break;
211
+ // case('7000000'):
212
+ // $multiplier = 0.9413264083635103463010117778776;
213
+ // break;
214
+ // default:
215
+ // $multiplier = 1;
216
+ // break;
217
+ //}
218
+ //$info['playtime_seconds'] *= $multiplier;
219
+ //$info['warning'][] = 'Interleaved MPEG audio/video playtime may be inaccurate. With current hack should be within a few seconds of accurate. Report to info@getid3.org if off by more than 10 seconds.';
220
+ if ($info['video']['bitrate'] < 50000) {
221
+ $info['warning'][] = 'Interleaved MPEG audio/video playtime may be slightly inaccurate for video bitrates below 100kbps. Except in extreme low-bitrate situations, error should be less than 1%. Report to info@getid3.org if greater than this.';
222
+ }
223
+ }
224
+
225
+ return true;
226
+ }
227
+
228
+
229
+ function MPEGsystemNonOverheadPercentage($VideoBitrate, $AudioBitrate) {
230
+ $OverheadPercentage = 0;
231
+
232
+ $AudioBitrate = max(min($AudioBitrate / 1000, 384), 32); // limit to range of 32kbps - 384kbps (should be only legal bitrates, but maybe VBR?)
233
+ $VideoBitrate = max(min($VideoBitrate / 1000, 10000), 10); // limit to range of 10kbps - 10Mbps (beyond that curves flatten anyways, no big loss)
234
+
235
+
236
+ //OMBB[audiobitrate] = array(video-10kbps, video-100kbps, video-1000kbps, video-10000kbps)
237
+ $OverheadMultiplierByBitrate[32] = array(0, 0.9676287944368530, 0.9802276264360310, 0.9844916183244460, 0.9852821845179940);
238
+ $OverheadMultiplierByBitrate[48] = array(0, 0.9779100089209830, 0.9787770035359320, 0.9846738664076130, 0.9852683013799960);
239
+ $OverheadMultiplierByBitrate[56] = array(0, 0.9731249855367600, 0.9776624308938040, 0.9832606361852130, 0.9843922606633340);
240
+ $OverheadMultiplierByBitrate[64] = array(0, 0.9755642683275760, 0.9795256705493390, 0.9836573009193170, 0.9851122539404470);
241
+ $OverheadMultiplierByBitrate[96] = array(0, 0.9788025247497290, 0.9798553314148700, 0.9822956869792560, 0.9834815119124690);
242
+ $OverheadMultiplierByBitrate[128] = array(0, 0.9816940050925480, 0.9821675936072120, 0.9829756927470870, 0.9839763420152050);
243
+ $OverheadMultiplierByBitrate[160] = array(0, 0.9825894094561180, 0.9820913399073960, 0.9823907143253970, 0.9832821783651570);
244
+ $OverheadMultiplierByBitrate[192] = array(0, 0.9832038474336260, 0.9825731694317960, 0.9821028622712400, 0.9828262076447620);
245
+ $OverheadMultiplierByBitrate[224] = array(0, 0.9836516298538770, 0.9824718601823890, 0.9818302180625380, 0.9823735101626480);
246
+ $OverheadMultiplierByBitrate[256] = array(0, 0.9845863022094920, 0.9837229411967540, 0.9824521662210830, 0.9828645172100790);
247
+ $OverheadMultiplierByBitrate[320] = array(0, 0.9849565280263180, 0.9837683142805110, 0.9822885275960400, 0.9824424382727190);
248
+ $OverheadMultiplierByBitrate[384] = array(0, 0.9856094774357600, 0.9844573394432720, 0.9825970399837330, 0.9824673808303890);
249
+
250
+ $BitrateToUseMin = 32;
251
+ $BitrateToUseMax = 32;
252
+ $previousBitrate = 32;
253
+ foreach ($OverheadMultiplierByBitrate as $key => $value) {
254
+ if ($AudioBitrate >= $previousBitrate) {
255
+ $BitrateToUseMin = $previousBitrate;
256
+ }
257
+ if ($AudioBitrate < $key) {
258
+ $BitrateToUseMax = $key;
259
+ break;
260
+ }
261
+ $previousBitrate = $key;
262
+ }
263
+ $FactorA = ($BitrateToUseMax - $AudioBitrate) / ($BitrateToUseMax - $BitrateToUseMin);
264
+
265
+ $VideoBitrateLog10 = log10($VideoBitrate);
266
+ $VideoFactorMin1 = $OverheadMultiplierByBitrate[$BitrateToUseMin][floor($VideoBitrateLog10)];
267
+ $VideoFactorMin2 = $OverheadMultiplierByBitrate[$BitrateToUseMax][floor($VideoBitrateLog10)];
268
+ $VideoFactorMax1 = $OverheadMultiplierByBitrate[$BitrateToUseMin][ceil($VideoBitrateLog10)];
269
+ $VideoFactorMax2 = $OverheadMultiplierByBitrate[$BitrateToUseMax][ceil($VideoBitrateLog10)];
270
+ $FactorV = $VideoBitrateLog10 - floor($VideoBitrateLog10);
271
+
272
+ $OverheadPercentage = $VideoFactorMin1 * $FactorA * $FactorV;
273
+ $OverheadPercentage += $VideoFactorMin2 * (1 - $FactorA) * $FactorV;
274
+ $OverheadPercentage += $VideoFactorMax1 * $FactorA * (1 - $FactorV);
275
+ $OverheadPercentage += $VideoFactorMax2 * (1 - $FactorA) * (1 - $FactorV);
276
+
277
+ return $OverheadPercentage;
278
+ }
279
+
280
+
281
+ function MPEGvideoFramerateLookup($rawframerate) {
282
+ $MPEGvideoFramerateLookup = array(0, 23.976, 24, 25, 29.97, 30, 50, 59.94, 60);
283
+ return (isset($MPEGvideoFramerateLookup[$rawframerate]) ? (float) $MPEGvideoFramerateLookup[$rawframerate] : (float) 0);
284
+ }
285
+
286
+ function MPEGvideoAspectRatioLookup($rawaspectratio) {
287
+ $MPEGvideoAspectRatioLookup = array(0, 1, 0.6735, 0.7031, 0.7615, 0.8055, 0.8437, 0.8935, 0.9157, 0.9815, 1.0255, 1.0695, 1.0950, 1.1575, 1.2015, 0);
288
+ return (isset($MPEGvideoAspectRatioLookup[$rawaspectratio]) ? (float) $MPEGvideoAspectRatioLookup[$rawaspectratio] : (float) 0);
289
+ }
290
+
291
+ function MPEGvideoAspectRatioTextLookup($rawaspectratio) {
292
+ $MPEGvideoAspectRatioTextLookup = array('forbidden', 'square pixels', '0.6735', '16:9, 625 line, PAL', '0.7615', '0.8055', '16:9, 525 line, NTSC', '0.8935', '4:3, 625 line, PAL, CCIR601', '0.9815', '1.0255', '1.0695', '4:3, 525 line, NTSC, CCIR601', '1.1575', '1.2015', 'reserved');
293
+ return (isset($MPEGvideoAspectRatioTextLookup[$rawaspectratio]) ? $MPEGvideoAspectRatioTextLookup[$rawaspectratio] : '');
294
+ }
295
+
296
+ }
297
+
298
+
299
+ ?>
includes/lib/getid3/module.audio-video.quicktime.php ADDED
@@ -0,0 +1,2134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /////////////////////////////////////////////////////////////////
3
+ /// getID3() by James Heinrich <info@getid3.org> //
4
+ // available at http://getid3.sourceforge.net //
5
+ // or http://www.getid3.org //
6
+ /////////////////////////////////////////////////////////////////
7
+ // See readme.txt for more details //
8
+ /////////////////////////////////////////////////////////////////
9
+ // //
10
+ // module.audio-video.quicktime.php //
11
+ // module for analyzing Quicktime and MP3-in-MP4 files //
12
+ // dependencies: module.audio.mp3.php //
13
+ // ///
14
+ /////////////////////////////////////////////////////////////////
15
+
16
+ getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.audio.mp3.php', __FILE__, true);
17
+
18
+ class getid3_quicktime extends getid3_handler
19
+ {
20
+
21
+ var $ReturnAtomData = true;
22
+ var $ParseAllPossibleAtoms = false;
23
+
24
+ function Analyze() {
25
+ $info = &$this->getid3->info;
26
+
27
+ $info['fileformat'] = 'quicktime';
28
+ $info['quicktime']['hinting'] = false;
29
+ $info['quicktime']['controller'] = 'standard'; // may be overridden if 'ctyp' atom is present
30
+
31
+ fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
32
+
33
+ $offset = 0;
34
+ $atomcounter = 0;
35
+
36
+ while ($offset < $info['avdataend']) {
37
+ if (!getid3_lib::intValueSupported($offset)) {
38
+ $info['error'][] = 'Unable to parse atom at offset '.$offset.' because beyond '.round(PHP_INT_MAX / 1073741824).'GB limit of PHP filesystem functions';
39
+ break;
40
+ }
41
+ fseek($this->getid3->fp, $offset, SEEK_SET);
42
+ $AtomHeader = fread($this->getid3->fp, 8);
43
+
44
+ $atomsize = getid3_lib::BigEndian2Int(substr($AtomHeader, 0, 4));
45
+ $atomname = substr($AtomHeader, 4, 4);
46
+
47
+ // 64-bit MOV patch by jlegate�ktnc*com
48
+ if ($atomsize == 1) {
49
+ $atomsize = getid3_lib::BigEndian2Int(fread($this->getid3->fp, 8));
50
+ }
51
+
52
+ $info['quicktime'][$atomname]['name'] = $atomname;
53
+ $info['quicktime'][$atomname]['size'] = $atomsize;
54
+ $info['quicktime'][$atomname]['offset'] = $offset;
55
+
56
+ if (($offset + $atomsize) > $info['avdataend']) {
57
+ $info['error'][] = 'Atom at offset '.$offset.' claims to go beyond end-of-file (length: '.$atomsize.' bytes)';
58
+ return false;
59
+ }
60
+
61
+ if ($atomsize == 0) {
62
+ // Furthermore, for historical reasons the list of atoms is optionally
63
+ // terminated by a 32-bit integer set to 0. If you are writing a program
64
+ // to read user data atoms, you should allow for the terminating 0.
65
+ break;
66
+ }
67
+ switch ($atomname) {
68
+ case 'mdat': // Media DATa atom
69
+ // 'mdat' contains the actual data for the audio/video
70
+ if (($atomsize > 8) && (!isset($info['avdataend_tmp']) || ($info['quicktime'][$atomname]['size'] > ($info['avdataend_tmp'] - $info['avdataoffset'])))) {
71
+
72
+ $info['avdataoffset'] = $info['quicktime'][$atomname]['offset'] + 8;
73
+ $OldAVDataEnd = $info['avdataend'];
74
+ $info['avdataend'] = $info['quicktime'][$atomname]['offset'] + $info['quicktime'][$atomname]['size'];
75
+
76
+ $getid3_temp = new getID3();
77
+ $getid3_temp->openfile($this->getid3->filename);
78
+ $getid3_temp->info['avdataoffset'] = $info['avdataoffset'];
79
+ $getid3_temp->info['avdataend'] = $info['avdataend'];
80
+ $getid3_mp3 = new getid3_mp3($getid3_temp);
81
+ if ($getid3_mp3->MPEGaudioHeaderValid($getid3_mp3->MPEGaudioHeaderDecode(fread($this->getid3->fp, 4)))) {
82
+ $getid3_mp3->getOnlyMPEGaudioInfo($getid3_temp->info['avdataoffset'], false);
83
+ if (!empty($getid3_temp->info['warning'])) {
84
+ foreach ($getid3_temp->info['warning'] as $value) {
85
+ $info['warning'][] = $value;
86
+ }
87
+ }
88
+ if (!empty($getid3_temp->info['mpeg'])) {
89
+ $info['mpeg'] = $getid3_temp->info['mpeg'];
90
+ if (isset($info['mpeg']['audio'])) {
91
+ $info['audio']['dataformat'] = 'mp3';
92
+ $info['audio']['codec'] = (!empty($info['mpeg']['audio']['encoder']) ? $info['mpeg']['audio']['encoder'] : (!empty($info['mpeg']['audio']['codec']) ? $info['mpeg']['audio']['codec'] : (!empty($info['mpeg']['audio']['LAME']) ? 'LAME' :'mp3')));
93
+ $info['audio']['sample_rate'] = $info['mpeg']['audio']['sample_rate'];
94
+ $info['audio']['channels'] = $info['mpeg']['audio']['channels'];
95
+ $info['audio']['bitrate'] = $info['mpeg']['audio']['bitrate'];
96
+ $info['audio']['bitrate_mode'] = strtolower($info['mpeg']['audio']['bitrate_mode']);
97
+ $info['bitrate'] = $info['audio']['bitrate'];
98
+ }
99
+ }
100
+ }
101
+ unset($getid3_mp3, $getid3_temp);
102
+ $info['avdataend'] = $OldAVDataEnd;
103
+ unset($OldAVDataEnd);
104
+
105
+ }
106
+ break;
107
+
108
+ case 'free': // FREE space atom
109
+ case 'skip': // SKIP atom
110
+ case 'wide': // 64-bit expansion placeholder atom
111
+ // 'free', 'skip' and 'wide' are just padding, contains no useful data at all
112
+ break;
113
+
114
+ default:
115
+ $atomHierarchy = array();
116
+ $info['quicktime'][$atomname] = $this->QuicktimeParseAtom($atomname, $atomsize, fread($this->getid3->fp, $atomsize), $offset, $atomHierarchy, $this->ParseAllPossibleAtoms);
117
+ break;
118
+ }
119
+
120
+ $offset += $atomsize;
121
+ $atomcounter++;
122
+ }
123
+
124
+ if (!empty($info['avdataend_tmp'])) {
125
+ // this value is assigned to a temp value and then erased because
126
+ // otherwise any atoms beyond the 'mdat' atom would not get parsed
127
+ $info['avdataend'] = $info['avdataend_tmp'];
128
+ unset($info['avdataend_tmp']);
129
+ }
130
+
131
+ if (!isset($info['bitrate']) && isset($info['playtime_seconds'])) {
132
+ $info['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
133
+ }
134
+ if (isset($info['bitrate']) && !isset($info['audio']['bitrate']) && !isset($info['quicktime']['video'])) {
135
+ $info['audio']['bitrate'] = $info['bitrate'];
136
+ }
137
+ if (!empty($info['playtime_seconds']) && !isset($info['video']['frame_rate']) && !empty($info['quicktime']['stts_framecount'])) {
138
+ foreach ($info['quicktime']['stts_framecount'] as $key => $samples_count) {
139
+ $samples_per_second = $samples_count / $info['playtime_seconds'];
140
+ if ($samples_per_second > 240) {
141
+ // has to be audio samples
142
+ } else {
143
+ $info['video']['frame_rate'] = $samples_per_second;
144
+ break;
145
+ }
146
+ }
147
+ }
148
+ if (($info['audio']['dataformat'] == 'mp4') && empty($info['video']['resolution_x'])) {
149
+ $info['fileformat'] = 'mp4';
150
+ $info['mime_type'] = 'audio/mp4';
151
+ unset($info['video']['dataformat']);
152
+ }
153
+
154
+ if (!$this->ReturnAtomData) {
155
+ unset($info['quicktime']['moov']);
156
+ }
157
+
158
+ if (empty($info['audio']['dataformat']) && !empty($info['quicktime']['audio'])) {
159
+ $info['audio']['dataformat'] = 'quicktime';
160
+ }
161
+ if (empty($info['video']['dataformat']) && !empty($info['quicktime']['video'])) {
162
+ $info['video']['dataformat'] = 'quicktime';
163
+ }
164
+
165
+ return true;
166
+ }
167
+
168
+ function QuicktimeParseAtom($atomname, $atomsize, $atom_data, $baseoffset, &$atomHierarchy, $ParseAllPossibleAtoms) {
169
+ // http://developer.apple.com/techpubs/quicktime/qtdevdocs/APIREF/INDEX/atomalphaindex.htm
170
+
171
+ $info = &$this->getid3->info;
172
+
173
+ $atom_parent = array_pop($atomHierarchy);
174
+ array_push($atomHierarchy, $atomname);
175
+ $atom_structure['hierarchy'] = implode(' ', $atomHierarchy);
176
+ $atom_structure['name'] = $atomname;
177
+ $atom_structure['size'] = $atomsize;
178
+ $atom_structure['offset'] = $baseoffset;
179
+ //echo getid3_lib::PrintHexBytes(substr($atom_data, 0, 8)).'<br>';
180
+ //echo getid3_lib::PrintHexBytes(substr($atom_data, 0, 8), false).'<br><br>';
181
+ switch ($atomname) {
182
+ case 'moov': // MOVie container atom
183
+ case 'trak': // TRAcK container atom
184
+ case 'clip': // CLIPping container atom
185
+ case 'matt': // track MATTe container atom
186
+ case 'edts': // EDiTS container atom
187
+ case 'tref': // Track REFerence container atom
188
+ case 'mdia': // MeDIA container atom
189
+ case 'minf': // Media INFormation container atom
190
+ case 'dinf': // Data INFormation container atom
191
+ case 'udta': // User DaTA container atom
192
+ case 'cmov': // Compressed MOVie container atom
193
+ case 'rmra': // Reference Movie Record Atom
194
+ case 'rmda': // Reference Movie Descriptor Atom
195
+ case 'gmhd': // Generic Media info HeaDer atom (seen on QTVR)
196
+ $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms);
197
+ break;
198
+
199
+ case 'ilst': // Item LiST container atom
200
+ $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms);
201
+
202
+ // some "ilst" atoms contain data atoms that have a numeric name, and the data is far more accessible if the returned array is compacted
203
+ $allnumericnames = true;
204
+ foreach ($atom_structure['subatoms'] as $subatomarray) {
205
+ if (!is_integer($subatomarray['name']) || (count($subatomarray['subatoms']) != 1)) {
206
+ $allnumericnames = false;
207
+ break;
208
+ }
209
+ }
210
+ if ($allnumericnames) {
211
+ $newData = array();
212
+ foreach ($atom_structure['subatoms'] as $subatomarray) {
213
+ foreach ($subatomarray['subatoms'] as $newData_subatomarray) {
214
+ unset($newData_subatomarray['hierarchy'], $newData_subatomarray['name']);
215
+ $newData[$subatomarray['name']] = $newData_subatomarray;
216
+ break;
217
+ }
218
+ }
219
+ $atom_structure['data'] = $newData;
220
+ unset($atom_structure['subatoms']);
221
+ }
222
+ break;
223
+
224
+ case "\x00\x00\x00\x01":
225
+ case "\x00\x00\x00\x02":
226
+ case "\x00\x00\x00\x03":
227
+ case "\x00\x00\x00\x04":
228
+ case "\x00\x00\x00\x05":
229
+ $atomname = getid3_lib::BigEndian2Int($atomname);
230
+ $atom_structure['name'] = $atomname;
231
+ $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms);
232
+ break;
233
+
234
+ case 'stbl': // Sample TaBLe container atom
235
+ $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms);
236
+ $isVideo = false;
237
+ $framerate = 0;
238
+ $framecount = 0;
239
+ foreach ($atom_structure['subatoms'] as $key => $value_array) {
240
+ if (isset($value_array['sample_description_table'])) {
241
+ foreach ($value_array['sample_description_table'] as $key2 => $value_array2) {
242
+ if (isset($value_array2['data_format'])) {
243
+ switch ($value_array2['data_format']) {
244
+ case 'avc1':
245
+ case 'mp4v':
246
+ // video data
247
+ $isVideo = true;
248
+ break;
249
+ case 'mp4a':
250
+ // audio data
251
+ break;
252
+ }
253
+ }
254
+ }
255
+ } elseif (isset($value_array['time_to_sample_table'])) {
256
+ foreach ($value_array['time_to_sample_table'] as $key2 => $value_array2) {
257
+ if (isset($value_array2['sample_count']) && isset($value_array2['sample_duration']) && ($value_array2['sample_duration'] > 0)) {
258
+ $framerate = round($info['quicktime']['time_scale'] / $value_array2['sample_duration'], 3);
259
+ $framecount = $value_array2['sample_count'];
260
+ }
261
+ }
262
+ }
263
+ }
264
+ if ($isVideo && $framerate) {
265
+ $info['quicktime']['video']['frame_rate'] = $framerate;
266
+ $info['video']['frame_rate'] = $info['quicktime']['video']['frame_rate'];
267
+ }
268
+ if ($isVideo && $framecount) {
269
+ $info['quicktime']['video']['frame_count'] = $framecount;
270
+ }
271
+ break;
272
+
273
+
274
+ case 'aART': // Album ARTist
275
+ case 'catg': // CaTeGory
276
+ case 'covr': // COVeR artwork
277
+ case 'cpil': // ComPILation
278
+ case 'cprt': // CoPyRighT
279
+ case 'desc': // DESCription
280
+ case 'disk': // DISK number
281
+ case 'egid': // Episode Global ID
282
+ case 'gnre': // GeNRE
283
+ case 'keyw': // KEYWord
284
+ case 'ldes':
285
+ case 'pcst': // PodCaST
286
+ case 'pgap': // GAPless Playback
287
+ case 'purd': // PURchase Date
288
+ case 'purl': // Podcast URL
289
+ case 'rati':
290
+ case 'rndu':
291
+ case 'rpdu':
292
+ case 'rtng': // RaTiNG
293
+ case 'stik':
294
+ case 'tmpo': // TeMPO (BPM)
295
+ case 'trkn': // TRacK Number
296
+ case 'tves': // TV EpiSode
297
+ case 'tvnn': // TV Network Name
298
+ case 'tvsh': // TV SHow Name
299
+ case 'tvsn': // TV SeasoN
300
+ case 'akID': // iTunes store account type
301
+ case 'apID':
302
+ case 'atID':
303
+ case 'cmID':
304
+ case 'cnID':
305
+ case 'geID':
306
+ case 'plID':
307
+ case 'sfID': // iTunes store country
308
+ case '�alb': // ALBum
309
+ case '�art': // ARTist
310
+ case '�ART':
311
+ case '�aut':
312
+ case '�cmt': // CoMmenT
313
+ case '�com': // COMposer
314
+ case '�cpy':
315
+ case '�day': // content created year
316
+ case '�dir':
317
+ case '�ed1':
318
+ case '�ed2':
319
+ case '�ed3':
320
+ case '�ed4':
321
+ case '�ed5':
322
+ case '�ed6':
323
+ case '�ed7':
324
+ case '�ed8':
325
+ case '�ed9':
326
+ case '�enc':
327
+ case '�fmt':
328
+ case '�gen': // GENre
329
+ case '�grp': // GRouPing
330
+ case '�hst':
331
+ case '�inf':
332
+ case '�lyr': // LYRics
333
+ case '�mak':
334
+ case '�mod':
335
+ case '�nam': // full NAMe
336
+ case '�ope':
337
+ case '�PRD':
338
+ case '�prd':
339
+ case '�prf':
340
+ case '�req':
341
+ case '�src':
342
+ case '�swr':
343
+ case '�too': // encoder
344
+ case '�trk': // TRacK
345
+ case '�url':
346
+ case '�wrn':
347
+ case '�wrt': // WRiTer
348
+ case '----': // itunes specific
349
+ if ($atom_parent == 'udta') {
350
+ // User data atom handler
351
+ $atom_structure['data_length'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2));
352
+ $atom_structure['language_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 2));
353
+ $atom_structure['data'] = substr($atom_data, 4);
354
+
355
+ $atom_structure['language'] = $this->QuicktimeLanguageLookup($atom_structure['language_id']);
356
+ if (empty($info['comments']['language']) || (!in_array($atom_structure['language'], $info['comments']['language']))) {
357
+ $info['comments']['language'][] = $atom_structure['language'];
358
+ }
359
+ } else {
360
+ // Apple item list box atom handler
361
+ $atomoffset = 0;
362
+ if (substr($atom_data, 2, 2) == "\x10\xB5") {
363
+ // not sure what it means, but observed on iPhone4 data.
364
+ // Each $atom_data has 2 bytes of datasize, plus 0x10B5, then data
365
+ while ($atomoffset < strlen($atom_data)) {
366
+ $boxsmallsize = getid3_lib::BigEndian2Int(substr($atom_data, $atomoffset, 2));
367
+ $boxsmalltype = substr($atom_data, $atomoffset + 2, 2);
368
+ $boxsmalldata = substr($atom_data, $atomoffset + 4, $boxsmallsize);
369
+ switch ($boxsmalltype) {
370
+ case "\x10\xB5":
371
+ $atom_structure['data'] = $boxsmalldata;
372
+ break;
373
+ default:
374
+ $info['warning'][] = 'Unknown QuickTime smallbox type: "'.getid3_lib::PrintHexBytes($boxsmalltype).'" at offset '.$baseoffset;
375
+ $atom_structure['data'] = $atom_data;
376
+ break;
377
+ }
378
+ $atomoffset += (4 + $boxsmallsize);
379
+ }
380
+ } else {
381
+ while ($atomoffset < strlen($atom_data)) {
382
+ $boxsize = getid3_lib::BigEndian2Int(substr($atom_data, $atomoffset, 4));
383
+ $boxtype = substr($atom_data, $atomoffset + 4, 4);
384
+ $boxdata = substr($atom_data, $atomoffset + 8, $boxsize - 8);
385
+
386
+ switch ($boxtype) {
387
+ case 'mean':
388
+ case 'name':
389
+ $atom_structure[$boxtype] = substr($boxdata, 4);
390
+ break;
391
+
392
+ case 'data':
393
+ $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($boxdata, 0, 1));
394
+ $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($boxdata, 1, 3));
395
+ switch ($atom_structure['flags_raw']) {
396
+ case 0: // data flag
397
+ case 21: // tmpo/cpil flag
398
+ switch ($atomname) {
399
+ case 'cpil':
400
+ case 'pcst':
401
+ case 'pgap':
402
+ $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1));
403
+ break;
404
+
405
+ case 'tmpo':
406
+ $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 2));
407
+ break;
408
+
409
+ case 'disk':
410
+ case 'trkn':
411
+ $num = getid3_lib::BigEndian2Int(substr($boxdata, 10, 2));
412
+ $num_total = getid3_lib::BigEndian2Int(substr($boxdata, 12, 2));
413
+ $atom_structure['data'] = empty($num) ? '' : $num;
414
+ $atom_structure['data'] .= empty($num_total) ? '' : '/'.$num_total;
415
+ break;
416
+
417
+ case 'gnre':
418
+ $GenreID = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4));
419
+ $atom_structure['data'] = getid3_id3v1::LookupGenreName($GenreID - 1);
420
+ break;
421
+
422
+ case 'rtng':
423
+ $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1));
424
+ $atom_structure['data'] = $this->QuicktimeContentRatingLookup($atom_structure[$atomname]);
425
+ break;
426
+
427
+ case 'stik':
428
+ $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 1));
429
+ $atom_structure['data'] = $this->QuicktimeSTIKLookup($atom_structure[$atomname]);
430
+ break;
431
+
432
+ case 'sfID':
433
+ $atom_structure[$atomname] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4));
434
+ $atom_structure['data'] = $this->QuicktimeStoreFrontCodeLookup($atom_structure[$atomname]);
435
+ break;
436
+
437
+ case 'egid':
438
+ case 'purl':
439
+ $atom_structure['data'] = substr($boxdata, 8);
440
+ break;
441
+
442
+ default:
443
+ $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($boxdata, 8, 4));
444
+ }
445
+ break;
446
+
447
+ case 1: // text flag
448
+ case 13: // image flag
449
+ default:
450
+ $atom_structure['data'] = substr($boxdata, 8);
451
+ break;
452
+
453
+ }
454
+ break;
455
+
456
+ default:
457
+ $info['warning'][] = 'Unknown QuickTime box type: "'.getid3_lib::PrintHexBytes($boxtype).'" at offset '.$baseoffset;
458
+ $atom_structure['data'] = $atom_data;
459
+
460
+ }
461
+ $atomoffset += $boxsize;
462
+ }
463
+ }
464
+ }
465
+ $this->CopyToAppropriateCommentsSection($atomname, $atom_structure['data'], $atom_structure['name']);
466
+ break;
467
+
468
+
469
+ case 'play': // auto-PLAY atom
470
+ $atom_structure['autoplay'] = (bool) getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
471
+
472
+ $info['quicktime']['autoplay'] = $atom_structure['autoplay'];
473
+ break;
474
+
475
+
476
+ case 'WLOC': // Window LOCation atom
477
+ $atom_structure['location_x'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2));
478
+ $atom_structure['location_y'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 2));
479
+ break;
480
+
481
+
482
+ case 'LOOP': // LOOPing atom
483
+ case 'SelO': // play SELection Only atom
484
+ case 'AllF': // play ALL Frames atom
485
+ $atom_structure['data'] = getid3_lib::BigEndian2Int($atom_data);
486
+ break;
487
+
488
+
489
+ case 'name': //
490
+ case 'MCPS': // Media Cleaner PRo
491
+ case '@PRM': // adobe PReMiere version
492
+ case '@PRQ': // adobe PRemiere Quicktime version
493
+ $atom_structure['data'] = $atom_data;
494
+ break;
495
+
496
+
497
+ case 'cmvd': // Compressed MooV Data atom
498
+ // Code by ubergeek�ubergeek*tv based on information from
499
+ // http://developer.apple.com/quicktime/icefloe/dispatch012.html
500
+ $atom_structure['unCompressedSize'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4));
501
+
502
+ $CompressedFileData = substr($atom_data, 4);
503
+ if ($UncompressedHeader = @gzuncompress($CompressedFileData)) {
504
+ $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($UncompressedHeader, 0, $atomHierarchy, $ParseAllPossibleAtoms);
505
+ } else {
506
+ $info['warning'][] = 'Error decompressing compressed MOV atom at offset '.$atom_structure['offset'];
507
+ }
508
+ break;
509
+
510
+
511
+ case 'dcom': // Data COMpression atom
512
+ $atom_structure['compression_id'] = $atom_data;
513
+ $atom_structure['compression_text'] = $this->QuicktimeDCOMLookup($atom_data);
514
+ break;
515
+
516
+
517
+ case 'rdrf': // Reference movie Data ReFerence atom
518
+ $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
519
+ $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3));
520
+ $atom_structure['flags']['internal_data'] = (bool) ($atom_structure['flags_raw'] & 0x000001);
521
+
522
+ $atom_structure['reference_type_name'] = substr($atom_data, 4, 4);
523
+ $atom_structure['reference_length'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4));
524
+ switch ($atom_structure['reference_type_name']) {
525
+ case 'url ':
526
+ $atom_structure['url'] = $this->NoNullString(substr($atom_data, 12));
527
+ break;
528
+
529
+ case 'alis':
530
+ $atom_structure['file_alias'] = substr($atom_data, 12);
531
+ break;
532
+
533
+ case 'rsrc':
534
+ $atom_structure['resource_alias'] = substr($atom_data, 12);
535
+ break;
536
+
537
+ default:
538
+ $atom_structure['data'] = substr($atom_data, 12);
539
+ break;
540
+ }
541
+ break;
542
+
543
+
544
+ case 'rmqu': // Reference Movie QUality atom
545
+ $atom_structure['movie_quality'] = getid3_lib::BigEndian2Int($atom_data);
546
+ break;
547
+
548
+
549
+ case 'rmcs': // Reference Movie Cpu Speed atom
550
+ $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
551
+ $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000
552
+ $atom_structure['cpu_speed_rating'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2));
553
+ break;
554
+
555
+
556
+ case 'rmvc': // Reference Movie Version Check atom
557
+ $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
558
+ $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000
559
+ $atom_structure['gestalt_selector'] = substr($atom_data, 4, 4);
560
+ $atom_structure['gestalt_value_mask'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4));
561
+ $atom_structure['gestalt_value'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4));
562
+ $atom_structure['gestalt_check_type'] = getid3_lib::BigEndian2Int(substr($atom_data, 14, 2));
563
+ break;
564
+
565
+
566
+ case 'rmcd': // Reference Movie Component check atom
567
+ $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
568
+ $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000
569
+ $atom_structure['component_type'] = substr($atom_data, 4, 4);
570
+ $atom_structure['component_subtype'] = substr($atom_data, 8, 4);
571
+ $atom_structure['component_manufacturer'] = substr($atom_data, 12, 4);
572
+ $atom_structure['component_flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4));
573
+ $atom_structure['component_flags_mask'] = getid3_lib::BigEndian2Int(substr($atom_data, 20, 4));
574
+ $atom_structure['component_min_version'] = getid3_lib::BigEndian2Int(substr($atom_data, 24, 4));
575
+ break;
576
+
577
+
578
+ case 'rmdr': // Reference Movie Data Rate atom
579
+ $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
580
+ $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000
581
+ $atom_structure['data_rate'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4));
582
+
583
+ $atom_structure['data_rate_bps'] = $atom_structure['data_rate'] * 10;
584
+ break;
585
+
586
+
587
+ case 'rmla': // Reference Movie Language Atom
588
+ $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
589
+ $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000
590
+ $atom_structure['language_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2));
591
+
592
+ $atom_structure['language'] = $this->QuicktimeLanguageLookup($atom_structure['language_id']);
593
+ if (empty($info['comments']['language']) || (!in_array($atom_structure['language'], $info['comments']['language']))) {
594
+ $info['comments']['language'][] = $atom_structure['language'];
595
+ }
596
+ break;
597
+
598
+
599
+ case 'rmla': // Reference Movie Language Atom
600
+ $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
601
+ $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000
602
+ $atom_structure['track_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2));
603
+ break;
604
+
605
+
606
+ case 'ptv ': // Print To Video - defines a movie's full screen mode
607
+ // http://developer.apple.com/documentation/QuickTime/APIREF/SOURCESIV/at_ptv-_pg.htm
608
+ $atom_structure['display_size_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2));
609
+ $atom_structure['reserved_1'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 2)); // hardcoded: 0x0000
610
+ $atom_structure['reserved_2'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); // hardcoded: 0x0000
611
+ $atom_structure['slide_show_flag'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 1));
612
+ $atom_structure['play_on_open_flag'] = getid3_lib::BigEndian2Int(substr($atom_data, 7, 1));
613
+
614
+ $atom_structure['flags']['play_on_open'] = (bool) $atom_structure['play_on_open_flag'];
615
+ $atom_structure['flags']['slide_show'] = (bool) $atom_structure['slide_show_flag'];
616
+
617
+ $ptv_lookup[0] = 'normal';
618
+ $ptv_lookup[1] = 'double';
619
+ $ptv_lookup[2] = 'half';
620
+ $ptv_lookup[3] = 'full';
621
+ $ptv_lookup[4] = 'current';
622
+ if (isset($ptv_lookup[$atom_structure['display_size_raw']])) {
623
+ $atom_structure['display_size'] = $ptv_lookup[$atom_structure['display_size_raw']];
624
+ } else {
625
+ $info['warning'][] = 'unknown "ptv " display constant ('.$atom_structure['display_size_raw'].')';
626
+ }
627
+ break;
628
+
629
+
630
+ case 'stsd': // Sample Table Sample Description atom
631
+ $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
632
+ $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000
633
+ $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4));
634
+ $stsdEntriesDataOffset = 8;
635
+ for ($i = 0; $i < $atom_structure['number_entries']; $i++) {
636
+ $atom_structure['sample_description_table'][$i]['size'] = getid3_lib::BigEndian2Int(substr($atom_data, $stsdEntriesDataOffset, 4));
637
+ $stsdEntriesDataOffset += 4;
638
+ $atom_structure['sample_description_table'][$i]['data_format'] = substr($atom_data, $stsdEntriesDataOffset, 4);
639
+ $stsdEntriesDataOffset += 4;
640
+ $atom_structure['sample_description_table'][$i]['reserved'] = getid3_lib::BigEndian2Int(substr($atom_data, $stsdEntriesDataOffset, 6));
641
+ $stsdEntriesDataOffset += 6;
642
+ $atom_structure['sample_description_table'][$i]['reference_index'] = getid3_lib::BigEndian2Int(substr($atom_data, $stsdEntriesDataOffset, 2));
643
+ $stsdEntriesDataOffset += 2;
644
+ $atom_structure['sample_description_table'][$i]['data'] = substr($atom_data, $stsdEntriesDataOffset, ($atom_structure['sample_description_table'][$i]['size'] - 4 - 4 - 6 - 2));
645
+ $stsdEntriesDataOffset += ($atom_structure['sample_description_table'][$i]['size'] - 4 - 4 - 6 - 2);
646
+
647
+ $atom_structure['sample_description_table'][$i]['encoder_version'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 0, 2));
648
+ $atom_structure['sample_description_table'][$i]['encoder_revision'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 2, 2));
649
+ $atom_structure['sample_description_table'][$i]['encoder_vendor'] = substr($atom_structure['sample_description_table'][$i]['data'], 4, 4);
650
+
651
+ switch ($atom_structure['sample_description_table'][$i]['encoder_vendor']) {
652
+
653
+ case "\x00\x00\x00\x00":
654
+ // audio atom
655
+ $atom_structure['sample_description_table'][$i]['audio_channels'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 8, 2));
656
+ $atom_structure['sample_description_table'][$i]['audio_bit_depth'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 10, 2));
657
+ $atom_structure['sample_description_table'][$i]['audio_compression_id'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 12, 2));
658
+ $atom_structure['sample_description_table'][$i]['audio_packet_size'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 14, 2));
659
+ $atom_structure['sample_description_table'][$i]['audio_sample_rate'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 16, 4));
660
+
661
+ switch ($atom_structure['sample_description_table'][$i]['data_format']) {
662
+ case 'avc1':
663
+ case 'mp4v':
664
+ $info['fileformat'] = 'mp4';
665
+ $info['video']['fourcc'] = $atom_structure['sample_description_table'][$i]['data_format'];
666
+ //$info['warning'][] = 'This version of getID3() ['.$this->getid3->version().'] does not fully support MPEG-4 audio/video streams'; // 2011-02-18: why am I warning about this again? What's not supported?
667
+ break;
668
+
669
+ case 'qtvr':
670
+ $info['video']['dataformat'] = 'quicktimevr';
671
+ break;
672
+
673
+ case 'mp4a':
674
+ default:
675
+ $info['quicktime']['audio']['codec'] = $this->QuicktimeAudioCodecLookup($atom_structure['sample_description_table'][$i]['data_format']);
676
+ $info['quicktime']['audio']['sample_rate'] = $atom_structure['sample_description_table'][$i]['audio_sample_rate'];
677
+ $info['quicktime']['audio']['channels'] = $atom_structure['sample_description_table'][$i]['audio_channels'];
678
+ $info['quicktime']['audio']['bit_depth'] = $atom_structure['sample_description_table'][$i]['audio_bit_depth'];
679
+ $info['audio']['codec'] = $info['quicktime']['audio']['codec'];
680
+ $info['audio']['sample_rate'] = $info['quicktime']['audio']['sample_rate'];
681
+ $info['audio']['channels'] = $info['quicktime']['audio']['channels'];
682
+ $info['audio']['bits_per_sample'] = $info['quicktime']['audio']['bit_depth'];
683
+ switch ($atom_structure['sample_description_table'][$i]['data_format']) {
684
+ case 'raw ': // PCM
685
+ case 'alac': // Apple Lossless Audio Codec
686
+ $info['audio']['lossless'] = true;
687
+ break;
688
+ default:
689
+ $info['audio']['lossless'] = false;
690
+ break;
691
+ }
692
+ break;
693
+ }
694
+ break;
695
+
696
+ default:
697
+ switch ($atom_structure['sample_description_table'][$i]['data_format']) {
698
+ case 'mp4s':
699
+ $info['fileformat'] = 'mp4';
700
+ break;
701
+
702
+ default:
703
+ // video atom
704
+ $atom_structure['sample_description_table'][$i]['video_temporal_quality'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 8, 4));
705
+ $atom_structure['sample_description_table'][$i]['video_spatial_quality'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 12, 4));
706
+ $atom_structure['sample_description_table'][$i]['video_frame_width'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 16, 2));
707
+ $atom_structure['sample_description_table'][$i]['video_frame_height'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 18, 2));
708
+ $atom_structure['sample_description_table'][$i]['video_resolution_x'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 20, 4));
709
+ $atom_structure['sample_description_table'][$i]['video_resolution_y'] = getid3_lib::FixedPoint16_16(substr($atom_structure['sample_description_table'][$i]['data'], 24, 4));
710
+ $atom_structure['sample_description_table'][$i]['video_data_size'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 28, 4));
711
+ $atom_structure['sample_description_table'][$i]['video_frame_count'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 32, 2));
712
+ $atom_structure['sample_description_table'][$i]['video_encoder_name_len'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 34, 1));
713
+ $atom_structure['sample_description_table'][$i]['video_encoder_name'] = substr($atom_structure['sample_description_table'][$i]['data'], 35, $atom_structure['sample_description_table'][$i]['video_encoder_name_len']);
714
+ $atom_structure['sample_description_table'][$i]['video_pixel_color_depth'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 66, 2));
715
+ $atom_structure['sample_description_table'][$i]['video_color_table_id'] = getid3_lib::BigEndian2Int(substr($atom_structure['sample_description_table'][$i]['data'], 68, 2));
716
+
717
+ $atom_structure['sample_description_table'][$i]['video_pixel_color_type'] = (($atom_structure['sample_description_table'][$i]['video_pixel_color_depth'] > 32) ? 'grayscale' : 'color');
718
+ $atom_structure['sample_description_table'][$i]['video_pixel_color_name'] = $this->QuicktimeColorNameLookup($atom_structure['sample_description_table'][$i]['video_pixel_color_depth']);
719
+
720
+ if ($atom_structure['sample_description_table'][$i]['video_pixel_color_name'] != 'invalid') {
721
+ $info['quicktime']['video']['codec_fourcc'] = $atom_structure['sample_description_table'][$i]['data_format'];
722
+ $info['quicktime']['video']['codec_fourcc_lookup'] = $this->QuicktimeVideoCodecLookup($atom_structure['sample_description_table'][$i]['data_format']);
723
+ $info['quicktime']['video']['codec'] = (($atom_structure['sample_description_table'][$i]['video_encoder_name_len'] > 0) ? $atom_structure['sample_description_table'][$i]['video_encoder_name'] : $atom_structure['sample_description_table'][$i]['data_format']);
724
+ $info['quicktime']['video']['color_depth'] = $atom_structure['sample_description_table'][$i]['video_pixel_color_depth'];
725
+ $info['quicktime']['video']['color_depth_name'] = $atom_structure['sample_description_table'][$i]['video_pixel_color_name'];
726
+
727
+ $info['video']['codec'] = $info['quicktime']['video']['codec'];
728
+ $info['video']['bits_per_sample'] = $info['quicktime']['video']['color_depth'];
729
+ }
730
+ $info['video']['lossless'] = false;
731
+ $info['video']['pixel_aspect_ratio'] = (float) 1;
732
+ break;
733
+ }
734
+ break;
735
+ }
736
+ switch (strtolower($atom_structure['sample_description_table'][$i]['data_format'])) {
737
+ case 'mp4a':
738
+ $info['audio']['dataformat'] = 'mp4';
739
+ $info['quicktime']['audio']['codec'] = 'mp4';
740
+ break;
741
+
742
+ case '3ivx':
743
+ case '3iv1':
744
+ case '3iv2':
745
+ $info['video']['dataformat'] = '3ivx';
746
+ break;
747
+
748
+ case 'xvid':
749
+ $info['video']['dataformat'] = 'xvid';
750
+ break;
751
+
752
+ case 'mp4v':
753
+ $info['video']['dataformat'] = 'mpeg4';
754
+ break;
755
+
756
+ case 'divx':
757
+ case 'div1':
758
+ case 'div2':
759
+ case 'div3':
760
+ case 'div4':
761
+ case 'div5':
762
+ case 'div6':
763
+ $info['video']['dataformat'] = 'divx';
764
+ break;
765
+
766
+ default:
767
+ // do nothing
768
+ break;
769
+ }
770
+ unset($atom_structure['sample_description_table'][$i]['data']);
771
+ }
772
+ break;
773
+
774
+
775
+ case 'stts': // Sample Table Time-to-Sample atom
776
+ $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
777
+ $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000
778
+ $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4));
779
+ $sttsEntriesDataOffset = 8;
780
+ //$FrameRateCalculatorArray = array();
781
+ $frames_count = 0;
782
+ for ($i = 0; $i < $atom_structure['number_entries']; $i++) {
783
+ $atom_structure['time_to_sample_table'][$i]['sample_count'] = getid3_lib::BigEndian2Int(substr($atom_data, $sttsEntriesDataOffset, 4));
784
+ $sttsEntriesDataOffset += 4;
785
+ $atom_structure['time_to_sample_table'][$i]['sample_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, $sttsEntriesDataOffset, 4));
786
+ $sttsEntriesDataOffset += 4;
787
+
788
+ $frames_count += $atom_structure['time_to_sample_table'][$i]['sample_count'];
789
+
790
+ // THIS SECTION REPLACED WITH CODE IN "stbl" ATOM
791
+ //if (!empty($info['quicktime']['time_scale']) && ($atom_structure['time_to_sample_table'][$i]['sample_duration'] > 0)) {
792
+ // $stts_new_framerate = $info['quicktime']['time_scale'] / $atom_structure['time_to_sample_table'][$i]['sample_duration'];
793
+ // if ($stts_new_framerate <= 60) {
794
+ // // some atoms have durations of "1" giving a very large framerate, which probably is not right
795
+ // $info['video']['frame_rate'] = max($info['video']['frame_rate'], $stts_new_framerate);
796
+ // }
797
+ //}
798
+ //
799
+ //$FrameRateCalculatorArray[($info['quicktime']['time_scale'] / $atom_structure['time_to_sample_table'][$i]['sample_duration'])] += $atom_structure['time_to_sample_table'][$i]['sample_count'];
800
+ }
801
+ $info['quicktime']['stts_framecount'][] = $frames_count;
802
+ //$sttsFramesTotal = 0;
803
+ //$sttsSecondsTotal = 0;
804
+ //foreach ($FrameRateCalculatorArray as $frames_per_second => $frame_count) {
805
+ // if (($frames_per_second > 60) || ($frames_per_second < 1)) {
806
+ // // not video FPS information, probably audio information
807
+ // $sttsFramesTotal = 0;
808
+ // $sttsSecondsTotal = 0;
809
+ // break;
810
+ // }
811
+ // $sttsFramesTotal += $frame_count;
812
+ // $sttsSecondsTotal += $frame_count / $frames_per_second;
813
+ //}
814
+ //if (($sttsFramesTotal > 0) && ($sttsSecondsTotal > 0)) {
815
+ // if (($sttsFramesTotal / $sttsSecondsTotal) > $info['video']['frame_rate']) {
816
+ // $info['video']['frame_rate'] = $sttsFramesTotal / $sttsSecondsTotal;
817
+ // }
818
+ //}
819
+ break;
820
+
821
+
822
+ case 'stss': // Sample Table Sync Sample (key frames) atom
823
+ if ($ParseAllPossibleAtoms) {
824
+ $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
825
+ $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000
826
+ $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4));
827
+ $stssEntriesDataOffset = 8;
828
+ for ($i = 0; $i < $atom_structure['number_entries']; $i++) {
829
+ $atom_structure['time_to_sample_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stssEntriesDataOffset, 4));
830
+ $stssEntriesDataOffset += 4;
831
+ }
832
+ }
833
+ break;
834
+
835
+
836
+ case 'stsc': // Sample Table Sample-to-Chunk atom
837
+ if ($ParseAllPossibleAtoms) {
838
+ $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
839
+ $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000
840
+ $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4));
841
+ $stscEntriesDataOffset = 8;
842
+ for ($i = 0; $i < $atom_structure['number_entries']; $i++) {
843
+ $atom_structure['sample_to_chunk_table'][$i]['first_chunk'] = getid3_lib::BigEndian2Int(substr($atom_data, $stscEntriesDataOffset, 4));
844
+ $stscEntriesDataOffset += 4;
845
+ $atom_structure['sample_to_chunk_table'][$i]['samples_per_chunk'] = getid3_lib::BigEndian2Int(substr($atom_data, $stscEntriesDataOffset, 4));
846
+ $stscEntriesDataOffset += 4;
847
+ $atom_structure['sample_to_chunk_table'][$i]['sample_description'] = getid3_lib::BigEndian2Int(substr($atom_data, $stscEntriesDataOffset, 4));
848
+ $stscEntriesDataOffset += 4;
849
+ }
850
+ }
851
+ break;
852
+
853
+
854
+ case 'stsz': // Sample Table SiZe atom
855
+ if ($ParseAllPossibleAtoms) {
856
+ $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
857
+ $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000
858
+ $atom_structure['sample_size'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4));
859
+ $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4));
860
+ $stszEntriesDataOffset = 12;
861
+ if ($atom_structure['sample_size'] == 0) {
862
+ for ($i = 0; $i < $atom_structure['number_entries']; $i++) {
863
+ $atom_structure['sample_size_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stszEntriesDataOffset, 4));
864
+ $stszEntriesDataOffset += 4;
865
+ }
866
+ }
867
+ }
868
+ break;
869
+
870
+
871
+ case 'stco': // Sample Table Chunk Offset atom
872
+ if ($ParseAllPossibleAtoms) {
873
+ $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
874
+ $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000
875
+ $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4));
876
+ $stcoEntriesDataOffset = 8;
877
+ for ($i = 0; $i < $atom_structure['number_entries']; $i++) {
878
+ $atom_structure['chunk_offset_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stcoEntriesDataOffset, 4));
879
+ $stcoEntriesDataOffset += 4;
880
+ }
881
+ }
882
+ break;
883
+
884
+
885
+ case 'co64': // Chunk Offset 64-bit (version of "stco" that supports > 2GB files)
886
+ if ($ParseAllPossibleAtoms) {
887
+ $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
888
+ $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000
889
+ $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4));
890
+ $stcoEntriesDataOffset = 8;
891
+ for ($i = 0; $i < $atom_structure['number_entries']; $i++) {
892
+ $atom_structure['chunk_offset_table'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $stcoEntriesDataOffset, 8));
893
+ $stcoEntriesDataOffset += 8;
894
+ }
895
+ }
896
+ break;
897
+
898
+
899
+ case 'dref': // Data REFerence atom
900
+ $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
901
+ $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000
902
+ $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4));
903
+ $drefDataOffset = 8;
904
+ for ($i = 0; $i < $atom_structure['number_entries']; $i++) {
905
+ $atom_structure['data_references'][$i]['size'] = getid3_lib::BigEndian2Int(substr($atom_data, $drefDataOffset, 4));
906
+ $drefDataOffset += 4;
907
+ $atom_structure['data_references'][$i]['type'] = substr($atom_data, $drefDataOffset, 4);
908
+ $drefDataOffset += 4;
909
+ $atom_structure['data_references'][$i]['version'] = getid3_lib::BigEndian2Int(substr($atom_data, $drefDataOffset, 1));
910
+ $drefDataOffset += 1;
911
+ $atom_structure['data_references'][$i]['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, $drefDataOffset, 3)); // hardcoded: 0x0000
912
+ $drefDataOffset += 3;
913
+ $atom_structure['data_references'][$i]['data'] = substr($atom_data, $drefDataOffset, ($atom_structure['data_references'][$i]['size'] - 4 - 4 - 1 - 3));
914
+ $drefDataOffset += ($atom_structure['data_references'][$i]['size'] - 4 - 4 - 1 - 3);
915
+
916
+ $atom_structure['data_references'][$i]['flags']['self_reference'] = (bool) ($atom_structure['data_references'][$i]['flags_raw'] & 0x001);
917
+ }
918
+ break;
919
+
920
+
921
+ case 'gmin': // base Media INformation atom
922
+ $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
923
+ $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000
924
+ $atom_structure['graphics_mode'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2));
925
+ $atom_structure['opcolor_red'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 2));
926
+ $atom_structure['opcolor_green'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 2));
927
+ $atom_structure['opcolor_blue'] = getid3_lib::BigEndian2Int(substr($atom_data, 10, 2));
928
+ $atom_structure['balance'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 2));
929
+ $atom_structure['reserved'] = getid3_lib::BigEndian2Int(substr($atom_data, 14, 2));
930
+ break;
931
+
932
+
933
+ case 'smhd': // Sound Media information HeaDer atom
934
+ $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
935
+ $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000
936
+ $atom_structure['balance'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2));
937
+ $atom_structure['reserved'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 2));
938
+ break;
939
+
940
+
941
+ case 'vmhd': // Video Media information HeaDer atom
942
+ $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
943
+ $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3));
944
+ $atom_structure['graphics_mode'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2));
945
+ $atom_structure['opcolor_red'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 2));
946
+ $atom_structure['opcolor_green'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 2));
947
+ $atom_structure['opcolor_blue'] = getid3_lib::BigEndian2Int(substr($atom_data, 10, 2));
948
+
949
+ $atom_structure['flags']['no_lean_ahead'] = (bool) ($atom_structure['flags_raw'] & 0x001);
950
+ break;
951
+
952
+
953
+ case 'hdlr': // HanDLeR reference atom
954
+ $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
955
+ $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000
956
+ $atom_structure['component_type'] = substr($atom_data, 4, 4);
957
+ $atom_structure['component_subtype'] = substr($atom_data, 8, 4);
958
+ $atom_structure['component_manufacturer'] = substr($atom_data, 12, 4);
959
+ $atom_structure['component_flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4));
960
+ $atom_structure['component_flags_mask'] = getid3_lib::BigEndian2Int(substr($atom_data, 20, 4));
961
+ $atom_structure['component_name'] = $this->Pascal2String(substr($atom_data, 24));
962
+
963
+ if (($atom_structure['component_subtype'] == 'STpn') && ($atom_structure['component_manufacturer'] == 'zzzz')) {
964
+ $info['video']['dataformat'] = 'quicktimevr';
965
+ }
966
+ break;
967
+
968
+
969
+ case 'mdhd': // MeDia HeaDer atom
970
+ $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
971
+ $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000
972
+ $atom_structure['creation_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4));
973
+ $atom_structure['modify_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4));
974
+ $atom_structure['time_scale'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4));
975
+ $atom_structure['duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4));
976
+ $atom_structure['language_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 20, 2));
977
+ $atom_structure['quality'] = getid3_lib::BigEndian2Int(substr($atom_data, 22, 2));
978
+
979
+ if ($atom_structure['time_scale'] == 0) {
980
+ $info['error'][] = 'Corrupt Quicktime file: mdhd.time_scale == zero';
981
+ return false;
982
+ }
983
+ $info['quicktime']['time_scale'] = (isset($info['quicktime']['time_scale']) ? max($info['quicktime']['time_scale'], $atom_structure['time_scale']) : $atom_structure['time_scale']);
984
+
985
+ $atom_structure['creation_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['creation_time']);
986
+ $atom_structure['modify_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['modify_time']);
987
+ $atom_structure['playtime_seconds'] = $atom_structure['duration'] / $atom_structure['time_scale'];
988
+ $atom_structure['language'] = $this->QuicktimeLanguageLookup($atom_structure['language_id']);
989
+ if (empty($info['comments']['language']) || (!in_array($atom_structure['language'], $info['comments']['language']))) {
990
+ $info['comments']['language'][] = $atom_structure['language'];
991
+ }
992
+ break;
993
+
994
+
995
+ case 'pnot': // Preview atom
996
+ $atom_structure['modification_date'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); // "standard Macintosh format"
997
+ $atom_structure['version_number'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); // hardcoded: 0x00
998
+ $atom_structure['atom_type'] = substr($atom_data, 6, 4); // usually: 'PICT'
999
+ $atom_structure['atom_index'] = getid3_lib::BigEndian2Int(substr($atom_data, 10, 2)); // usually: 0x01
1000
+
1001
+ $atom_structure['modification_date_unix'] = getid3_lib::DateMac2Unix($atom_structure['modification_date']);
1002
+ break;
1003
+
1004
+
1005
+ case 'crgn': // Clipping ReGioN atom
1006
+ $atom_structure['region_size'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 2)); // The Region size, Region boundary box,
1007
+ $atom_structure['boundary_box'] = getid3_lib::BigEndian2Int(substr($atom_data, 2, 8)); // and Clipping region data fields
1008
+ $atom_structure['clipping_data'] = substr($atom_data, 10); // constitute a QuickDraw region.
1009
+ break;
1010
+
1011
+
1012
+ case 'load': // track LOAD settings atom
1013
+ $atom_structure['preload_start_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4));
1014
+ $atom_structure['preload_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4));
1015
+ $atom_structure['preload_flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4));
1016
+ $atom_structure['default_hints_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4));
1017
+
1018
+ $atom_structure['default_hints']['double_buffer'] = (bool) ($atom_structure['default_hints_raw'] & 0x0020);
1019
+ $atom_structure['default_hints']['high_quality'] = (bool) ($atom_structure['default_hints_raw'] & 0x0100);
1020
+ break;
1021
+
1022
+
1023
+ case 'tmcd': // TiMe CoDe atom
1024
+ case 'chap': // CHAPter list atom
1025
+ case 'sync': // SYNChronization atom
1026
+ case 'scpt': // tranSCriPT atom
1027
+ case 'ssrc': // non-primary SouRCe atom
1028
+ for ($i = 0; $i < (strlen($atom_data) % 4); $i++) {
1029
+ $atom_structure['track_id'][$i] = getid3_lib::BigEndian2Int(substr($atom_data, $i * 4, 4));
1030
+ }
1031
+ break;
1032
+
1033
+
1034
+ case 'elst': // Edit LiST atom
1035
+ $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
1036
+ $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000
1037
+ $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4));
1038
+ for ($i = 0; $i < $atom_structure['number_entries']; $i++ ) {
1039
+ $atom_structure['edit_list'][$i]['track_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($i * 12) + 0, 4));
1040
+ $atom_structure['edit_list'][$i]['media_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($i * 12) + 4, 4));
1041
+ $atom_structure['edit_list'][$i]['media_rate'] = getid3_lib::FixedPoint16_16(substr($atom_data, 8 + ($i * 12) + 8, 4));
1042
+ }
1043
+ break;
1044
+
1045
+
1046
+ case 'kmat': // compressed MATte atom
1047
+ $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
1048
+ $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000
1049
+ $atom_structure['matte_data_raw'] = substr($atom_data, 4);
1050
+ break;
1051
+
1052
+
1053
+ case 'ctab': // Color TABle atom
1054
+ $atom_structure['color_table_seed'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4)); // hardcoded: 0x00000000
1055
+ $atom_structure['color_table_flags'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 2)); // hardcoded: 0x8000
1056
+ $atom_structure['color_table_size'] = getid3_lib::BigEndian2Int(substr($atom_data, 6, 2)) + 1;
1057
+ for ($colortableentry = 0; $colortableentry < $atom_structure['color_table_size']; $colortableentry++) {
1058
+ $atom_structure['color_table'][$colortableentry]['alpha'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($colortableentry * 8) + 0, 2));
1059
+ $atom_structure['color_table'][$colortableentry]['red'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($colortableentry * 8) + 2, 2));
1060
+ $atom_structure['color_table'][$colortableentry]['green'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($colortableentry * 8) + 4, 2));
1061
+ $atom_structure['color_table'][$colortableentry]['blue'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($colortableentry * 8) + 6, 2));
1062
+ }
1063
+ break;
1064
+
1065
+
1066
+ case 'mvhd': // MoVie HeaDer atom
1067
+ $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
1068
+ $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3));
1069
+ $atom_structure['creation_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4));
1070
+ $atom_structure['modify_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4));
1071
+ $atom_structure['time_scale'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4));
1072
+ $atom_structure['duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4));
1073
+ $atom_structure['preferred_rate'] = getid3_lib::FixedPoint16_16(substr($atom_data, 20, 4));
1074
+ $atom_structure['preferred_volume'] = getid3_lib::FixedPoint8_8(substr($atom_data, 24, 2));
1075
+ $atom_structure['reserved'] = substr($atom_data, 26, 10);
1076
+ $atom_structure['matrix_a'] = getid3_lib::FixedPoint16_16(substr($atom_data, 36, 4));
1077
+ $atom_structure['matrix_b'] = getid3_lib::FixedPoint16_16(substr($atom_data, 40, 4));
1078
+ $atom_structure['matrix_u'] = getid3_lib::FixedPoint2_30(substr($atom_data, 44, 4));
1079
+ $atom_structure['matrix_c'] = getid3_lib::FixedPoint16_16(substr($atom_data, 48, 4));
1080
+ $atom_structure['matrix_d'] = getid3_lib::FixedPoint16_16(substr($atom_data, 52, 4));
1081
+ $atom_structure['matrix_v'] = getid3_lib::FixedPoint2_30(substr($atom_data, 56, 4));
1082
+ $atom_structure['matrix_x'] = getid3_lib::FixedPoint16_16(substr($atom_data, 60, 4));
1083
+ $atom_structure['matrix_y'] = getid3_lib::FixedPoint16_16(substr($atom_data, 64, 4));
1084
+ $atom_structure['matrix_w'] = getid3_lib::FixedPoint2_30(substr($atom_data, 68, 4));
1085
+ $atom_structure['preview_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 72, 4));
1086
+ $atom_structure['preview_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 76, 4));
1087
+ $atom_structure['poster_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 80, 4));
1088
+ $atom_structure['selection_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 84, 4));
1089
+ $atom_structure['selection_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 88, 4));
1090
+ $atom_structure['current_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 92, 4));
1091
+ $atom_structure['next_track_id'] = getid3_lib::BigEndian2Int(substr($atom_data, 96, 4));
1092
+
1093
+ if ($atom_structure['time_scale'] == 0) {
1094
+ $info['error'][] = 'Corrupt Quicktime file: mvhd.time_scale == zero';
1095
+ return false;
1096
+ }
1097
+ $atom_structure['creation_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['creation_time']);
1098
+ $atom_structure['modify_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['modify_time']);
1099
+ $info['quicktime']['time_scale'] = (isset($info['quicktime']['time_scale']) ? max($info['quicktime']['time_scale'], $atom_structure['time_scale']) : $atom_structure['time_scale']);
1100
+ $info['quicktime']['display_scale'] = $atom_structure['matrix_a'];
1101
+ $info['playtime_seconds'] = $atom_structure['duration'] / $atom_structure['time_scale'];
1102
+ break;
1103
+
1104
+
1105
+ case 'tkhd': // TracK HeaDer atom
1106
+ $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
1107
+ $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3));
1108
+ $atom_structure['creation_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4));
1109
+ $atom_structure['modify_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 8, 4));
1110
+ $atom_structure['trackid'] = getid3_lib::BigEndian2Int(substr($atom_data, 12, 4));
1111
+ $atom_structure['reserved1'] = getid3_lib::BigEndian2Int(substr($atom_data, 16, 4));
1112
+ $atom_structure['duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 20, 4));
1113
+ $atom_structure['reserved2'] = getid3_lib::BigEndian2Int(substr($atom_data, 24, 8));
1114
+ $atom_structure['layer'] = getid3_lib::BigEndian2Int(substr($atom_data, 32, 2));
1115
+ $atom_structure['alternate_group'] = getid3_lib::BigEndian2Int(substr($atom_data, 34, 2));
1116
+ $atom_structure['volume'] = getid3_lib::FixedPoint8_8(substr($atom_data, 36, 2));
1117
+ $atom_structure['reserved3'] = getid3_lib::BigEndian2Int(substr($atom_data, 38, 2));
1118
+ $atom_structure['matrix_a'] = getid3_lib::FixedPoint16_16(substr($atom_data, 40, 4));
1119
+ $atom_structure['matrix_b'] = getid3_lib::FixedPoint16_16(substr($atom_data, 44, 4));
1120
+ $atom_structure['matrix_u'] = getid3_lib::FixedPoint16_16(substr($atom_data, 48, 4));
1121
+ $atom_structure['matrix_c'] = getid3_lib::FixedPoint16_16(substr($atom_data, 52, 4));
1122
+ $atom_structure['matrix_d'] = getid3_lib::FixedPoint16_16(substr($atom_data, 56, 4));
1123
+ $atom_structure['matrix_v'] = getid3_lib::FixedPoint16_16(substr($atom_data, 60, 4));
1124
+ $atom_structure['matrix_x'] = getid3_lib::FixedPoint2_30(substr($atom_data, 64, 4));
1125
+ $atom_structure['matrix_y'] = getid3_lib::FixedPoint2_30(substr($atom_data, 68, 4));
1126
+ $atom_structure['matrix_w'] = getid3_lib::FixedPoint2_30(substr($atom_data, 72, 4));
1127
+ $atom_structure['width'] = getid3_lib::FixedPoint16_16(substr($atom_data, 76, 4));
1128
+ $atom_structure['height'] = getid3_lib::FixedPoint16_16(substr($atom_data, 80, 4));
1129
+
1130
+ $atom_structure['flags']['enabled'] = (bool) ($atom_structure['flags_raw'] & 0x0001);
1131
+ $atom_structure['flags']['in_movie'] = (bool) ($atom_structure['flags_raw'] & 0x0002);
1132
+ $atom_structure['flags']['in_preview'] = (bool) ($atom_structure['flags_raw'] & 0x0004);
1133
+ $atom_structure['flags']['in_poster'] = (bool) ($atom_structure['flags_raw'] & 0x0008);
1134
+ $atom_structure['creation_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['creation_time']);
1135
+ $atom_structure['modify_time_unix'] = getid3_lib::DateMac2Unix($atom_structure['modify_time']);
1136
+
1137
+ if ($atom_structure['flags']['enabled'] == 1) {
1138
+ if (!isset($info['video']['resolution_x']) || !isset($info['video']['resolution_y'])) {
1139
+ $info['video']['resolution_x'] = $atom_structure['width'];
1140
+ $info['video']['resolution_y'] = $atom_structure['height'];
1141
+ }
1142
+ $info['video']['resolution_x'] = max($info['video']['resolution_x'], $atom_structure['width']);
1143
+ $info['video']['resolution_y'] = max($info['video']['resolution_y'], $atom_structure['height']);
1144
+ $info['quicktime']['video']['resolution_x'] = $info['video']['resolution_x'];
1145
+ $info['quicktime']['video']['resolution_y'] = $info['video']['resolution_y'];
1146
+ } else {
1147
+ if (isset($info['video']['resolution_x'])) { unset($info['video']['resolution_x']); }
1148
+ if (isset($info['video']['resolution_y'])) { unset($info['video']['resolution_y']); }
1149
+ if (isset($info['quicktime']['video'])) { unset($info['quicktime']['video']); }
1150
+ }
1151
+ break;
1152
+
1153
+
1154
+ case 'iods': // Initial Object DeScriptor atom
1155
+ // http://www.koders.com/c/fid1FAB3E762903DC482D8A246D4A4BF9F28E049594.aspx?s=windows.h
1156
+ // http://libquicktime.sourcearchive.com/documentation/1.0.2plus-pdebian/iods_8c-source.html
1157
+ $offset = 0;
1158
+ $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1));
1159
+ $offset += 1;
1160
+ $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 3));
1161
+ $offset += 3;
1162
+ $atom_structure['mp4_iod_tag'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1));
1163
+ $offset += 1;
1164
+ $atom_structure['length'] = $this->quicktime_read_mp4_descr_length($atom_data, $offset);
1165
+ //$offset already adjusted by quicktime_read_mp4_descr_length()
1166
+ $atom_structure['object_descriptor_id'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 2));
1167
+ $offset += 2;
1168
+ $atom_structure['od_profile_level'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1));
1169
+ $offset += 1;
1170
+ $atom_structure['scene_profile_level'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1));
1171
+ $offset += 1;
1172
+ $atom_structure['audio_profile_id'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1));
1173
+ $offset += 1;
1174
+ $atom_structure['video_profile_id'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1));
1175
+ $offset += 1;
1176
+ $atom_structure['graphics_profile_level'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1));
1177
+ $offset += 1;
1178
+
1179
+ $atom_structure['num_iods_tracks'] = ($atom_structure['length'] - 7) / 6; // 6 bytes would only be right if all tracks use 1-byte length fields
1180
+ for ($i = 0; $i < $atom_structure['num_iods_tracks']; $i++) {
1181
+ $atom_structure['track'][$i]['ES_ID_IncTag'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 1));
1182
+ $offset += 1;
1183
+ $atom_structure['track'][$i]['length'] = $this->quicktime_read_mp4_descr_length($atom_data, $offset);
1184
+ //$offset already adjusted by quicktime_read_mp4_descr_length()
1185
+ $atom_structure['track'][$i]['track_id'] = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 4));
1186
+ $offset += 4;
1187
+ }
1188
+
1189
+ $atom_structure['audio_profile_name'] = $this->QuicktimeIODSaudioProfileName($atom_structure['audio_profile_id']);
1190
+ $atom_structure['video_profile_name'] = $this->QuicktimeIODSvideoProfileName($atom_structure['video_profile_id']);
1191
+ break;
1192
+
1193
+ case 'ftyp': // FileTYPe (?) atom (for MP4 it seems)
1194
+ $atom_structure['signature'] = substr($atom_data, 0, 4);
1195
+ $atom_structure['unknown_1'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4));
1196
+ $atom_structure['fourcc'] = substr($atom_data, 8, 4);
1197
+ break;
1198
+
1199
+ case 'mdat': // Media DATa atom
1200
+ case 'free': // FREE space atom
1201
+ case 'skip': // SKIP atom
1202
+ case 'wide': // 64-bit expansion placeholder atom
1203
+ // 'mdat' data is too big to deal with, contains no useful metadata
1204
+ // 'free', 'skip' and 'wide' are just padding, contains no useful data at all
1205
+
1206
+ // When writing QuickTime files, it is sometimes necessary to update an atom's size.
1207
+ // It is impossible to update a 32-bit atom to a 64-bit atom since the 32-bit atom
1208
+ // is only 8 bytes in size, and the 64-bit atom requires 16 bytes. Therefore, QuickTime
1209
+ // puts an 8-byte placeholder atom before any atoms it may have to update the size of.
1210
+ // In this way, if the atom needs to be converted from a 32-bit to a 64-bit atom, the
1211
+ // placeholder atom can be overwritten to obtain the necessary 8 extra bytes.
1212
+ // The placeholder atom has a type of kWideAtomPlaceholderType ( 'wide' ).
1213
+ break;
1214
+
1215
+
1216
+ case 'nsav': // NoSAVe atom
1217
+ // http://developer.apple.com/technotes/tn/tn2038.html
1218
+ $atom_structure['data'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4));
1219
+ break;
1220
+
1221
+ case 'ctyp': // Controller TYPe atom (seen on QTVR)
1222
+ // http://homepages.slingshot.co.nz/~helmboy/quicktime/formats/qtm-layout.txt
1223
+ // some controller names are:
1224
+ // 0x00 + 'std' for linear movie
1225
+ // 'none' for no controls
1226
+ $atom_structure['ctyp'] = substr($atom_data, 0, 4);
1227
+ $info['quicktime']['controller'] = $atom_structure['ctyp'];
1228
+ switch ($atom_structure['ctyp']) {
1229
+ case 'qtvr':
1230
+ $info['video']['dataformat'] = 'quicktimevr';
1231
+ break;
1232
+ }
1233
+ break;
1234
+
1235
+ case 'pano': // PANOrama track (seen on QTVR)
1236
+ $atom_structure['pano'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 4));
1237
+ break;
1238
+
1239
+ case 'hint': // HINT track
1240
+ case 'hinf': //
1241
+ case 'hinv': //
1242
+ case 'hnti': //
1243
+ $info['quicktime']['hinting'] = true;
1244
+ break;
1245
+
1246
+ case 'imgt': // IMaGe Track reference (kQTVRImageTrackRefType) (seen on QTVR)
1247
+ for ($i = 0; $i < ($atom_structure['size'] - 8); $i += 4) {
1248
+ $atom_structure['imgt'][] = getid3_lib::BigEndian2Int(substr($atom_data, $i, 4));
1249
+ }
1250
+ break;
1251
+
1252
+
1253
+ // Observed-but-not-handled atom types are just listed here to prevent warnings being generated
1254
+ case 'FXTC': // Something to do with Adobe After Effects (?)
1255
+ case 'PrmA':
1256
+ case 'code':
1257
+ case 'FIEL': // this is NOT "fiel" (Field Ordering) as describe here: http://developer.apple.com/documentation/QuickTime/QTFF/QTFFChap3/chapter_4_section_2.html
1258
+ case 'tapt': // TrackApertureModeDimensionsAID - http://developer.apple.com/documentation/QuickTime/Reference/QT7-1_Update_Reference/Constants/Constants.html
1259
+ // tapt seems to be used to compute the video size [http://www.getid3.org/phpBB3/viewtopic.php?t=838]
1260
+ // * http://lists.apple.com/archives/quicktime-api/2006/Aug/msg00014.html
1261
+ // * http://handbrake.fr/irclogs/handbrake-dev/handbrake-dev20080128_pg2.html
1262
+ case 'ctts':// STCompositionOffsetAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html
1263
+ case 'cslg':// STCompositionShiftLeastGreatestAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html
1264
+ case 'sdtp':// STSampleDependencyAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html
1265
+ case 'stps':// STPartialSyncSampleAID - http://developer.apple.com/documentation/QuickTime/Reference/QTRef_Constants/Reference/reference.html
1266
+ //$atom_structure['data'] = $atom_data;
1267
+ break;
1268
+
1269
+ case '�xyz': // GPS latitude+longitude+altitude
1270
+ $atom_structure['data'] = $atom_data;
1271
+ if (preg_match('#([\\+\\-][0-9\\.]+)([\\+\\-][0-9\\.]+)([\\+\\-][0-9\\.]+)?/$#i', $atom_data, $matches)) {
1272
+ @list($all, $latitude, $longitude, $altitude) = $matches;
1273
+ $info['quicktime']['comments']['gps_latitude'][] = floatval($latitude);
1274
+ $info['quicktime']['comments']['gps_longitude'][] = floatval($longitude);
1275
+ if (!empty($altitude)) {
1276
+ $info['quicktime']['comments']['gps_altitude'][] = floatval($altitude);
1277
+ }
1278
+ } else {
1279
+ $info['warning'][] = 'QuickTime atom "�xyz" data does not match expected data pattern at offset '.$baseoffset.'. Please report as getID3() bug.';
1280
+ }
1281
+ break;
1282
+
1283
+ case 'NCDT':
1284
+ // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html
1285
+ // Nikon-specific QuickTime tags found in the NCDT atom of MOV videos from some Nikon cameras such as the Coolpix S8000 and D5100
1286
+ $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 4, $atomHierarchy, $ParseAllPossibleAtoms);
1287
+ break;
1288
+ case 'NCTH': // Nikon Camera THumbnail image
1289
+ case 'NCVW': // Nikon Camera preVieW image
1290
+ // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html
1291
+ if (preg_match('/^\xFF\xD8\xFF/', $atom_data)) {
1292
+ $atom_structure['data'] = $atom_data;
1293
+ $atom_structure['image_mime'] = 'image/jpeg';
1294
+ $atom_structure['description'] = (($atomname == 'NCTH') ? 'Nikon Camera Thumbnail Image' : (($atomname == 'NCVW') ? 'Nikon Camera Preview Image' : 'Nikon preview image'));
1295
+ $info['quicktime']['comments']['picture'][] = array('image_mime'=>$atom_structure['image_mime'], 'data'=>$atom_data, 'description'=>$atom_structure['description']);
1296
+ }
1297
+ break;
1298
+ case 'NCHD': // MakerNoteVersion
1299
+ // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html
1300
+ $atom_structure['data'] = $atom_data;
1301
+ break;
1302
+ case 'NCTG': // NikonTags
1303
+ // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html#NCTG
1304
+ $atom_structure['data'] = $this->QuicktimeParseNikonNCTG($atom_data);
1305
+ break;
1306
+ case 'NCDB': // NikonTags
1307
+ // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html
1308
+ $atom_structure['data'] = $atom_data;
1309
+ break;
1310
+
1311
+ case "\x00\x00\x00\x00":
1312
+ case 'meta': // METAdata atom
1313
+ // some kind of metacontainer, may contain a big data dump such as:
1314
+ // mdta keys mdtacom.apple.quicktime.make (mdtacom.apple.quicktime.creationdate ,mdtacom.apple.quicktime.location.ISO6709 $mdtacom.apple.quicktime.software !mdtacom.apple.quicktime.model ilst data DEApple 0 (data DE2011-05-11T17:54:04+0200 2 *data DE+52.4936+013.3897+040.247/ data DE4.3.1 data DEiPhone 4
1315
+ // http://www.geocities.com/xhelmboyx/quicktime/formats/qti-layout.txt
1316
+
1317
+ $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
1318
+ $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3));
1319
+ $atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom(substr($atom_data, 4), $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms);
1320
+ //$atom_structure['subatoms'] = $this->QuicktimeParseContainerAtom($atom_data, $baseoffset + 8, $atomHierarchy, $ParseAllPossibleAtoms);
1321
+ break;
1322
+
1323
+ case 'data': // metaDATA atom
1324
+ // seems to be 2 bytes language code (ASCII), 2 bytes unknown (set to 0x10B5 in sample I have), remainder is useful data
1325
+ $atom_structure['language'] = substr($atom_data, 4 + 0, 2);
1326
+ $atom_structure['unknown'] = getid3_lib::BigEndian2Int(substr($atom_data, 4 + 2, 2));
1327
+ $atom_structure['data'] = substr($atom_data, 4 + 4);
1328
+ break;
1329
+
1330
+ default:
1331
+ $info['warning'][] = 'Unknown QuickTime atom type: "'.getid3_lib::PrintHexBytes($atomname).'" at offset '.$baseoffset;
1332
+ $atom_structure['data'] = $atom_data;
1333
+ break;
1334
+ }
1335
+ array_pop($atomHierarchy);
1336
+ return $atom_structure;
1337
+ }
1338
+
1339
+ function QuicktimeParseContainerAtom($atom_data, $baseoffset, &$atomHierarchy, $ParseAllPossibleAtoms) {
1340
+ //echo 'QuicktimeParseContainerAtom('.substr($atom_data, 4, 4).') @ '.$baseoffset.'<br><br>';
1341
+ $atom_structure = false;
1342
+ $subatomoffset = 0;
1343
+ $subatomcounter = 0;
1344
+ if ((strlen($atom_data) == 4) && (getid3_lib::BigEndian2Int($atom_data) == 0x00000000)) {
1345
+ return false;
1346
+ }
1347
+ while ($subatomoffset < strlen($atom_data)) {
1348
+ $subatomsize = getid3_lib::BigEndian2Int(substr($atom_data, $subatomoffset + 0, 4));
1349
+ $subatomname = substr($atom_data, $subatomoffset + 4, 4);
1350
+ $subatomdata = substr($atom_data, $subatomoffset + 8, $subatomsize - 8);
1351
+ if ($subatomsize == 0) {
1352
+ // Furthermore, for historical reasons the list of atoms is optionally
1353
+ // terminated by a 32-bit integer set to 0. If you are writing a program
1354
+ // to read user data atoms, you should allow for the terminating 0.
1355
+ return $atom_structure;
1356
+ }
1357
+
1358
+ $atom_structure[$subatomcounter] = $this->QuicktimeParseAtom($subatomname, $subatomsize, $subatomdata, $baseoffset + $subatomoffset, $atomHierarchy, $ParseAllPossibleAtoms);
1359
+
1360
+ $subatomoffset += $subatomsize;
1361
+ $subatomcounter++;
1362
+ }
1363
+ return $atom_structure;
1364
+ }
1365
+
1366
+
1367
+ function quicktime_read_mp4_descr_length($data, &$offset) {
1368
+ // http://libquicktime.sourcearchive.com/documentation/2:1.0.2plus-pdebian-2build1/esds_8c-source.html
1369
+ $num_bytes = 0;
1370
+ $length = 0;
1371
+ do {
1372
+ $b = ord(substr($data, $offset++, 1));
1373
+ $length = ($length << 7) | ($b & 0x7F);
1374
+ } while (($b & 0x80) && ($num_bytes++ < 4));
1375
+ return $length;
1376
+ }
1377
+
1378
+
1379
+ function QuicktimeLanguageLookup($languageid) {
1380
+ static $QuicktimeLanguageLookup = array();
1381
+ if (empty($QuicktimeLanguageLookup)) {
1382
+ $QuicktimeLanguageLookup[0] = 'English';
1383
+ $QuicktimeLanguageLookup[1] = 'French';
1384
+ $QuicktimeLanguageLookup[2] = 'German';
1385
+ $QuicktimeLanguageLookup[3] = 'Italian';
1386
+ $QuicktimeLanguageLookup[4] = 'Dutch';
1387
+ $QuicktimeLanguageLookup[5] = 'Swedish';
1388
+ $QuicktimeLanguageLookup[6] = 'Spanish';
1389
+ $QuicktimeLanguageLookup[7] = 'Danish';
1390
+ $QuicktimeLanguageLookup[8] = 'Portuguese';
1391
+ $QuicktimeLanguageLookup[9] = 'Norwegian';
1392
+ $QuicktimeLanguageLookup[10] = 'Hebrew';
1393
+ $QuicktimeLanguageLookup[11] = 'Japanese';
1394
+ $QuicktimeLanguageLookup[12] = 'Arabic';
1395
+ $QuicktimeLanguageLookup[13] = 'Finnish';
1396
+ $QuicktimeLanguageLookup[14] = 'Greek';
1397
+ $QuicktimeLanguageLookup[15] = 'Icelandic';
1398
+ $QuicktimeLanguageLookup[16] = 'Maltese';
1399
+ $QuicktimeLanguageLookup[17] = 'Turkish';
1400
+ $QuicktimeLanguageLookup[18] = 'Croatian';
1401
+ $QuicktimeLanguageLookup[19] = 'Chinese (Traditional)';
1402
+ $QuicktimeLanguageLookup[20] = 'Urdu';
1403
+ $QuicktimeLanguageLookup[21] = 'Hindi';
1404
+ $QuicktimeLanguageLookup[22] = 'Thai';
1405
+ $QuicktimeLanguageLookup[23] = 'Korean';
1406
+ $QuicktimeLanguageLookup[24] = 'Lithuanian';
1407
+ $QuicktimeLanguageLookup[25] = 'Polish';
1408
+ $QuicktimeLanguageLookup[26] = 'Hungarian';
1409
+ $QuicktimeLanguageLookup[27] = 'Estonian';
1410
+ $QuicktimeLanguageLookup[28] = 'Lettish';
1411
+ $QuicktimeLanguageLookup[28] = 'Latvian';
1412
+ $QuicktimeLanguageLookup[29] = 'Saamisk';
1413
+ $QuicktimeLanguageLookup[29] = 'Lappish';
1414
+ $QuicktimeLanguageLookup[30] = 'Faeroese';
1415
+ $QuicktimeLanguageLookup[31] = 'Farsi';
1416
+ $QuicktimeLanguageLookup[31] = 'Persian';
1417
+ $QuicktimeLanguageLookup[32] = 'Russian';
1418
+ $QuicktimeLanguageLookup[33] = 'Chinese (Simplified)';
1419
+ $QuicktimeLanguageLookup[34] = 'Flemish';
1420
+ $QuicktimeLanguageLookup[35] = 'Irish';
1421
+ $QuicktimeLanguageLookup[36] = 'Albanian';
1422
+ $QuicktimeLanguageLookup[37] = 'Romanian';
1423
+ $QuicktimeLanguageLookup[38] = 'Czech';
1424
+ $QuicktimeLanguageLookup[39] = 'Slovak';
1425
+ $QuicktimeLanguageLookup[40] = 'Slovenian';
1426
+ $QuicktimeLanguageLookup[41] = 'Yiddish';
1427
+ $QuicktimeLanguageLookup[42] = 'Serbian';
1428
+ $QuicktimeLanguageLookup[43] = 'Macedonian';
1429
+ $QuicktimeLanguageLookup[44] = 'Bulgarian';
1430
+ $QuicktimeLanguageLookup[45] = 'Ukrainian';
1431
+ $QuicktimeLanguageLookup[46] = 'Byelorussian';
1432
+ $QuicktimeLanguageLookup[47] = 'Uzbek';
1433
+ $QuicktimeLanguageLookup[48] = 'Kazakh';
1434
+ $QuicktimeLanguageLookup[49] = 'Azerbaijani';
1435
+ $QuicktimeLanguageLookup[50] = 'AzerbaijanAr';
1436
+ $QuicktimeLanguageLookup[51] = 'Armenian';
1437
+ $QuicktimeLanguageLookup[52] = 'Georgian';
1438
+ $QuicktimeLanguageLookup[53] = 'Moldavian';
1439
+ $QuicktimeLanguageLookup[54] = 'Kirghiz';
1440
+ $QuicktimeLanguageLookup[55] = 'Tajiki';
1441
+ $QuicktimeLanguageLookup[56] = 'Turkmen';
1442
+ $QuicktimeLanguageLookup[57] = 'Mongolian';
1443
+ $QuicktimeLanguageLookup[58] = 'MongolianCyr';
1444
+ $QuicktimeLanguageLookup[59] = 'Pashto';
1445
+ $QuicktimeLanguageLookup[60] = 'Kurdish';
1446
+ $QuicktimeLanguageLookup[61] = 'Kashmiri';
1447
+ $QuicktimeLanguageLookup[62] = 'Sindhi';
1448
+ $QuicktimeLanguageLookup[63] = 'Tibetan';
1449
+ $QuicktimeLanguageLookup[64] = 'Nepali';
1450
+ $QuicktimeLanguageLookup[65] = 'Sanskrit';
1451
+ $QuicktimeLanguageLookup[66] = 'Marathi';
1452
+ $QuicktimeLanguageLookup[67] = 'Bengali';
1453
+ $QuicktimeLanguageLookup[68] = 'Assamese';
1454
+ $QuicktimeLanguageLookup[69] = 'Gujarati';
1455
+ $QuicktimeLanguageLookup[70] = 'Punjabi';
1456
+ $QuicktimeLanguageLookup[71] = 'Oriya';
1457
+ $QuicktimeLanguageLookup[72] = 'Malayalam';
1458
+ $QuicktimeLanguageLookup[73] = 'Kannada';
1459
+ $QuicktimeLanguageLookup[74] = 'Tamil';
1460
+ $QuicktimeLanguageLookup[75] = 'Telugu';
1461
+ $QuicktimeLanguageLookup[76] = 'Sinhalese';
1462
+ $QuicktimeLanguageLookup[77] = 'Burmese';
1463
+ $QuicktimeLanguageLookup[78] = 'Khmer';
1464
+ $QuicktimeLanguageLookup[79] = 'Lao';
1465
+ $QuicktimeLanguageLookup[80] = 'Vietnamese';
1466
+ $QuicktimeLanguageLookup[81] = 'Indonesian';
1467
+ $QuicktimeLanguageLookup[82] = 'Tagalog';
1468
+ $QuicktimeLanguageLookup[83] = 'MalayRoman';
1469
+ $QuicktimeLanguageLookup[84] = 'MalayArabic';
1470
+ $QuicktimeLanguageLookup[85] = 'Amharic';
1471
+ $QuicktimeLanguageLookup[86] = 'Tigrinya';
1472
+ $QuicktimeLanguageLookup[87] = 'Galla';
1473
+ $QuicktimeLanguageLookup[87] = 'Oromo';
1474
+ $QuicktimeLanguageLookup[88] = 'Somali';
1475
+ $QuicktimeLanguageLookup[89] = 'Swahili';
1476
+ $QuicktimeLanguageLookup[90] = 'Ruanda';
1477
+ $QuicktimeLanguageLookup[91] = 'Rundi';
1478
+ $QuicktimeLanguageLookup[92] = 'Chewa';
1479
+ $QuicktimeLanguageLookup[93] = 'Malagasy';
1480
+ $QuicktimeLanguageLookup[94] = 'Esperanto';
1481
+ $QuicktimeLanguageLookup[128] = 'Welsh';
1482
+ $QuicktimeLanguageLookup[129] = 'Basque';
1483
+ $QuicktimeLanguageLookup[130] = 'Catalan';
1484
+ $QuicktimeLanguageLookup[131] = 'Latin';
1485
+ $QuicktimeLanguageLookup[132] = 'Quechua';
1486
+ $QuicktimeLanguageLookup[133] = 'Guarani';
1487
+ $QuicktimeLanguageLookup[134] = 'Aymara';
1488
+ $QuicktimeLanguageLookup[135] = 'Tatar';
1489
+ $QuicktimeLanguageLookup[136] = 'Uighur';
1490
+ $QuicktimeLanguageLookup[137] = 'Dzongkha';
1491
+ $QuicktimeLanguageLookup[138] = 'JavaneseRom';
1492
+ }
1493
+ return (isset($QuicktimeLanguageLookup[$languageid]) ? $QuicktimeLanguageLookup[$languageid] : 'invalid');
1494
+ }
1495
+
1496
+ function QuicktimeVideoCodecLookup($codecid) {
1497
+ static $QuicktimeVideoCodecLookup = array();
1498
+ if (empty($QuicktimeVideoCodecLookup)) {
1499
+ $QuicktimeVideoCodecLookup['.SGI'] = 'SGI';
1500
+ $QuicktimeVideoCodecLookup['3IV1'] = '3ivx MPEG-4 v1';
1501
+ $QuicktimeVideoCodecLookup['3IV2'] = '3ivx MPEG-4 v2';
1502
+ $QuicktimeVideoCodecLookup['3IVX'] = '3ivx MPEG-4';
1503
+ $QuicktimeVideoCodecLookup['8BPS'] = 'Planar RGB';
1504
+ $QuicktimeVideoCodecLookup['avc1'] = 'H.264/MPEG-4 AVC';
1505
+ $QuicktimeVideoCodecLookup['avr '] = 'AVR-JPEG';
1506
+ $QuicktimeVideoCodecLookup['b16g'] = '16Gray';
1507
+ $QuicktimeVideoCodecLookup['b32a'] = '32AlphaGray';
1508
+ $QuicktimeVideoCodecLookup['b48r'] = '48RGB';
1509
+ $QuicktimeVideoCodecLookup['b64a'] = '64ARGB';
1510
+ $QuicktimeVideoCodecLookup['base'] = 'Base';
1511
+ $QuicktimeVideoCodecLookup['clou'] = 'Cloud';
1512
+ $QuicktimeVideoCodecLookup['cmyk'] = 'CMYK';
1513
+ $QuicktimeVideoCodecLookup['cvid'] = 'Cinepak';
1514
+ $QuicktimeVideoCodecLookup['dmb1'] = 'OpenDML JPEG';
1515
+ $QuicktimeVideoCodecLookup['dvc '] = 'DVC-NTSC';
1516
+ $QuicktimeVideoCodecLookup['dvcp'] = 'DVC-PAL';
1517
+ $QuicktimeVideoCodecLookup['dvpn'] = 'DVCPro-NTSC';
1518
+ $QuicktimeVideoCodecLookup['dvpp'] = 'DVCPro-PAL';
1519
+ $QuicktimeVideoCodecLookup['fire'] = 'Fire';
1520
+ $QuicktimeVideoCodecLookup['flic'] = 'FLC';
1521
+ $QuicktimeVideoCodecLookup['gif '] = 'GIF';
1522
+ $QuicktimeVideoCodecLookup['h261'] = 'H261';
1523
+ $QuicktimeVideoCodecLookup['h263'] = 'H263';
1524
+ $QuicktimeVideoCodecLookup['IV41'] = 'Indeo4';
1525
+ $QuicktimeVideoCodecLookup['jpeg'] = 'JPEG';
1526
+ $QuicktimeVideoCodecLookup['kpcd'] = 'PhotoCD';
1527
+ $QuicktimeVideoCodecLookup['mjpa'] = 'Motion JPEG-A';
1528
+ $QuicktimeVideoCodecLookup['mjpb'] = 'Motion JPEG-B';
1529
+ $QuicktimeVideoCodecLookup['msvc'] = 'Microsoft Video1';
1530
+ $QuicktimeVideoCodecLookup['myuv'] = 'MPEG YUV420';
1531
+ $QuicktimeVideoCodecLookup['path'] = 'Vector';
1532
+ $QuicktimeVideoCodecLookup['png '] = 'PNG';
1533
+ $QuicktimeVideoCodecLookup['PNTG'] = 'MacPaint';
1534
+ $QuicktimeVideoCodecLookup['qdgx'] = 'QuickDrawGX';
1535
+ $QuicktimeVideoCodecLookup['qdrw'] = 'QuickDraw';
1536
+ $QuicktimeVideoCodecLookup['raw '] = 'RAW';
1537
+ $QuicktimeVideoCodecLookup['ripl'] = 'WaterRipple';
1538
+ $QuicktimeVideoCodecLookup['rpza'] = 'Video';
1539
+ $QuicktimeVideoCodecLookup['smc '] = 'Graphics';
1540
+ $QuicktimeVideoCodecLookup['SVQ1'] = 'Sorenson Video 1';
1541
+ $QuicktimeVideoCodecLookup['SVQ1'] = 'Sorenson Video 3';
1542
+ $QuicktimeVideoCodecLookup['syv9'] = 'Sorenson YUV9';
1543
+ $QuicktimeVideoCodecLookup['tga '] = 'Targa';
1544
+ $QuicktimeVideoCodecLookup['tiff'] = 'TIFF';
1545
+ $QuicktimeVideoCodecLookup['WRAW'] = 'Windows RAW';
1546
+ $QuicktimeVideoCodecLookup['WRLE'] = 'BMP';
1547
+ $QuicktimeVideoCodecLookup['y420'] = 'YUV420';
1548
+ $QuicktimeVideoCodecLookup['yuv2'] = 'ComponentVideo';
1549
+ $QuicktimeVideoCodecLookup['yuvs'] = 'ComponentVideoUnsigned';
1550
+ $QuicktimeVideoCodecLookup['yuvu'] = 'ComponentVideoSigned';
1551
+ }
1552
+ return (isset($QuicktimeVideoCodecLookup[$codecid]) ? $QuicktimeVideoCodecLookup[$codecid] : '');
1553
+ }
1554
+
1555
+ function QuicktimeAudioCodecLookup($codecid) {
1556
+ static $QuicktimeAudioCodecLookup = array();
1557
+ if (empty($QuicktimeAudioCodecLookup)) {
1558
+ $QuicktimeAudioCodecLookup['.mp3'] = 'Fraunhofer MPEG Layer-III alias';
1559
+ $QuicktimeAudioCodecLookup['aac '] = 'ISO/IEC 14496-3 AAC';
1560
+ $QuicktimeAudioCodecLookup['agsm'] = 'Apple GSM 10:1';
1561
+ $QuicktimeAudioCodecLookup['alac'] = 'Apple Lossless Audio Codec';
1562
+ $QuicktimeAudioCodecLookup['alaw'] = 'A-law 2:1';
1563
+ $QuicktimeAudioCodecLookup['conv'] = 'Sample Format';
1564
+ $QuicktimeAudioCodecLookup['dvca'] = 'DV';
1565
+ $QuicktimeAudioCodecLookup['dvi '] = 'DV 4:1';
1566
+ $QuicktimeAudioCodecLookup['eqal'] = 'Frequency Equalizer';
1567
+ $QuicktimeAudioCodecLookup['fl32'] = '32-bit Floating Point';
1568
+ $QuicktimeAudioCodecLookup['fl64'] = '64-bit Floating Point';
1569
+ $QuicktimeAudioCodecLookup['ima4'] = 'Interactive Multimedia Association 4:1';
1570
+ $QuicktimeAudioCodecLookup['in24'] = '24-bit Integer';
1571
+ $QuicktimeAudioCodecLookup['in32'] = '32-bit Integer';
1572
+ $QuicktimeAudioCodecLookup['lpc '] = 'LPC 23:1';
1573
+ $QuicktimeAudioCodecLookup['MAC3'] = 'Macintosh Audio Compression/Expansion (MACE) 3:1';
1574
+ $QuicktimeAudioCodecLookup['MAC6'] = 'Macintosh Audio Compression/Expansion (MACE) 6:1';
1575
+ $QuicktimeAudioCodecLookup['mixb'] = '8-bit Mixer';
1576
+ $QuicktimeAudioCodecLookup['mixw'] = '16-bit Mixer';
1577
+ $QuicktimeAudioCodecLookup['mp4a'] = 'ISO/IEC 14496-3 AAC';
1578
+ $QuicktimeAudioCodecLookup['MS'."\x00\x02"] = 'Microsoft ADPCM';
1579
+ $QuicktimeAudioCodecLookup['MS'."\x00\x11"] = 'DV IMA';
1580
+ $QuicktimeAudioCodecLookup['MS'."\x00\x55"] = 'Fraunhofer MPEG Layer III';
1581
+ $QuicktimeAudioCodecLookup['NONE'] = 'No Encoding';
1582
+ $QuicktimeAudioCodecLookup['Qclp'] = 'Qualcomm PureVoice';
1583
+ $QuicktimeAudioCodecLookup['QDM2'] = 'QDesign Music 2';
1584
+ $QuicktimeAudioCodecLookup['QDMC'] = 'QDesign Music 1';
1585
+ $QuicktimeAudioCodecLookup['ratb'] = '8-bit Rate';
1586
+ $QuicktimeAudioCodecLookup['ratw'] = '16-bit Rate';
1587
+ $QuicktimeAudioCodecLookup['raw '] = 'raw PCM';
1588
+ $QuicktimeAudioCodecLookup['sour'] = 'Sound Source';
1589
+ $QuicktimeAudioCodecLookup['sowt'] = 'signed/two\'s complement (Little Endian)';
1590
+ $QuicktimeAudioCodecLookup['str1'] = 'Iomega MPEG layer II';
1591
+ $QuicktimeAudioCodecLookup['str2'] = 'Iomega MPEG *layer II';
1592
+ $QuicktimeAudioCodecLookup['str3'] = 'Iomega MPEG **layer II';
1593
+ $QuicktimeAudioCodecLookup['str4'] = 'Iomega MPEG ***layer II';
1594
+ $QuicktimeAudioCodecLookup['twos'] = 'signed/two\'s complement (Big Endian)';
1595
+ $QuicktimeAudioCodecLookup['ulaw'] = 'mu-law 2:1';
1596
+ }
1597
+ return (isset($QuicktimeAudioCodecLookup[$codecid]) ? $QuicktimeAudioCodecLookup[$codecid] : '');
1598
+ }
1599
+
1600
+ function QuicktimeDCOMLookup($compressionid) {
1601
+ static $QuicktimeDCOMLookup = array();
1602
+ if (empty($QuicktimeDCOMLookup)) {
1603
+ $QuicktimeDCOMLookup['zlib'] = 'ZLib Deflate';
1604
+ $QuicktimeDCOMLookup['adec'] = 'Apple Compression';
1605
+ }
1606
+ return (isset($QuicktimeDCOMLookup[$compressionid]) ? $QuicktimeDCOMLookup[$compressionid] : '');
1607
+ }
1608
+
1609
+ function QuicktimeColorNameLookup($colordepthid) {
1610
+ static $QuicktimeColorNameLookup = array();
1611
+ if (empty($QuicktimeColorNameLookup)) {
1612
+ $QuicktimeColorNameLookup[1] = '2-color (monochrome)';
1613
+ $QuicktimeColorNameLookup[2] = '4-color';
1614
+ $QuicktimeColorNameLookup[4] = '16-color';
1615
+ $QuicktimeColorNameLookup[8] = '256-color';
1616
+ $QuicktimeColorNameLookup[16] = 'thousands (16-bit color)';
1617
+ $QuicktimeColorNameLookup[24] = 'millions (24-bit color)';
1618
+ $QuicktimeColorNameLookup[32] = 'millions+ (32-bit color)';
1619
+ $QuicktimeColorNameLookup[33] = 'black & white';
1620
+ $QuicktimeColorNameLookup[34] = '4-gray';
1621
+ $QuicktimeColorNameLookup[36] = '16-gray';
1622
+ $QuicktimeColorNameLookup[40] = '256-gray';
1623
+ }
1624
+ return (isset($QuicktimeColorNameLookup[$colordepthid]) ? $QuicktimeColorNameLookup[$colordepthid] : 'invalid');
1625
+ }
1626
+
1627
+ function QuicktimeSTIKLookup($stik) {
1628
+ static $QuicktimeSTIKLookup = array();
1629
+ if (empty($QuicktimeSTIKLookup)) {
1630
+ $QuicktimeSTIKLookup[0] = 'Movie';
1631
+ $QuicktimeSTIKLookup[1] = 'Normal';
1632
+ $QuicktimeSTIKLookup[2] = 'Audiobook';
1633
+ $QuicktimeSTIKLookup[5] = 'Whacked Bookmark';
1634
+ $QuicktimeSTIKLookup[6] = 'Music Video';
1635
+ $QuicktimeSTIKLookup[9] = 'Short Film';
1636
+ $QuicktimeSTIKLookup[10] = 'TV Show';
1637
+ $QuicktimeSTIKLookup[11] = 'Booklet';
1638
+ $QuicktimeSTIKLookup[14] = 'Ringtone';
1639
+ $QuicktimeSTIKLookup[21] = 'Podcast';
1640
+ }
1641
+ return (isset($QuicktimeSTIKLookup[$stik]) ? $QuicktimeSTIKLookup[$stik] : 'invalid');
1642
+ }
1643
+
1644
+ function QuicktimeIODSaudioProfileName($audio_profile_id) {
1645
+ static $QuicktimeIODSaudioProfileNameLookup = array();
1646
+ if (empty($QuicktimeIODSaudioProfileNameLookup)) {
1647
+ $QuicktimeIODSaudioProfileNameLookup = array(
1648
+ 0x00 => 'ISO Reserved (0x00)',
1649
+ 0x01 => 'Main Audio Profile @ Level 1',
1650
+ 0x02 => 'Main Audio Profile @ Level 2',
1651
+ 0x03 => 'Main Audio Profile @ Level 3',
1652
+ 0x04 => 'Main Audio Profile @ Level 4',
1653
+ 0x05 => 'Scalable Audio Profile @ Level 1',
1654
+ 0x06 => 'Scalable Audio Profile @ Level 2',
1655
+ 0x07 => 'Scalable Audio Profile @ Level 3',
1656
+ 0x08 => 'Scalable Audio Profile @ Level 4',
1657
+ 0x09 => 'Speech Audio Profile @ Level 1',
1658
+ 0x0A => 'Speech Audio Profile @ Level 2',
1659
+ 0x0B => 'Synthetic Audio Profile @ Level 1',
1660
+ 0x0C => 'Synthetic Audio Profile @ Level 2',
1661
+ 0x0D => 'Synthetic Audio Profile @ Level 3',
1662
+ 0x0E => 'High Quality Audio Profile @ Level 1',
1663
+ 0x0F => 'High Quality Audio Profile @ Level 2',
1664
+ 0x10 => 'High Quality Audio Profile @ Level 3',
1665
+ 0x11 => 'High Quality Audio Profile @ Level 4',
1666
+ 0x12 => 'High Quality Audio Profile @ Level 5',
1667
+ 0x13 => 'High Quality Audio Profile @ Level 6',
1668
+ 0x14 => 'High Quality Audio Profile @ Level 7',
1669
+ 0x15 => 'High Quality Audio Profile @ Level 8',
1670
+ 0x16 => 'Low Delay Audio Profile @ Level 1',
1671
+ 0x17 => 'Low Delay Audio Profile @ Level 2',
1672
+ 0x18 => 'Low Delay Audio Profile @ Level 3',
1673
+ 0x19 => 'Low Delay Audio Profile @ Level 4',
1674
+ 0x1A => 'Low Delay Audio Profile @ Level 5',
1675
+ 0x1B => 'Low Delay Audio Profile @ Level 6',
1676
+ 0x1C => 'Low Delay Audio Profile @ Level 7',
1677
+ 0x1D => 'Low Delay Audio Profile @ Level 8',
1678
+ 0x1E => 'Natural Audio Profile @ Level 1',
1679
+ 0x1F => 'Natural Audio Profile @ Level 2',
1680
+ 0x20 => 'Natural Audio Profile @ Level 3',
1681
+ 0x21 => 'Natural Audio Profile @ Level 4',
1682
+ 0x22 => 'Mobile Audio Internetworking Profile @ Level 1',
1683
+ 0x23 => 'Mobile Audio Internetworking Profile @ Level 2',
1684
+ 0x24 => 'Mobile Audio Internetworking Profile @ Level 3',
1685
+ 0x25 => 'Mobile Audio Internetworking Profile @ Level 4',
1686
+ 0x26 => 'Mobile Audio Internetworking Profile @ Level 5',
1687
+ 0x27 => 'Mobile Audio Internetworking Profile @ Level 6',
1688
+ 0x28 => 'AAC Profile @ Level 1',
1689
+ 0x29 => 'AAC Profile @ Level 2',
1690
+ 0x2A => 'AAC Profile @ Level 4',
1691
+ 0x2B => 'AAC Profile @ Level 5',
1692
+ 0x2C => 'High Efficiency AAC Profile @ Level 2',
1693
+ 0x2D => 'High Efficiency AAC Profile @ Level 3',
1694
+ 0x2E => 'High Efficiency AAC Profile @ Level 4',
1695
+ 0x2F => 'High Efficiency AAC Profile @ Level 5',
1696
+ 0xFE => 'Not part of MPEG-4 audio profiles',
1697
+ 0xFF => 'No audio capability required',
1698
+ );
1699
+ }
1700
+ return (isset($QuicktimeIODSaudioProfileNameLookup[$audio_profile_id]) ? $QuicktimeIODSaudioProfileNameLookup[$audio_profile_id] : 'ISO Reserved / User Private');
1701
+ }
1702
+
1703
+
1704
+ function QuicktimeIODSvideoProfileName($video_profile_id) {
1705
+ static $QuicktimeIODSvideoProfileNameLookup = array();
1706
+ if (empty($QuicktimeIODSvideoProfileNameLookup)) {
1707
+ $QuicktimeIODSvideoProfileNameLookup = array(
1708
+ 0x00 => 'Reserved (0x00) Profile',
1709
+ 0x01 => 'Simple Profile @ Level 1',
1710
+ 0x02 => 'Simple Profile @ Level 2',
1711
+ 0x03 => 'Simple Profile @ Level 3',
1712
+ 0x08 => 'Simple Profile @ Level 0',
1713
+ 0x10 => 'Simple Scalable Profile @ Level 0',
1714
+ 0x11 => 'Simple Scalable Profile @ Level 1',
1715
+ 0x12 => 'Simple Scalable Profile @ Level 2',
1716
+ 0x15 => 'AVC/H264 Profile',
1717
+ 0x21 => 'Core Profile @ Level 1',
1718
+ 0x22 => 'Core Profile @ Level 2',
1719
+ 0x32 => 'Main Profile @ Level 2',
1720
+ 0x33 => 'Main Profile @ Level 3',
1721
+ 0x34 => 'Main Profile @ Level 4',
1722
+ 0x42 => 'N-bit Profile @ Level 2',
1723
+ 0x51 => 'Scalable Texture Profile @ Level 1',
1724
+ 0x61 => 'Simple Face Animation Profile @ Level 1',
1725
+ 0x62 => 'Simple Face Animation Profile @ Level 2',
1726
+ 0x63 => 'Simple FBA Profile @ Level 1',
1727
+ 0x64 => 'Simple FBA Profile @ Level 2',
1728
+ 0x71 => 'Basic Animated Texture Profile @ Level 1',
1729
+ 0x72 => 'Basic Animated Texture Profile @ Level 2',
1730
+ 0x81 => 'Hybrid Profile @ Level 1',
1731
+ 0x82 => 'Hybrid Profile @ Level 2',
1732
+ 0x91 => 'Advanced Real Time Simple Profile @ Level 1',
1733
+ 0x92 => 'Advanced Real Time Simple Profile @ Level 2',
1734
+ 0x93 => 'Advanced Real Time Simple Profile @ Level 3',
1735
+ 0x94 => 'Advanced Real Time Simple Profile @ Level 4',
1736
+ 0xA1 => 'Core Scalable Profile @ Level1',
1737
+ 0xA2 => 'Core Scalable Profile @ Level2',
1738
+ 0xA3 => 'Core Scalable Profile @ Level3',
1739
+ 0xB1 => 'Advanced Coding Efficiency Profile @ Level 1',
1740
+ 0xB2 => 'Advanced Coding Efficiency Profile @ Level 2',
1741
+ 0xB3 => 'Advanced Coding Efficiency Profile @ Level 3',
1742
+ 0xB4 => 'Advanced Coding Efficiency Profile @ Level 4',
1743
+ 0xC1 => 'Advanced Core Profile @ Level 1',
1744
+ 0xC2 => 'Advanced Core Profile @ Level 2',
1745
+ 0xD1 => 'Advanced Scalable Texture @ Level1',
1746
+ 0xD2 => 'Advanced Scalable Texture @ Level2',
1747
+ 0xE1 => 'Simple Studio Profile @ Level 1',
1748
+ 0xE2 => 'Simple Studio Profile @ Level 2',
1749
+ 0xE3 => 'Simple Studio Profile @ Level 3',
1750
+ 0xE4 => 'Simple Studio Profile @ Level 4',
1751
+ 0xE5 => 'Core Studio Profile @ Level 1',
1752
+ 0xE6 => 'Core Studio Profile @ Level 2',
1753
+ 0xE7 => 'Core Studio Profile @ Level 3',
1754
+ 0xE8 => 'Core Studio Profile @ Level 4',
1755
+ 0xF0 => 'Advanced Simple Profile @ Level 0',
1756
+ 0xF1 => 'Advanced Simple Profile @ Level 1',
1757
+ 0xF2 => 'Advanced Simple Profile @ Level 2',
1758
+ 0xF3 => 'Advanced Simple Profile @ Level 3',
1759
+ 0xF4 => 'Advanced Simple Profile @ Level 4',
1760
+ 0xF5 => 'Advanced Simple Profile @ Level 5',
1761
+ 0xF7 => 'Advanced Simple Profile @ Level 3b',
1762
+ 0xF8 => 'Fine Granularity Scalable Profile @ Level 0',
1763
+ 0xF9 => 'Fine Granularity Scalable Profile @ Level 1',
1764
+ 0xFA => 'Fine Granularity Scalable Profile @ Level 2',
1765
+ 0xFB => 'Fine Granularity Scalable Profile @ Level 3',
1766
+ 0xFC => 'Fine Granularity Scalable Profile @ Level 4',
1767
+ 0xFD => 'Fine Granularity Scalable Profile @ Level 5',
1768
+ 0xFE => 'Not part of MPEG-4 Visual profiles',
1769
+ 0xFF => 'No visual capability required',
1770
+ );
1771
+ }
1772
+ return (isset($QuicktimeIODSvideoProfileNameLookup[$video_profile_id]) ? $QuicktimeIODSvideoProfileNameLookup[$video_profile_id] : 'ISO Reserved Profile');
1773
+ }
1774
+
1775
+
1776
+ function QuicktimeContentRatingLookup($rtng) {
1777
+ static $QuicktimeContentRatingLookup = array();
1778
+ if (empty($QuicktimeContentRatingLookup)) {
1779
+ $QuicktimeContentRatingLookup[0] = 'None';
1780
+ $QuicktimeContentRatingLookup[2] = 'Clean';
1781
+ $QuicktimeContentRatingLookup[4] = 'Explicit';
1782
+ }
1783
+ return (isset($QuicktimeContentRatingLookup[$rtng]) ? $QuicktimeContentRatingLookup[$rtng] : 'invalid');
1784
+ }
1785
+
1786
+ function QuicktimeStoreAccountTypeLookup($akid) {
1787
+ static $QuicktimeStoreAccountTypeLookup = array();
1788
+ if (empty($QuicktimeStoreAccountTypeLookup)) {
1789
+ $QuicktimeStoreAccountTypeLookup[0] = 'iTunes';
1790
+ $QuicktimeStoreAccountTypeLookup[1] = 'AOL';
1791
+ }
1792
+ return (isset($QuicktimeStoreAccountTypeLookup[$akid]) ? $QuicktimeStoreAccountTypeLookup[$akid] : 'invalid');
1793
+ }
1794
+
1795
+ function QuicktimeStoreFrontCodeLookup($sfid) {
1796
+ static $QuicktimeStoreFrontCodeLookup = array();
1797
+ if (empty($QuicktimeStoreFrontCodeLookup)) {
1798
+ $QuicktimeStoreFrontCodeLookup[143460] = 'Australia';
1799
+ $QuicktimeStoreFrontCodeLookup[143445] = 'Austria';
1800
+ $QuicktimeStoreFrontCodeLookup[143446] = 'Belgium';
1801
+ $QuicktimeStoreFrontCodeLookup[143455] = 'Canada';
1802
+ $QuicktimeStoreFrontCodeLookup[143458] = 'Denmark';
1803
+ $QuicktimeStoreFrontCodeLookup[143447] = 'Finland';
1804
+ $QuicktimeStoreFrontCodeLookup[143442] = 'France';
1805
+ $QuicktimeStoreFrontCodeLookup[143443] = 'Germany';
1806
+ $QuicktimeStoreFrontCodeLookup[143448] = 'Greece';
1807
+ $QuicktimeStoreFrontCodeLookup[143449] = 'Ireland';
1808
+ $QuicktimeStoreFrontCodeLookup[143450] = 'Italy';
1809
+ $QuicktimeStoreFrontCodeLookup[143462] = 'Japan';
1810
+ $QuicktimeStoreFrontCodeLookup[143451] = 'Luxembourg';
1811
+ $QuicktimeStoreFrontCodeLookup[143452] = 'Netherlands';
1812
+ $QuicktimeStoreFrontCodeLookup[143461] = 'New Zealand';
1813
+ $QuicktimeStoreFrontCodeLookup[143457] = 'Norway';
1814
+ $QuicktimeStoreFrontCodeLookup[143453] = 'Portugal';
1815
+ $QuicktimeStoreFrontCodeLookup[143454] = 'Spain';
1816
+ $QuicktimeStoreFrontCodeLookup[143456] = 'Sweden';
1817
+ $QuicktimeStoreFrontCodeLookup[143459] = 'Switzerland';
1818
+ $QuicktimeStoreFrontCodeLookup[143444] = 'United Kingdom';
1819
+ $QuicktimeStoreFrontCodeLookup[143441] = 'United States';
1820
+ }
1821
+ return (isset($QuicktimeStoreFrontCodeLookup[$sfid]) ? $QuicktimeStoreFrontCodeLookup[$sfid] : 'invalid');
1822
+ }
1823
+
1824
+ function QuicktimeParseNikonNCTG($atom_data) {
1825
+ // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html#NCTG
1826
+ // Nikon-specific QuickTime tags found in the NCDT atom of MOV videos from some Nikon cameras such as the Coolpix S8000 and D5100
1827
+ // Data is stored as records of:
1828
+ // * 4 bytes record type
1829
+ // * 2 bytes size of data field type:
1830
+ // 0x0001 = flag (size field *= 1-byte)
1831
+ // 0x0002 = char (size field *= 1-byte)
1832
+ // 0x0003 = DWORD+ (size field *= 2-byte), values are stored CDAB
1833
+ // 0x0004 = QWORD+ (size field *= 4-byte), values are stored EFGHABCD
1834
+ // 0x0005 = float (size field *= 8-byte), values are stored aaaabbbb where value is aaaa/bbbb; possibly multiple sets of values appended together
1835
+ // 0x0007 = bytes (size field *= 1-byte), values are stored as ??????
1836
+ // 0x0008 = ????? (size field *= 2-byte), values are stored as ??????
1837
+ // * 2 bytes data size field
1838
+ // * ? bytes data (string data may be null-padded; datestamp fields are in the format "2011:05:25 20:24:15")
1839
+ // all integers are stored BigEndian
1840
+
1841
+ $NCTGtagName = array(
1842
+ 0x00000001 => 'Make',
1843
+ 0x00000002 => 'Model',
1844
+ 0x00000003 => 'Software',
1845
+ 0x00000011 => 'CreateDate',
1846
+ 0x00000012 => 'DateTimeOriginal',
1847
+ 0x00000013 => 'FrameCount',
1848
+ 0x00000016 => 'FrameRate',
1849
+ 0x00000022 => 'FrameWidth',
1850
+ 0x00000023 => 'FrameHeight',
1851
+ 0x00000032 => 'AudioChannels',
1852
+ 0x00000033 => 'AudioBitsPerSample',
1853
+ 0x00000034 => 'AudioSampleRate',
1854
+ 0x02000001 => 'MakerNoteVersion',
1855
+ 0x02000005 => 'WhiteBalance',
1856
+ 0x0200000b => 'WhiteBalanceFineTune',
1857
+ 0x0200001e => 'ColorSpace',
1858
+ 0x02000023 => 'PictureControlData',
1859
+ 0x02000024 => 'WorldTime',
1860
+ 0x02000032 => 'UnknownInfo',
1861
+ 0x02000083 => 'LensType',
1862
+ 0x02000084 => 'Lens',
1863
+ );
1864
+
1865
+ $offset = 0;
1866
+ $datalength = strlen($atom_data);
1867
+ $parsed = array();
1868
+ while ($offset < $datalength) {
1869
+ //echo getid3_lib::PrintHexBytes(substr($atom_data, $offset, 4)).'<br>';
1870
+ $record_type = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 4)); $offset += 4;
1871
+ $data_size_type = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 2)); $offset += 2;
1872
+ $data_size = getid3_lib::BigEndian2Int(substr($atom_data, $offset, 2)); $offset += 2;
1873
+ switch ($data_size_type) {
1874
+ case 0x0001: // 0x0001 = flag (size field *= 1-byte)
1875
+ $data = getid3_lib::BigEndian2Int(substr($atom_data, $offset, $data_size * 1));
1876
+ $offset += ($data_size * 1);
1877
+ break;
1878
+ case 0x0002: // 0x0002 = char (size field *= 1-byte)
1879
+ $data = substr($atom_data, $offset, $data_size * 1);
1880
+ $offset += ($data_size * 1);
1881
+ $data = rtrim($data, "\x00");
1882
+ break;
1883
+ case 0x0003: // 0x0003 = DWORD+ (size field *= 2-byte), values are stored CDAB
1884
+ $data = '';
1885
+ for ($i = $data_size - 1; $i >= 0; $i--) {
1886
+ $data .= substr($atom_data, $offset + ($i * 2), 2);
1887
+ }
1888
+ $data = getid3_lib::BigEndian2Int($data);
1889
+ $offset += ($data_size * 2);
1890
+ break;
1891
+ case 0x0004: // 0x0004 = QWORD+ (size field *= 4-byte), values are stored EFGHABCD
1892
+ $data = '';
1893
+ for ($i = $data_size - 1; $i >= 0; $i--) {
1894
+ $data .= substr($atom_data, $offset + ($i * 4), 4);
1895
+ }
1896
+ $data = getid3_lib::BigEndian2Int($data);
1897
+ $offset += ($data_size * 4);
1898
+ break;
1899
+ case 0x0005: // 0x0005 = float (size field *= 8-byte), values are stored aaaabbbb where value is aaaa/bbbb; possibly multiple sets of values appended together
1900
+ $data = array();
1901
+ for ($i = 0; $i < $data_size; $i++) {
1902
+ $numerator = getid3_lib::BigEndian2Int(substr($atom_data, $offset + ($i * 8) + 0, 4));
1903
+ $denomninator = getid3_lib::BigEndian2Int(substr($atom_data, $offset + ($i * 8) + 4, 4));
1904
+ if ($denomninator == 0) {
1905
+ $data[$i] = false;
1906
+ } else {
1907
+ $data[$i] = (double) $numerator / $denomninator;
1908
+ }
1909
+ }
1910
+ $offset += (8 * $data_size);
1911
+ if (count($data) == 1) {
1912
+ $data = $data[0];
1913
+ }
1914
+ break;
1915
+ case 0x0007: // 0x0007 = bytes (size field *= 1-byte), values are stored as ??????
1916
+ $data = substr($atom_data, $offset, $data_size * 1);
1917
+ $offset += ($data_size * 1);
1918
+ break;
1919
+ case 0x0008: // 0x0008 = ????? (size field *= 2-byte), values are stored as ??????
1920
+ $data = substr($atom_data, $offset, $data_size * 2);
1921
+ $offset += ($data_size * 2);
1922
+ break;
1923
+ default:
1924
+ echo 'QuicktimeParseNikonNCTG()::unknown $data_size_type: '.$data_size_type.'<br>';
1925
+ break 2;
1926
+ }
1927
+
1928
+ switch ($record_type) {
1929
+ case 0x00000011: // CreateDate
1930
+ case 0x00000012: // DateTimeOriginal
1931
+ $data = strtotime($data);
1932
+ break;
1933
+ case 0x0200001e: // ColorSpace
1934
+ switch ($data) {
1935
+ case 1:
1936
+ $data = 'sRGB';
1937
+ break;
1938
+ case 2:
1939
+ $data = 'Adobe RGB';
1940
+ break;
1941
+ }
1942
+ break;
1943
+ case 0x02000023: // PictureControlData
1944
+ $PictureControlAdjust = array(0=>'default', 1=>'quick', 2=>'full');
1945
+ $FilterEffect = array(0x80=>'off', 0x81=>'yellow', 0x82=>'orange', 0x83=>'red', 0x84=>'green', 0xff=>'n/a');
1946
+ $ToningEffect = array(0x80=>'b&w', 0x81=>'sepia', 0x82=>'cyanotype', 0x83=>'red', 0x84=>'yellow', 0x85=>'green', 0x86=>'blue-green', 0x87=>'blue', 0x88=>'purple-blue', 0x89=>'red-purple', 0xff=>'n/a');
1947
+ $data = array(
1948
+ 'PictureControlVersion' => substr($data, 0, 4),
1949
+ 'PictureControlName' => rtrim(substr($data, 4, 20), "\x00"),
1950
+ 'PictureControlBase' => rtrim(substr($data, 24, 20), "\x00"),
1951
+ //'?' => substr($data, 44, 4),
1952
+ 'PictureControlAdjust' => $PictureControlAdjust[ord(substr($data, 48, 1))],
1953
+ 'PictureControlQuickAdjust' => ord(substr($data, 49, 1)),
1954
+ 'Sharpness' => ord(substr($data, 50, 1)),
1955
+ 'Contrast' => ord(substr($data, 51, 1)),
1956
+ 'Brightness' => ord(substr($data, 52, 1)),
1957
+ 'Saturation' => ord(substr($data, 53, 1)),
1958
+ 'HueAdjustment' => ord(substr($data, 54, 1)),
1959
+ 'FilterEffect' => $FilterEffect[ord(substr($data, 55, 1))],
1960
+ 'ToningEffect' => $ToningEffect[ord(substr($data, 56, 1))],
1961
+ 'ToningSaturation' => ord(substr($data, 57, 1)),
1962
+ );
1963
+ break;
1964
+ case 0x02000024: // WorldTime
1965
+ // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html#WorldTime
1966
+ // timezone is stored as offset from GMT in minutes
1967
+ $timezone = getid3_lib::BigEndian2Int(substr($data, 0, 2));
1968
+ if ($timezone & 0x8000) {
1969
+ $timezone = 0 - (0x10000 - $timezone);
1970
+ }
1971
+ $timezone /= 60;
1972
+
1973
+ $dst = (bool) getid3_lib::BigEndian2Int(substr($data, 2, 1));
1974
+ switch (getid3_lib::BigEndian2Int(substr($data, 3, 1))) {
1975
+ case 2:
1976
+ $datedisplayformat = 'D/M/Y'; break;
1977
+ case 1:
1978
+ $datedisplayformat = 'M/D/Y'; break;
1979
+ case 0:
1980
+ default:
1981
+ $datedisplayformat = 'Y/M/D'; break;
1982
+ }
1983
+
1984
+ $data = array('timezone'=>floatval($timezone), 'dst'=>$dst, 'display'=>$datedisplayformat);
1985
+ break;
1986
+ case 0x02000083: // LensType
1987
+ $data = array(
1988
+ //'_' => $data,
1989
+ 'mf' => (bool) ($data & 0x01),
1990
+ 'd' => (bool) ($data & 0x02),
1991
+ 'g' => (bool) ($data & 0x04),
1992
+ 'vr' => (bool) ($data & 0x08),
1993
+ );
1994
+ break;
1995
+ }
1996
+ $tag_name = (isset($NCTGtagName[$record_type]) ? $NCTGtagName[$record_type] : '0x'.str_pad(dechex($record_type), 8, '0', STR_PAD_LEFT));
1997
+ $parsed[$tag_name] = $data;
1998
+ }
1999
+ return $parsed;
2000
+ }
2001
+
2002
+
2003
+ function CopyToAppropriateCommentsSection($keyname, $data, $boxname='') {
2004
+ static $handyatomtranslatorarray = array();
2005
+ if (empty($handyatomtranslatorarray)) {
2006
+ $handyatomtranslatorarray['�cpy'] = 'copyright';
2007
+ $handyatomtranslatorarray['�day'] = 'creation_date'; // iTunes 4.0
2008
+ $handyatomtranslatorarray['�dir'] = 'director';
2009
+ $handyatomtranslatorarray['�ed1'] = 'edit1';
2010
+ $handyatomtranslatorarray['�ed2'] = 'edit2';
2011
+ $handyatomtranslatorarray['�ed3'] = 'edit3';
2012
+ $handyatomtranslatorarray['�ed4'] = 'edit4';
2013
+ $handyatomtranslatorarray['�ed5'] = 'edit5';
2014
+ $handyatomtranslatorarray['�ed6'] = 'edit6';
2015
+ $handyatomtranslatorarray['�ed7'] = 'edit7';
2016
+ $handyatomtranslatorarray['�ed8'] = 'edit8';
2017
+ $handyatomtranslatorarray['�ed9'] = 'edit9';
2018
+ $handyatomtranslatorarray['�fmt'] = 'format';
2019
+ $handyatomtranslatorarray['�inf'] = 'information';
2020
+ $handyatomtranslatorarray['�prd'] = 'producer';
2021
+ $handyatomtranslatorarray['�prf'] = 'performers';
2022
+ $handyatomtranslatorarray['�req'] = 'system_requirements';
2023
+ $handyatomtranslatorarray['�src'] = 'source_credit';
2024
+ $handyatomtranslatorarray['�wrt'] = 'writer';
2025
+
2026
+ // http://www.geocities.com/xhelmboyx/quicktime/formats/qtm-layout.txt
2027
+ $handyatomtranslatorarray['�nam'] = 'title'; // iTunes 4.0
2028
+ $handyatomtranslatorarray['�cmt'] = 'comment'; // iTunes 4.0
2029
+ $handyatomtranslatorarray['�wrn'] = 'warning';
2030
+ $handyatomtranslatorarray['�hst'] = 'host_computer';
2031
+ $handyatomtranslatorarray['�mak'] = 'make';
2032
+ $handyatomtranslatorarray['�mod'] = 'model';
2033
+ $handyatomtranslatorarray['�PRD'] = 'product';
2034
+ $handyatomtranslatorarray['�swr'] = 'software';
2035
+ $handyatomtranslatorarray['�aut'] = 'author';
2036
+ $handyatomtranslatorarray['�ART'] = 'artist';
2037
+ $handyatomtranslatorarray['�trk'] = 'track';
2038
+ $handyatomtranslatorarray['�alb'] = 'album'; // iTunes 4.0
2039
+ $handyatomtranslatorarray['�com'] = 'comment';
2040
+ $handyatomtranslatorarray['�gen'] = 'genre'; // iTunes 4.0
2041
+ $handyatomtranslatorarray['�ope'] = 'composer';
2042
+ $handyatomtranslatorarray['�url'] = 'url';
2043
+ $handyatomtranslatorarray['�enc'] = 'encoder';
2044
+
2045
+ // http://atomicparsley.sourceforge.net/mpeg-4files.html
2046
+ $handyatomtranslatorarray['�art'] = 'artist'; // iTunes 4.0
2047
+ $handyatomtranslatorarray['aART'] = 'album_artist';
2048
+ $handyatomtranslatorarray['trkn'] = 'track_number'; // iTunes 4.0
2049
+ $handyatomtranslatorarray['disk'] = 'disc_number'; // iTunes 4.0
2050
+ $handyatomtranslatorarray['gnre'] = 'genre'; // iTunes 4.0
2051
+ $handyatomtranslatorarray['�too'] = 'encoder'; // iTunes 4.0
2052
+ $handyatomtranslatorarray['tmpo'] = 'bpm'; // iTunes 4.0
2053
+ $handyatomtranslatorarray['cprt'] = 'copyright'; // iTunes 4.0?
2054
+ $handyatomtranslatorarray['cpil'] = 'compilation'; // iTunes 4.0
2055
+ $handyatomtranslatorarray['covr'] = 'picture'; // iTunes 4.0
2056
+ $handyatomtranslatorarray['rtng'] = 'rating'; // iTunes 4.0
2057
+ $handyatomtranslatorarray['�grp'] = 'grouping'; // iTunes 4.2
2058
+ $handyatomtranslatorarray['stik'] = 'stik'; // iTunes 4.9
2059
+ $handyatomtranslatorarray['pcst'] = 'podcast'; // iTunes 4.9
2060
+ $handyatomtranslatorarray['catg'] = 'category'; // iTunes 4.9
2061
+ $handyatomtranslatorarray['keyw'] = 'keyword'; // iTunes 4.9
2062
+ $handyatomtranslatorarray['purl'] = 'podcast_url'; // iTunes 4.9
2063
+ $handyatomtranslatorarray['egid'] = 'episode_guid'; // iTunes 4.9
2064
+ $handyatomtranslatorarray['desc'] = 'description'; // iTunes 5.0
2065
+ $handyatomtranslatorarray['�lyr'] = 'lyrics'; // iTunes 5.0
2066
+ $handyatomtranslatorarray['tvnn'] = 'tv_network_name'; // iTunes 6.0
2067
+ $handyatomtranslatorarray['tvsh'] = 'tv_show_name'; // iTunes 6.0
2068
+ $handyatomtranslatorarray['tvsn'] = 'tv_season'; // iTunes 6.0
2069
+ $handyatomtranslatorarray['tves'] = 'tv_episode'; // iTunes 6.0
2070
+ $handyatomtranslatorarray['purd'] = 'purchase_date'; // iTunes 6.0.2
2071
+ $handyatomtranslatorarray['pgap'] = 'gapless_playback'; // iTunes 7.0
2072
+
2073
+ // http://www.geocities.com/xhelmboyx/quicktime/formats/mp4-layout.txt
2074
+
2075
+
2076
+
2077
+ // boxnames:
2078
+ $handyatomtranslatorarray['iTunSMPB'] = 'iTunSMPB';
2079
+ $handyatomtranslatorarray['iTunNORM'] = 'iTunNORM';
2080
+ $handyatomtranslatorarray['Encoding Params'] = 'Encoding Params';
2081
+ $handyatomtranslatorarray['replaygain_track_gain'] = 'replaygain_track_gain';
2082
+ $handyatomtranslatorarray['replaygain_track_peak'] = 'replaygain_track_peak';
2083
+ $handyatomtranslatorarray['replaygain_track_minmax'] = 'replaygain_track_minmax';
2084
+ $handyatomtranslatorarray['MusicIP PUID'] = 'MusicIP PUID';
2085
+ $handyatomtranslatorarray['MusicBrainz Artist Id'] = 'MusicBrainz Artist Id';
2086
+ $handyatomtranslatorarray['MusicBrainz Album Id'] = 'MusicBrainz Album Id';
2087
+ $handyatomtranslatorarray['MusicBrainz Album Artist Id'] = 'MusicBrainz Album Artist Id';
2088
+ $handyatomtranslatorarray['MusicBrainz Track Id'] = 'MusicBrainz Track Id';
2089
+ $handyatomtranslatorarray['MusicBrainz Disc Id'] = 'MusicBrainz Disc Id';
2090
+ }
2091
+ $info = &$this->getid3->info;
2092
+ $comment_key = '';
2093
+ if ($boxname && ($boxname != $keyname) && isset($handyatomtranslatorarray[$boxname])) {
2094
+ $comment_key = $handyatomtranslatorarray[$boxname];
2095
+ } elseif (isset($handyatomtranslatorarray[$keyname])) {
2096
+ $comment_key = $handyatomtranslatorarray[$keyname];
2097
+ }
2098
+ if ($comment_key) {
2099
+ if ($comment_key == 'picture') {
2100
+ if (!is_array($data)) {
2101
+ $image_mime = '';
2102
+ if (preg_match('#^\x89\x50\x4E\x47\x0D\x0A\x1A\x0A#', $data)) {
2103
+ $image_mime = 'image/png';
2104
+ } elseif (preg_match('#^\xFF\xD8\xFF#', $data)) {
2105
+ $image_mime = 'image/jpeg';
2106
+ } elseif (preg_match('#^GIF#', $data)) {
2107
+ $image_mime = 'image/gif';
2108
+ } elseif (preg_match('#^BM#', $data)) {
2109
+ $image_mime = 'image/bmp';
2110
+ }
2111
+ $data = array('data'=>$data, 'image_mime'=>$image_mime);
2112
+ }
2113
+ }
2114
+ $info['quicktime']['comments'][$comment_key][] = $data;
2115
+ }
2116
+ return true;
2117
+ }
2118
+
2119
+ function NoNullString($nullterminatedstring) {
2120
+ // remove the single null terminator on null terminated strings
2121
+ if (substr($nullterminatedstring, strlen($nullterminatedstring) - 1, 1) === "\x00") {
2122
+ return substr($nullterminatedstring, 0, strlen($nullterminatedstring) - 1);
2123
+ }
2124
+ return $nullterminatedstring;
2125
+ }
2126
+
2127
+ function Pascal2String($pascalstring) {
2128
+ // Pascal strings have 1 unsigned byte at the beginning saying how many chars (1-255) are in the string
2129
+ return substr($pascalstring, 1);
2130
+ }
2131
+
2132
+ }
2133
+
2134
+ ?>
includes/lib/getid3/module.audio.aac.php ADDED
@@ -0,0 +1,515 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /////////////////////////////////////////////////////////////////
3
+ /// getID3() by James Heinrich <info@getid3.org> //
4
+ // available at http://getid3.sourceforge.net //
5
+ // or http://www.getid3.org //
6
+ /////////////////////////////////////////////////////////////////
7
+ // See readme.txt for more details //
8
+ /////////////////////////////////////////////////////////////////
9
+ // //
10
+ // module.audio.aac.php //
11
+ // module for analyzing AAC Audio files //
12
+ // dependencies: NONE //
13
+ // ///
14
+ /////////////////////////////////////////////////////////////////
15
+
16
+
17
+ class getid3_aac extends getid3_handler
18
+ {
19
+ function Analyze() {
20
+ $info = &$this->getid3->info;
21
+ fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
22
+ if (fread($this->getid3->fp, 4) == 'ADIF') {
23
+ $this->getAACADIFheaderFilepointer();
24
+ } else {
25
+ $this->getAACADTSheaderFilepointer();
26
+ }
27
+ return true;
28
+ }
29
+
30
+
31
+
32
+ function getAACADIFheaderFilepointer() {
33
+ $info = &$this->getid3->info;
34
+ $info['fileformat'] = 'aac';
35
+ $info['audio']['dataformat'] = 'aac';
36
+ $info['audio']['lossless'] = false;
37
+
38
+ fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
39
+ $AACheader = fread($this->getid3->fp, 1024);
40
+ $offset = 0;
41
+
42
+ if (substr($AACheader, 0, 4) == 'ADIF') {
43
+
44
+ // http://faac.sourceforge.net/wiki/index.php?page=ADIF
45
+
46
+ // http://libmpeg.org/mpeg4/doc/w2203tfs.pdf
47
+ // adif_header() {
48
+ // adif_id 32
49
+ // copyright_id_present 1
50
+ // if( copyright_id_present )
51
+ // copyright_id 72
52
+ // original_copy 1
53
+ // home 1
54
+ // bitstream_type 1
55
+ // bitrate 23
56
+ // num_program_config_elements 4
57
+ // for (i = 0; i < num_program_config_elements + 1; i++ ) {
58
+ // if( bitstream_type == '0' )
59
+ // adif_buffer_fullness 20
60
+ // program_config_element()
61
+ // }
62
+ // }
63
+
64
+ $AACheaderBitstream = getid3_lib::BigEndian2Bin($AACheader);
65
+ $bitoffset = 0;
66
+
67
+ $info['aac']['header_type'] = 'ADIF';
68
+ $bitoffset += 32;
69
+ $info['aac']['header']['mpeg_version'] = 4;
70
+
71
+ $info['aac']['header']['copyright'] = (bool) (substr($AACheaderBitstream, $bitoffset, 1) == '1');
72
+ $bitoffset += 1;
73
+ if ($info['aac']['header']['copyright']) {
74
+ $info['aac']['header']['copyright_id'] = getid3_lib::Bin2String(substr($AACheaderBitstream, $bitoffset, 72));
75
+ $bitoffset += 72;
76
+ }
77
+ $info['aac']['header']['original_copy'] = (bool) (substr($AACheaderBitstream, $bitoffset, 1) == '1');
78
+ $bitoffset += 1;
79
+ $info['aac']['header']['home'] = (bool) (substr($AACheaderBitstream, $bitoffset, 1) == '1');
80
+ $bitoffset += 1;
81
+ $info['aac']['header']['is_vbr'] = (bool) (substr($AACheaderBitstream, $bitoffset, 1) == '1');
82
+ $bitoffset += 1;
83
+ if ($info['aac']['header']['is_vbr']) {
84
+ $info['audio']['bitrate_mode'] = 'vbr';
85
+ $info['aac']['header']['bitrate_max'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 23));
86
+ $bitoffset += 23;
87
+ } else {
88
+ $info['audio']['bitrate_mode'] = 'cbr';
89
+ $info['aac']['header']['bitrate'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 23));
90
+ $bitoffset += 23;
91
+ $info['audio']['bitrate'] = $info['aac']['header']['bitrate'];
92
+ }
93
+ if ($info['audio']['bitrate'] == 0) {
94
+ $info['error'][] = 'Corrupt AAC file: bitrate_audio == zero';
95
+ return false;
96
+ }
97
+ $info['aac']['header']['num_program_configs'] = 1 + getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
98
+ $bitoffset += 4;
99
+
100
+ for ($i = 0; $i < $info['aac']['header']['num_program_configs']; $i++) {
101
+ // http://www.audiocoding.com/wiki/index.php?page=program_config_element
102
+
103
+ // buffer_fullness 20
104
+
105
+ // element_instance_tag 4
106
+ // object_type 2
107
+ // sampling_frequency_index 4
108
+ // num_front_channel_elements 4
109
+ // num_side_channel_elements 4
110
+ // num_back_channel_elements 4
111
+ // num_lfe_channel_elements 2
112
+ // num_assoc_data_elements 3
113
+ // num_valid_cc_elements 4
114
+ // mono_mixdown_present 1
115
+ // mono_mixdown_element_number 4 if mono_mixdown_present == 1
116
+ // stereo_mixdown_present 1
117
+ // stereo_mixdown_element_number 4 if stereo_mixdown_present == 1
118
+ // matrix_mixdown_idx_present 1
119
+ // matrix_mixdown_idx 2 if matrix_mixdown_idx_present == 1
120
+ // pseudo_surround_enable 1 if matrix_mixdown_idx_present == 1
121
+ // for (i = 0; i < num_front_channel_elements; i++) {
122
+ // front_element_is_cpe[i] 1
123
+ // front_element_tag_select[i] 4
124
+ // }
125
+ // for (i = 0; i < num_side_channel_elements; i++) {
126
+ // side_element_is_cpe[i] 1
127
+ // side_element_tag_select[i] 4
128
+ // }
129
+ // for (i = 0; i < num_back_channel_elements; i++) {
130
+ // back_element_is_cpe[i] 1
131
+ // back_element_tag_select[i] 4
132
+ // }
133
+ // for (i = 0; i < num_lfe_channel_elements; i++) {
134
+ // lfe_element_tag_select[i] 4
135
+ // }
136
+ // for (i = 0; i < num_assoc_data_elements; i++) {
137
+ // assoc_data_element_tag_select[i] 4
138
+ // }
139
+ // for (i = 0; i < num_valid_cc_elements; i++) {
140
+ // cc_element_is_ind_sw[i] 1
141
+ // valid_cc_element_tag_select[i] 4
142
+ // }
143
+ // byte_alignment() VAR
144
+ // comment_field_bytes 8
145
+ // for (i = 0; i < comment_field_bytes; i++) {
146
+ // comment_field_data[i] 8
147
+ // }
148
+
149
+ if (!$info['aac']['header']['is_vbr']) {
150
+ $info['aac']['program_configs'][$i]['buffer_fullness'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 20));
151
+ $bitoffset += 20;
152
+ }
153
+ $info['aac']['program_configs'][$i]['element_instance_tag'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
154
+ $bitoffset += 4;
155
+ $info['aac']['program_configs'][$i]['object_type'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 2));
156
+ $bitoffset += 2;
157
+ $info['aac']['program_configs'][$i]['sampling_frequency_index'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
158
+ $bitoffset += 4;
159
+ $info['aac']['program_configs'][$i]['num_front_channel_elements'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
160
+ $bitoffset += 4;
161
+ $info['aac']['program_configs'][$i]['num_side_channel_elements'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
162
+ $bitoffset += 4;
163
+ $info['aac']['program_configs'][$i]['num_back_channel_elements'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
164
+ $bitoffset += 4;
165
+ $info['aac']['program_configs'][$i]['num_lfe_channel_elements'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 2));
166
+ $bitoffset += 2;
167
+ $info['aac']['program_configs'][$i]['num_assoc_data_elements'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 3));
168
+ $bitoffset += 3;
169
+ $info['aac']['program_configs'][$i]['num_valid_cc_elements'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
170
+ $bitoffset += 4;
171
+ $info['aac']['program_configs'][$i]['mono_mixdown_present'] = (bool) getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1));
172
+ $bitoffset += 1;
173
+ if ($info['aac']['program_configs'][$i]['mono_mixdown_present']) {
174
+ $info['aac']['program_configs'][$i]['mono_mixdown_element_number'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
175
+ $bitoffset += 4;
176
+ }
177
+ $info['aac']['program_configs'][$i]['stereo_mixdown_present'] = (bool) getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1));
178
+ $bitoffset += 1;
179
+ if ($info['aac']['program_configs'][$i]['stereo_mixdown_present']) {
180
+ $info['aac']['program_configs'][$i]['stereo_mixdown_element_number'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
181
+ $bitoffset += 4;
182
+ }
183
+ $info['aac']['program_configs'][$i]['matrix_mixdown_idx_present'] = (bool) getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1));
184
+ $bitoffset += 1;
185
+ if ($info['aac']['program_configs'][$i]['matrix_mixdown_idx_present']) {
186
+ $info['aac']['program_configs'][$i]['matrix_mixdown_idx'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 2));
187
+ $bitoffset += 2;
188
+ $info['aac']['program_configs'][$i]['pseudo_surround_enable'] = (bool) getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1));
189
+ $bitoffset += 1;
190
+ }
191
+ for ($j = 0; $j < $info['aac']['program_configs'][$i]['num_front_channel_elements']; $j++) {
192
+ $info['aac']['program_configs'][$i]['front_element_is_cpe'][$j] = (bool) getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1));
193
+ $bitoffset += 1;
194
+ $info['aac']['program_configs'][$i]['front_element_tag_select'][$j] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
195
+ $bitoffset += 4;
196
+ }
197
+ for ($j = 0; $j < $info['aac']['program_configs'][$i]['num_side_channel_elements']; $j++) {
198
+ $info['aac']['program_configs'][$i]['side_element_is_cpe'][$j] = (bool) getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1));
199
+ $bitoffset += 1;
200
+ $info['aac']['program_configs'][$i]['side_element_tag_select'][$j] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
201
+ $bitoffset += 4;
202
+ }
203
+ for ($j = 0; $j < $info['aac']['program_configs'][$i]['num_back_channel_elements']; $j++) {
204
+ $info['aac']['program_configs'][$i]['back_element_is_cpe'][$j] = (bool) getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1));
205
+ $bitoffset += 1;
206
+ $info['aac']['program_configs'][$i]['back_element_tag_select'][$j] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
207
+ $bitoffset += 4;
208
+ }
209
+ for ($j = 0; $j < $info['aac']['program_configs'][$i]['num_lfe_channel_elements']; $j++) {
210
+ $info['aac']['program_configs'][$i]['lfe_element_tag_select'][$j] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
211
+ $bitoffset += 4;
212
+ }
213
+ for ($j = 0; $j < $info['aac']['program_configs'][$i]['num_assoc_data_elements']; $j++) {
214
+ $info['aac']['program_configs'][$i]['assoc_data_element_tag_select'][$j] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
215
+ $bitoffset += 4;
216
+ }
217
+ for ($j = 0; $j < $info['aac']['program_configs'][$i]['num_valid_cc_elements']; $j++) {
218
+ $info['aac']['program_configs'][$i]['cc_element_is_ind_sw'][$j] = (bool) getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1));
219
+ $bitoffset += 1;
220
+ $info['aac']['program_configs'][$i]['valid_cc_element_tag_select'][$j] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
221
+ $bitoffset += 4;
222
+ }
223
+
224
+ $bitoffset = ceil($bitoffset / 8) * 8;
225
+
226
+ $info['aac']['program_configs'][$i]['comment_field_bytes'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 8));
227
+ $bitoffset += 8;
228
+ $info['aac']['program_configs'][$i]['comment_field'] = getid3_lib::Bin2String(substr($AACheaderBitstream, $bitoffset, 8 * $info['aac']['program_configs'][$i]['comment_field_bytes']));
229
+ $bitoffset += 8 * $info['aac']['program_configs'][$i]['comment_field_bytes'];
230
+
231
+
232
+ $info['aac']['header']['profile'] = self::AACprofileLookup($info['aac']['program_configs'][$i]['object_type'], $info['aac']['header']['mpeg_version']);
233
+ $info['aac']['program_configs'][$i]['sampling_frequency'] = self::AACsampleRateLookup($info['aac']['program_configs'][$i]['sampling_frequency_index']);
234
+ $info['audio']['sample_rate'] = $info['aac']['program_configs'][$i]['sampling_frequency'];
235
+ $info['audio']['channels'] = self::AACchannelCountCalculate($info['aac']['program_configs'][$i]);
236
+ if ($info['aac']['program_configs'][$i]['comment_field']) {
237
+ $info['aac']['comments'][] = $info['aac']['program_configs'][$i]['comment_field'];
238
+ }
239
+ }
240
+ $info['playtime_seconds'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['audio']['bitrate'];
241
+
242
+ $info['audio']['encoder_options'] = $info['aac']['header_type'].' '.$info['aac']['header']['profile'];
243
+
244
+
245
+
246
+ return true;
247
+
248
+ } else {
249
+
250
+ unset($info['fileformat']);
251
+ unset($info['aac']);
252
+ $info['error'][] = 'AAC-ADIF synch not found at offset '.$info['avdataoffset'].' (expected "ADIF", found "'.substr($AACheader, 0, 4).'" instead)';
253
+ return false;
254
+
255
+ }
256
+
257
+ }
258
+
259
+
260
+ function getAACADTSheaderFilepointer($MaxFramesToScan=1000000, $ReturnExtendedInfo=false) {
261
+ $info = &$this->getid3->info;
262
+
263
+ // based loosely on code from AACfile by Jurgen Faul <jfaul�gmx.de>
264
+ // http://jfaul.de/atl or http://j-faul.virtualave.net/atl/atl.html
265
+
266
+
267
+ // http://faac.sourceforge.net/wiki/index.php?page=ADTS // dead link
268
+ // http://wiki.multimedia.cx/index.php?title=ADTS
269
+
270
+ // * ADTS Fixed Header: these don't change from frame to frame
271
+ // syncword 12 always: '111111111111'
272
+ // ID 1 0: MPEG-4, 1: MPEG-2
273
+ // MPEG layer 2 If you send AAC in MPEG-TS, set to 0
274
+ // protection_absent 1 0: CRC present; 1: no CRC
275
+ // profile 2 0: AAC Main; 1: AAC LC (Low Complexity); 2: AAC SSR (Scalable Sample Rate); 3: AAC LTP (Long Term Prediction)
276
+ // sampling_frequency_index 4 15 not allowed
277
+ // private_bit 1 usually 0
278
+ // channel_configuration 3
279
+ // original/copy 1 0: original; 1: copy
280
+ // home 1 usually 0
281
+ // emphasis 2 only if ID == 0 (ie MPEG-4) // not present in some documentation?
282
+
283
+ // * ADTS Variable Header: these can change from frame to frame
284
+ // copyright_identification_bit 1
285
+ // copyright_identification_start 1
286
+ // aac_frame_length 13 length of the frame including header (in bytes)
287
+ // adts_buffer_fullness 11 0x7FF indicates VBR
288
+ // no_raw_data_blocks_in_frame 2
289
+
290
+ // * ADTS Error check
291
+ // crc_check 16 only if protection_absent == 0
292
+
293
+ $byteoffset = $info['avdataoffset'];
294
+ $framenumber = 0;
295
+
296
+ // Init bit pattern array
297
+ static $decbin = array();
298
+
299
+ // Populate $bindec
300
+ for ($i = 0; $i < 256; $i++) {
301
+ $decbin[chr($i)] = str_pad(decbin($i), 8, '0', STR_PAD_LEFT);
302
+ }
303
+
304
+ // used to calculate bitrate below
305
+ $BitrateCache = array();
306
+
307
+
308
+ while (true) {
309
+ // breaks out when end-of-file encountered, or invalid data found,
310
+ // or MaxFramesToScan frames have been scanned
311
+
312
+ if (!getid3_lib::intValueSupported($byteoffset)) {
313
+ $info['warning'][] = 'Unable to parse AAC file beyond '.ftell($this->getid3->fp).' (PHP does not support file operations beyond '.round(PHP_INT_MAX / 1073741824).'GB)';
314
+ return false;
315
+ }
316
+ fseek($this->getid3->fp, $byteoffset, SEEK_SET);
317
+
318
+ // First get substring
319
+ $substring = fread($this->getid3->fp, 9); // header is 7 bytes (or 9 if CRC is present)
320
+ $substringlength = strlen($substring);
321
+ if ($substringlength != 9) {
322
+ $info['error'][] = 'Failed to read 7 bytes at offset '.(ftell($this->getid3->fp) - $substringlength).' (only read '.$substringlength.' bytes)';
323
+ return false;
324
+ }
325
+ // this would be easier with 64-bit math, but split it up to allow for 32-bit:
326
+ $header1 = getid3_lib::BigEndian2Int(substr($substring, 0, 2));
327
+ $header2 = getid3_lib::BigEndian2Int(substr($substring, 2, 4));
328
+ $header3 = getid3_lib::BigEndian2Int(substr($substring, 6, 1));
329
+
330
+ $info['aac']['header']['raw']['syncword'] = ($header1 & 0xFFF0) >> 4;
331
+ if ($info['aac']['header']['raw']['syncword'] != 0x0FFF) {
332
+ $info['error'][] = 'Synch pattern (0x0FFF) not found at offset '.(ftell($this->getid3->fp) - $substringlength).' (found 0x0'.strtoupper(dechex($info['aac']['header']['raw']['syncword'])).' instead)';
333
+ //if ($info['fileformat'] == 'aac') {
334
+ // return true;
335
+ //}
336
+ unset($info['aac']);
337
+ return false;
338
+ }
339
+
340
+ // Gather info for first frame only - this takes time to do 1000 times!
341
+ if ($framenumber == 0) {
342
+ $info['aac']['header_type'] = 'ADTS';
343
+ $info['fileformat'] = 'aac';
344
+ $info['audio']['dataformat'] = 'aac';
345
+
346
+ $info['aac']['header']['raw']['mpeg_version'] = ($header1 & 0x0008) >> 3;
347
+ $info['aac']['header']['raw']['mpeg_layer'] = ($header1 & 0x0006) >> 1;
348
+ $info['aac']['header']['raw']['protection_absent'] = ($header1 & 0x0001) >> 0;
349
+
350
+ $info['aac']['header']['raw']['profile_code'] = ($header2 & 0xC0000000) >> 30;
351
+ $info['aac']['header']['raw']['sample_rate_code'] = ($header2 & 0x3C000000) >> 26;
352
+ $info['aac']['header']['raw']['private_stream'] = ($header2 & 0x02000000) >> 25;
353
+ $info['aac']['header']['raw']['channels_code'] = ($header2 & 0x01C00000) >> 22;
354
+ $info['aac']['header']['raw']['original'] = ($header2 & 0x00200000) >> 21;
355
+ $info['aac']['header']['raw']['home'] = ($header2 & 0x00100000) >> 20;
356
+ $info['aac']['header']['raw']['copyright_stream'] = ($header2 & 0x00080000) >> 19;
357
+ $info['aac']['header']['raw']['copyright_start'] = ($header2 & 0x00040000) >> 18;
358
+ $info['aac']['header']['raw']['frame_length'] = ($header2 & 0x0003FFE0) >> 5;
359
+
360
+ $info['aac']['header']['mpeg_version'] = ($info['aac']['header']['raw']['mpeg_version'] ? 2 : 4);
361
+ $info['aac']['header']['crc_present'] = ($info['aac']['header']['raw']['protection_absent'] ? false: true);
362
+ $info['aac']['header']['profile'] = self::AACprofileLookup($info['aac']['header']['raw']['profile_code'], $info['aac']['header']['mpeg_version']);
363
+ $info['aac']['header']['sample_frequency'] = self::AACsampleRateLookup($info['aac']['header']['raw']['sample_rate_code']);
364
+ $info['aac']['header']['private'] = (bool) $info['aac']['header']['raw']['private_stream'];
365
+ $info['aac']['header']['original'] = (bool) $info['aac']['header']['raw']['original'];
366
+ $info['aac']['header']['home'] = (bool) $info['aac']['header']['raw']['home'];
367
+ $info['aac']['header']['channels'] = (($info['aac']['header']['raw']['channels_code'] == 7) ? 8 : $info['aac']['header']['raw']['channels_code']);
368
+ if ($ReturnExtendedInfo) {
369
+ $info['aac'][$framenumber]['copyright_id_bit'] = (bool) $info['aac']['header']['raw']['copyright_stream'];
370
+ $info['aac'][$framenumber]['copyright_id_start'] = (bool) $info['aac']['header']['raw']['copyright_start'];
371
+ }
372
+
373
+ if ($info['aac']['header']['raw']['mpeg_layer'] != 0) {
374
+ $info['warning'][] = 'Layer error - expected "0", found "'.$info['aac']['header']['raw']['mpeg_layer'].'" instead';
375
+ }
376
+ if ($info['aac']['header']['sample_frequency'] == 0) {
377
+ $info['error'][] = 'Corrupt AAC file: sample_frequency == zero';
378
+ return false;
379
+ }
380
+
381
+ $info['audio']['sample_rate'] = $info['aac']['header']['sample_frequency'];
382
+ $info['audio']['channels'] = $info['aac']['header']['channels'];
383
+ }
384
+
385
+ $FrameLength = ($header2 & 0x0003FFE0) >> 5;
386
+
387
+ if (!isset($BitrateCache[$FrameLength])) {
388
+ $BitrateCache[$FrameLength] = ($info['aac']['header']['sample_frequency'] / 1024) * $FrameLength * 8;
389
+ }
390
+ getid3_lib::safe_inc($info['aac']['bitrate_distribution'][$BitrateCache[$FrameLength]], 1);
391
+
392
+ $info['aac'][$framenumber]['aac_frame_length'] = $FrameLength;
393
+
394
+ $info['aac'][$framenumber]['adts_buffer_fullness'] = (($header2 & 0x0000001F) << 6) & (($header3 & 0xFC) >> 2);
395
+ if ($info['aac'][$framenumber]['adts_buffer_fullness'] == 0x07FF) {
396
+ $info['audio']['bitrate_mode'] = 'vbr';
397
+ } else {
398
+ $info['audio']['bitrate_mode'] = 'cbr';
399
+ }
400
+ $info['aac'][$framenumber]['num_raw_data_blocks'] = (($header3 & 0x03) >> 0);
401
+
402
+ if ($info['aac']['header']['crc_present']) {
403
+ //$info['aac'][$framenumber]['crc'] = getid3_lib::BigEndian2Int(substr($substring, 7, 2);
404
+ }
405
+
406
+ if (!$ReturnExtendedInfo) {
407
+ unset($info['aac'][$framenumber]);
408
+ }
409
+
410
+ /*
411
+ $rounded_precision = 5000;
412
+ $info['aac']['bitrate_distribution_rounded'] = array();
413
+ foreach ($info['aac']['bitrate_distribution'] as $bitrate => $count) {
414
+ $rounded_bitrate = round($bitrate / $rounded_precision) * $rounded_precision;
415
+ getid3_lib::safe_inc($info['aac']['bitrate_distribution_rounded'][$rounded_bitrate], $count);
416
+ }
417
+ ksort($info['aac']['bitrate_distribution_rounded']);
418
+ */
419
+
420
+ $byteoffset += $FrameLength;
421
+ if ((++$framenumber < $MaxFramesToScan) && (($byteoffset + 10) < $info['avdataend'])) {
422
+
423
+ // keep scanning
424
+
425
+ } else {
426
+
427
+ $info['aac']['frames'] = $framenumber;
428
+ $info['playtime_seconds'] = ($info['avdataend'] / $byteoffset) * (($framenumber * 1024) / $info['aac']['header']['sample_frequency']); // (1 / % of file scanned) * (samples / (samples/sec)) = seconds
429
+ if ($info['playtime_seconds'] == 0) {
430
+ $info['error'][] = 'Corrupt AAC file: playtime_seconds == zero';
431
+ return false;
432
+ }
433
+ $info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
434
+ ksort($info['aac']['bitrate_distribution']);
435
+
436
+ $info['audio']['encoder_options'] = $info['aac']['header_type'].' '.$info['aac']['header']['profile'];
437
+
438
+ return true;
439
+
440
+ }
441
+ }
442
+ // should never get here.
443
+ }
444
+
445
+ public static function AACsampleRateLookup($samplerateid) {
446
+ static $AACsampleRateLookup = array();
447
+ if (empty($AACsampleRateLookup)) {
448
+ $AACsampleRateLookup[0] = 96000;
449
+ $AACsampleRateLookup[1] = 88200;
450
+ $AACsampleRateLookup[2] = 64000;
451
+ $AACsampleRateLookup[3] = 48000;
452
+ $AACsampleRateLookup[4] = 44100;
453
+ $AACsampleRateLookup[5] = 32000;
454
+ $AACsampleRateLookup[6] = 24000;
455
+ $AACsampleRateLookup[7] = 22050;
456
+ $AACsampleRateLookup[8] = 16000;
457
+ $AACsampleRateLookup[9] = 12000;
458
+ $AACsampleRateLookup[10] = 11025;
459
+ $AACsampleRateLookup[11] = 8000;
460
+ $AACsampleRateLookup[12] = 0;
461
+ $AACsampleRateLookup[13] = 0;
462
+ $AACsampleRateLookup[14] = 0;
463
+ $AACsampleRateLookup[15] = 0;
464
+ }
465
+ return (isset($AACsampleRateLookup[$samplerateid]) ? $AACsampleRateLookup[$samplerateid] : 'invalid');
466
+ }
467
+
468
+ public static function AACprofileLookup($profileid, $mpegversion) {
469
+ static $AACprofileLookup = array();
470
+ if (empty($AACprofileLookup)) {
471
+ $AACprofileLookup[2][0] = 'Main profile';
472
+ $AACprofileLookup[2][1] = 'Low Complexity profile (LC)';
473
+ $AACprofileLookup[2][2] = 'Scalable Sample Rate profile (SSR)';
474
+ $AACprofileLookup[2][3] = '(reserved)';
475
+ $AACprofileLookup[4][0] = 'AAC_MAIN';
476
+ $AACprofileLookup[4][1] = 'AAC_LC';
477
+ $AACprofileLookup[4][2] = 'AAC_SSR';
478
+ $AACprofileLookup[4][3] = 'AAC_LTP';
479
+ }
480
+ return (isset($AACprofileLookup[$mpegversion][$profileid]) ? $AACprofileLookup[$mpegversion][$profileid] : 'invalid');
481
+ }
482
+
483
+ public static function AACchannelCountCalculate($program_configs) {
484
+ $channels = 0;
485
+ for ($i = 0; $i < $program_configs['num_front_channel_elements']; $i++) {
486
+ $channels++;
487
+ if ($program_configs['front_element_is_cpe'][$i]) {
488
+ // each front element is channel pair (CPE = Channel Pair Element)
489
+ $channels++;
490
+ }
491
+ }
492
+ for ($i = 0; $i < $program_configs['num_side_channel_elements']; $i++) {
493
+ $channels++;
494
+ if ($program_configs['side_element_is_cpe'][$i]) {
495
+ // each side element is channel pair (CPE = Channel Pair Element)
496
+ $channels++;
497
+ }
498
+ }
499
+ for ($i = 0; $i < $program_configs['num_back_channel_elements']; $i++) {
500
+ $channels++;
501
+ if ($program_configs['back_element_is_cpe'][$i]) {
502
+ // each back element is channel pair (CPE = Channel Pair Element)
503
+ $channels++;
504
+ }
505
+ }
506
+ for ($i = 0; $i < $program_configs['num_lfe_channel_elements']; $i++) {
507
+ $channels++;
508
+ }
509
+ return $channels;
510
+ }
511
+
512
+ }
513
+
514
+
515
+ ?>
includes/lib/getid3/module.audio.ac3.php ADDED
@@ -0,0 +1,473 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /////////////////////////////////////////////////////////////////
3
+ /// getID3() by James Heinrich <info@getid3.org> //
4
+ // available at http://getid3.sourceforge.net //
5
+ // or http://www.getid3.org //
6
+ /////////////////////////////////////////////////////////////////
7
+ // See readme.txt for more details //
8
+ /////////////////////////////////////////////////////////////////
9
+ // //
10
+ // module.audio.ac3.php //
11
+ // module for analyzing AC-3 (aka Dolby Digital) audio files //
12
+ // dependencies: NONE //
13
+ // ///
14
+ /////////////////////////////////////////////////////////////////
15
+
16
+
17
+ class getid3_ac3 extends getid3_handler
18
+ {
19
+ private $AC3header = '';
20
+ private $BSIoffset = 0;
21
+
22
+
23
+ public function Analyze() {
24
+ $info = &$this->getid3->info;
25
+
26
+ ///AH
27
+ $info['ac3']['raw']['bsi'] = array();
28
+ $thisfile_ac3 = &$info['ac3'];
29
+ $thisfile_ac3_raw = &$thisfile_ac3['raw'];
30
+ $thisfile_ac3_raw_bsi = &$thisfile_ac3_raw['bsi'];
31
+
32
+
33
+ // http://www.atsc.org/standards/a_52a.pdf
34
+
35
+ $info['fileformat'] = 'ac3';
36
+
37
+ // An AC-3 serial coded audio bit stream is made up of a sequence of synchronization frames
38
+ // Each synchronization frame contains 6 coded audio blocks (AB), each of which represent 256
39
+ // new audio samples per channel. A synchronization information (SI) header at the beginning
40
+ // of each frame contains information needed to acquire and maintain synchronization. A
41
+ // bit stream information (BSI) header follows SI, and contains parameters describing the coded
42
+ // audio service. The coded audio blocks may be followed by an auxiliary data (Aux) field. At the
43
+ // end of each frame is an error check field that includes a CRC word for error detection. An
44
+ // additional CRC word is located in the SI header, the use of which, by a decoder, is optional.
45
+ //
46
+ // syncinfo() | bsi() | AB0 | AB1 | AB2 | AB3 | AB4 | AB5 | Aux | CRC
47
+
48
+ fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
49
+ $this->AC3header['syncinfo'] = fread($this->getid3->fp, 5);
50
+ $thisfile_ac3_raw['synchinfo']['synchword'] = substr($this->AC3header['syncinfo'], 0, 2);
51
+
52
+ $magic = "\x0B\x77";
53
+ if ($thisfile_ac3_raw['synchinfo']['synchword'] != $magic) {
54
+ $info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes($magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($thisfile_ac3_raw['synchinfo']['synchword']).'"';
55
+ unset($info['fileformat'], $info['ac3']);
56
+ return false;
57
+ }
58
+
59
+ $info['audio']['dataformat'] = 'ac3';
60
+ $info['audio']['bitrate_mode'] = 'cbr';
61
+ $info['audio']['lossless'] = false;
62
+
63
+ // syncinfo() {
64
+ // syncword 16
65
+ // crc1 16
66
+ // fscod 2
67
+ // frmsizecod 6
68
+ // } /* end of syncinfo */
69
+
70
+ $thisfile_ac3_raw['synchinfo']['crc1'] = getid3_lib::LittleEndian2Int(substr($this->AC3header['syncinfo'], 2, 2));
71
+ $ac3_synchinfo_fscod_frmsizecod = getid3_lib::LittleEndian2Int(substr($this->AC3header['syncinfo'], 4, 1));
72
+ $thisfile_ac3_raw['synchinfo']['fscod'] = ($ac3_synchinfo_fscod_frmsizecod & 0xC0) >> 6;
73
+ $thisfile_ac3_raw['synchinfo']['frmsizecod'] = ($ac3_synchinfo_fscod_frmsizecod & 0x3F);
74
+
75
+ $thisfile_ac3['sample_rate'] = $this->AC3sampleRateCodeLookup($thisfile_ac3_raw['synchinfo']['fscod']);
76
+ if ($thisfile_ac3_raw['synchinfo']['fscod'] <= 3) {
77
+ $info['audio']['sample_rate'] = $thisfile_ac3['sample_rate'];
78
+ }
79
+
80
+ $thisfile_ac3['frame_length'] = $this->AC3frameSizeLookup($thisfile_ac3_raw['synchinfo']['frmsizecod'], $thisfile_ac3_raw['synchinfo']['fscod']);
81
+ $thisfile_ac3['bitrate'] = $this->AC3bitrateLookup($thisfile_ac3_raw['synchinfo']['frmsizecod']);
82
+ $info['audio']['bitrate'] = $thisfile_ac3['bitrate'];
83
+
84
+ $this->AC3header['bsi'] = getid3_lib::BigEndian2Bin(fread($this->getid3->fp, 15));
85
+ $ac3_bsi_offset = 0;
86
+
87
+ $thisfile_ac3_raw_bsi['bsid'] = $this->readHeaderBSI(5);
88
+ if ($thisfile_ac3_raw_bsi['bsid'] > 8) {
89
+ // Decoders which can decode version 8 will thus be able to decode version numbers less than 8.
90
+ // If this standard is extended by the addition of additional elements or features, a value of bsid greater than 8 will be used.
91
+ // Decoders built to this version of the standard will not be able to decode versions with bsid greater than 8.
92
+ $info['error'][] = 'Bit stream identification is version '.$thisfile_ac3_raw_bsi['bsid'].', but getID3() only understands up to version 8';
93
+ unset($thisfile_ac3);
94
+ return false;
95
+ }
96
+
97
+ $thisfile_ac3_raw_bsi['bsmod'] = $this->readHeaderBSI(3);
98
+ $thisfile_ac3_raw_bsi['acmod'] = $this->readHeaderBSI(3);
99
+
100
+ $thisfile_ac3['service_type'] = $this->AC3serviceTypeLookup($thisfile_ac3_raw_bsi['bsmod'], $thisfile_ac3_raw_bsi['acmod']);
101
+ $ac3_coding_mode = $this->AC3audioCodingModeLookup($thisfile_ac3_raw_bsi['acmod']);
102
+ foreach($ac3_coding_mode as $key => $value) {
103
+ $thisfile_ac3[$key] = $value;
104
+ }
105
+ switch ($thisfile_ac3_raw_bsi['acmod']) {
106
+ case 0:
107
+ case 1:
108
+ $info['audio']['channelmode'] = 'mono';
109
+ break;
110
+ case 3:
111
+ case 4:
112
+ $info['audio']['channelmode'] = 'stereo';
113
+ break;
114
+ default:
115
+ $info['audio']['channelmode'] = 'surround';
116
+ break;
117
+ }
118
+ $info['audio']['channels'] = $thisfile_ac3['num_channels'];
119
+
120
+ if ($thisfile_ac3_raw_bsi['acmod'] & 0x01) {
121
+ // If the lsb of acmod is a 1, center channel is in use and cmixlev follows in the bit stream.
122
+ $thisfile_ac3_raw_bsi['cmixlev'] = $this->readHeaderBSI(2);
123
+ $thisfile_ac3['center_mix_level'] = $this->AC3centerMixLevelLookup($thisfile_ac3_raw_bsi['cmixlev']);
124
+ }
125
+
126
+ if ($thisfile_ac3_raw_bsi['acmod'] & 0x04) {
127
+ // If the msb of acmod is a 1, surround channels are in use and surmixlev follows in the bit stream.
128
+ $thisfile_ac3_raw_bsi['surmixlev'] = $this->readHeaderBSI(2);
129
+ $thisfile_ac3['surround_mix_level'] = $this->AC3surroundMixLevelLookup($thisfile_ac3_raw_bsi['surmixlev']);
130
+ }
131
+
132
+ if ($thisfile_ac3_raw_bsi['acmod'] == 0x02) {
133
+ // When operating in the two channel mode, this 2-bit code indicates whether or not the program has been encoded in Dolby Surround.
134
+ $thisfile_ac3_raw_bsi['dsurmod'] = $this->readHeaderBSI(2);
135
+ $thisfile_ac3['dolby_surround_mode'] = $this->AC3dolbySurroundModeLookup($thisfile_ac3_raw_bsi['dsurmod']);
136
+ }
137
+
138
+ $thisfile_ac3_raw_bsi['lfeon'] = (bool) $this->readHeaderBSI(1);
139
+ $thisfile_ac3['lfe_enabled'] = $thisfile_ac3_raw_bsi['lfeon'];
140
+ if ($thisfile_ac3_raw_bsi['lfeon']) {
141
+ //$info['audio']['channels']++;
142
+ $info['audio']['channels'] .= '.1';
143
+ }
144
+
145
+ $thisfile_ac3['channels_enabled'] = $this->AC3channelsEnabledLookup($thisfile_ac3_raw_bsi['acmod'], $thisfile_ac3_raw_bsi['lfeon']);
146
+
147
+ // This indicates how far the average dialogue level is below digital 100 percent. Valid values are 1�31.
148
+ // The value of 0 is reserved. The values of 1 to 31 are interpreted as -1 dB to -31 dB with respect to digital 100 percent.
149
+ $thisfile_ac3_raw_bsi['dialnorm'] = $this->readHeaderBSI(5);
150
+ $thisfile_ac3['dialogue_normalization'] = '-'.$thisfile_ac3_raw_bsi['dialnorm'].'dB';
151
+
152
+ $thisfile_ac3_raw_bsi['compre_flag'] = (bool) $this->readHeaderBSI(1);
153
+ if ($thisfile_ac3_raw_bsi['compre_flag']) {
154
+ $thisfile_ac3_raw_bsi['compr'] = $this->readHeaderBSI(8);
155
+ $thisfile_ac3['heavy_compression'] = $this->AC3heavyCompression($thisfile_ac3_raw_bsi['compr']);
156
+ }
157
+
158
+ $thisfile_ac3_raw_bsi['langcode_flag'] = (bool) $this->readHeaderBSI(1);
159
+ if ($thisfile_ac3_raw_bsi['langcode_flag']) {
160
+ $thisfile_ac3_raw_bsi['langcod'] = $this->readHeaderBSI(8);
161
+ }
162
+
163
+ $thisfile_ac3_raw_bsi['audprodie'] = (bool) $this->readHeaderBSI(1);
164
+ if ($thisfile_ac3_raw_bsi['audprodie']) {
165
+ $thisfile_ac3_raw_bsi['mixlevel'] = $this->readHeaderBSI(5);
166
+ $thisfile_ac3_raw_bsi['roomtyp'] = $this->readHeaderBSI(2);
167
+
168
+ $thisfile_ac3['mixing_level'] = (80 + $thisfile_ac3_raw_bsi['mixlevel']).'dB';
169
+ $thisfile_ac3['room_type'] = $this->AC3roomTypeLookup($thisfile_ac3_raw_bsi['roomtyp']);
170
+ }
171
+
172
+ if ($thisfile_ac3_raw_bsi['acmod'] == 0x00) {
173
+ // If acmod is 0, then two completely independent program channels (dual mono)
174
+ // are encoded into the bit stream, and are referenced as Ch1, Ch2. In this case,
175
+ // a number of additional items are present in BSI or audblk to fully describe Ch2.
176
+
177
+ // This indicates how far the average dialogue level is below digital 100 percent. Valid values are 1�31.
178
+ // The value of 0 is reserved. The values of 1 to 31 are interpreted as -1 dB to -31 dB with respect to digital 100 percent.
179
+ $thisfile_ac3_raw_bsi['dialnorm2'] = $this->readHeaderBSI(5);
180
+ $thisfile_ac3['dialogue_normalization2'] = '-'.$thisfile_ac3_raw_bsi['dialnorm2'].'dB';
181
+
182
+ $thisfile_ac3_raw_bsi['compre_flag2'] = (bool) $this->readHeaderBSI(1);
183
+ if ($thisfile_ac3_raw_bsi['compre_flag2']) {
184
+ $thisfile_ac3_raw_bsi['compr2'] = $this->readHeaderBSI(8);
185
+ $thisfile_ac3['heavy_compression2'] = $this->AC3heavyCompression($thisfile_ac3_raw_bsi['compr2']);
186
+ }
187
+
188
+ $thisfile_ac3_raw_bsi['langcode_flag2'] = (bool) $this->readHeaderBSI(1);
189
+ if ($thisfile_ac3_raw_bsi['langcode_flag2']) {
190
+ $thisfile_ac3_raw_bsi['langcod2'] = $this->readHeaderBSI(8);
191
+ }
192
+
193
+ $thisfile_ac3_raw_bsi['audprodie2'] = (bool) $this->readHeaderBSI(1);
194
+ if ($thisfile_ac3_raw_bsi['audprodie2']) {
195
+ $thisfile_ac3_raw_bsi['mixlevel2'] = $this->readHeaderBSI(5);
196
+ $thisfile_ac3_raw_bsi['roomtyp2'] = $this->readHeaderBSI(2);
197
+
198
+ $thisfile_ac3['mixing_level2'] = (80 + $thisfile_ac3_raw_bsi['mixlevel2']).'dB';
199
+ $thisfile_ac3['room_type2'] = $this->AC3roomTypeLookup($thisfile_ac3_raw_bsi['roomtyp2']);
200
+ }
201
+
202
+ }
203
+
204
+ $thisfile_ac3_raw_bsi['copyright'] = (bool) $this->readHeaderBSI(1);
205
+
206
+ $thisfile_ac3_raw_bsi['original'] = (bool) $this->readHeaderBSI(1);
207
+
208
+ $thisfile_ac3_raw_bsi['timecode1_flag'] = (bool) $this->readHeaderBSI(1);
209
+ if ($thisfile_ac3_raw_bsi['timecode1_flag']) {
210
+ $thisfile_ac3_raw_bsi['timecode1'] = $this->readHeaderBSI(14);
211
+ }
212
+
213
+ $thisfile_ac3_raw_bsi['timecode2_flag'] = (bool) $this->readHeaderBSI(1);
214
+ if ($thisfile_ac3_raw_bsi['timecode2_flag']) {
215
+ $thisfile_ac3_raw_bsi['timecode2'] = $this->readHeaderBSI(14);
216
+ }
217
+
218
+ $thisfile_ac3_raw_bsi['addbsi_flag'] = (bool) $this->readHeaderBSI(1);
219
+ if ($thisfile_ac3_raw_bsi['addbsi_flag']) {
220
+ $thisfile_ac3_raw_bsi['addbsi_length'] = $this->readHeaderBSI(6);
221
+
222
+ $this->AC3header['bsi'] .= getid3_lib::BigEndian2Bin(fread($this->getid3->fp, $thisfile_ac3_raw_bsi['addbsi_length']));
223
+
224
+ $thisfile_ac3_raw_bsi['addbsi_data'] = substr($this->AC3header['bsi'], $this->BSIoffset, $thisfile_ac3_raw_bsi['addbsi_length'] * 8);
225
+ $this->BSIoffset += $thisfile_ac3_raw_bsi['addbsi_length'] * 8;
226
+ }
227
+
228
+ return true;
229
+ }
230
+
231
+ private function readHeaderBSI($length) {
232
+ $data = substr($this->AC3header['bsi'], $this->BSIoffset, $length);
233
+ $this->BSIoffset += $length;
234
+
235
+ return bindec($data);
236
+ }
237
+
238
+ public static function AC3sampleRateCodeLookup($fscod) {
239
+ static $AC3sampleRateCodeLookup = array(
240
+ 0 => 48000,
241
+ 1 => 44100,
242
+ 2 => 32000,
243
+ 3 => 'reserved' // If the reserved code is indicated, the decoder should not attempt to decode audio and should mute.
244
+ );
245
+ return (isset($AC3sampleRateCodeLookup[$fscod]) ? $AC3sampleRateCodeLookup[$fscod] : false);
246
+ }
247
+
248
+ public static function AC3serviceTypeLookup($bsmod, $acmod) {
249
+ static $AC3serviceTypeLookup = array();
250
+ if (empty($AC3serviceTypeLookup)) {
251
+ for ($i = 0; $i <= 7; $i++) {
252
+ $AC3serviceTypeLookup[0][$i] = 'main audio service: complete main (CM)';
253
+ $AC3serviceTypeLookup[1][$i] = 'main audio service: music and effects (ME)';
254
+ $AC3serviceTypeLookup[2][$i] = 'associated service: visually impaired (VI)';
255
+ $AC3serviceTypeLookup[3][$i] = 'associated service: hearing impaired (HI)';
256
+ $AC3serviceTypeLookup[4][$i] = 'associated service: dialogue (D)';
257
+ $AC3serviceTypeLookup[5][$i] = 'associated service: commentary (C)';
258
+ $AC3serviceTypeLookup[6][$i] = 'associated service: emergency (E)';
259
+ }
260
+
261
+ $AC3serviceTypeLookup[7][1] = 'associated service: voice over (VO)';
262
+ for ($i = 2; $i <= 7; $i++) {
263
+ $AC3serviceTypeLookup[7][$i] = 'main audio service: karaoke';
264
+ }
265
+ }
266
+ return (isset($AC3serviceTypeLookup[$bsmod][$acmod]) ? $AC3serviceTypeLookup[$bsmod][$acmod] : false);
267
+ }
268
+
269
+ public static function AC3audioCodingModeLookup($acmod) {
270
+ static $AC3audioCodingModeLookup = array();
271
+ if (empty($AC3audioCodingModeLookup)) {
272
+ // array(channel configuration, # channels (not incl LFE), channel order)
273
+ $AC3audioCodingModeLookup = array (
274
+ 0 => array('channel_config'=>'1+1', 'num_channels'=>2, 'channel_order'=>'Ch1,Ch2'),
275
+ 1 => array('channel_config'=>'1/0', 'num_channels'=>1, 'channel_order'=>'C'),
276
+ 2 => array('channel_config'=>'2/0', 'num_channels'=>2, 'channel_order'=>'L,R'),
277
+ 3 => array('channel_config'=>'3/0', 'num_channels'=>3, 'channel_order'=>'L,C,R'),
278
+ 4 => array('channel_config'=>'2/1', 'num_channels'=>3, 'channel_order'=>'L,R,S'),
279
+ 5 => array('channel_config'=>'3/1', 'num_channels'=>4, 'channel_order'=>'L,C,R,S'),
280
+ 6 => array('channel_config'=>'2/2', 'num_channels'=>4, 'channel_order'=>'L,R,SL,SR'),
281
+ 7 => array('channel_config'=>'3/2', 'num_channels'=>5, 'channel_order'=>'L,C,R,SL,SR')
282
+ );
283
+ }
284
+ return (isset($AC3audioCodingModeLookup[$acmod]) ? $AC3audioCodingModeLookup[$acmod] : false);
285
+ }
286
+
287
+ public static function AC3centerMixLevelLookup($cmixlev) {
288
+ static $AC3centerMixLevelLookup;
289
+ if (empty($AC3centerMixLevelLookup)) {
290
+ $AC3centerMixLevelLookup = array(
291
+ 0 => pow(2, -3.0 / 6), // 0.707 (�3.0 dB)
292
+ 1 => pow(2, -4.5 / 6), // 0.595 (�4.5 dB)
293
+ 2 => pow(2, -6.0 / 6), // 0.500 (�6.0 dB)
294
+ 3 => 'reserved'
295
+ );
296
+ }
297
+ return (isset($AC3centerMixLevelLookup[$cmixlev]) ? $AC3centerMixLevelLookup[$cmixlev] : false);
298
+ }
299
+
300
+ public static function AC3surroundMixLevelLookup($surmixlev) {
301
+ static $AC3surroundMixLevelLookup;
302
+ if (empty($AC3surroundMixLevelLookup)) {
303
+ $AC3surroundMixLevelLookup = array(
304
+ 0 => pow(2, -3.0 / 6),
305
+ 1 => pow(2, -6.0 / 6),
306
+ 2 => 0,
307
+ 3 => 'reserved'
308
+ );
309
+ }
310
+ return (isset($AC3surroundMixLevelLookup[$surmixlev]) ? $AC3surroundMixLevelLookup[$surmixlev] : false);
311
+ }
312
+
313
+ public static function AC3dolbySurroundModeLookup($dsurmod) {
314
+ static $AC3dolbySurroundModeLookup = array(
315
+ 0 => 'not indicated',
316
+ 1 => 'Not Dolby Surround encoded',
317
+ 2 => 'Dolby Surround encoded',
318
+ 3 => 'reserved'
319
+ );
320
+ return (isset($AC3dolbySurroundModeLookup[$dsurmod]) ? $AC3dolbySurroundModeLookup[$dsurmod] : false);
321
+ }
322
+
323
+ public static function AC3channelsEnabledLookup($acmod, $lfeon) {
324
+ $AC3channelsEnabledLookup = array(
325
+ 'ch1'=>(bool) ($acmod == 0),
326
+ 'ch2'=>(bool) ($acmod == 0),
327
+ 'left'=>(bool) ($acmod > 1),
328
+ 'right'=>(bool) ($acmod > 1),
329
+ 'center'=>(bool) ($acmod & 0x01),
330
+ 'surround_mono'=>false,
331
+ 'surround_left'=>false,
332
+ 'surround_right'=>false,
333
+ 'lfe'=>$lfeon);
334
+ switch ($acmod) {
335
+ case 4:
336
+ case 5:
337
+ $AC3channelsEnabledLookup['surround_mono'] = true;
338
+ break;
339
+ case 6:
340
+ case 7:
341
+ $AC3channelsEnabledLookup['surround_left'] = true;
342
+ $AC3channelsEnabledLookup['surround_right'] = true;
343
+ break;
344
+ }
345
+ return $AC3channelsEnabledLookup;
346
+ }
347
+
348
+ public static function AC3heavyCompression($compre) {
349
+ // The first four bits indicate gain changes in 6.02dB increments which can be
350
+ // implemented with an arithmetic shift operation. The following four bits
351
+ // indicate linear gain changes, and require a 5-bit multiply.
352
+ // We will represent the two 4-bit fields of compr as follows:
353
+ // X0 X1 X2 X3 . Y4 Y5 Y6 Y7
354
+ // The meaning of the X values is most simply described by considering X to represent a 4-bit
355
+ // signed integer with values from �8 to +7. The gain indicated by X is then (X + 1) * 6.02 dB. The
356
+ // following table shows this in detail.
357
+
358
+ // Meaning of 4 msb of compr
359
+ // 7 +48.16 dB
360
+ // 6 +42.14 dB
361
+ // 5 +36.12 dB
362
+ // 4 +30.10 dB
363
+ // 3 +24.08 dB
364
+ // 2 +18.06 dB
365
+ // 1 +12.04 dB
366
+ // 0 +6.02 dB
367
+ // -1 0 dB
368
+ // -2 �6.02 dB
369
+ // -3 �12.04 dB
370
+ // -4 �18.06 dB
371
+ // -5 �24.08 dB
372
+ // -6 �30.10 dB
373
+ // -7 �36.12 dB
374
+ // -8 �42.14 dB
375
+
376
+ $fourbit = str_pad(decbin(($compre & 0xF0) >> 4), 4, '0', STR_PAD_LEFT);
377
+ if ($fourbit{0} == '1') {
378
+ $log_gain = -8 + bindec(substr($fourbit, 1));
379
+ } else {
380
+ $log_gain = bindec(substr($fourbit, 1));
381
+ }
382
+ $log_gain = ($log_gain + 1) * getid3_lib::RGADamplitude2dB(2);
383
+
384
+ // The value of Y is a linear representation of a gain change of up to �6 dB. Y is considered to
385
+ // be an unsigned fractional integer, with a leading value of 1, or: 0.1 Y4 Y5 Y6 Y7 (base 2). Y can
386
+ // represent values between 0.111112 (or 31/32) and 0.100002 (or 1/2). Thus, Y can represent gain
387
+ // changes from �0.28 dB to �6.02 dB.
388
+
389
+ $lin_gain = (16 + ($compre & 0x0F)) / 32;
390
+
391
+ // The combination of X and Y values allows compr to indicate gain changes from
392
+ // 48.16 � 0.28 = +47.89 dB, to
393
+ // �42.14 � 6.02 = �48.16 dB.
394
+
395
+ return $log_gain - $lin_gain;
396
+ }
397
+
398
+ public static function AC3roomTypeLookup($roomtyp) {
399
+ static $AC3roomTypeLookup = array(
400
+ 0 => 'not indicated',
401
+ 1 => 'large room, X curve monitor',
402
+ 2 => 'small room, flat monitor',
403
+ 3 => 'reserved'
404
+ );
405
+ return (isset($AC3roomTypeLookup[$roomtyp]) ? $AC3roomTypeLookup[$roomtyp] : false);
406
+ }
407
+
408
+ public static function AC3frameSizeLookup($frmsizecod, $fscod) {
409
+ $padding = (bool) ($frmsizecod % 2);
410
+ $framesizeid = floor($frmsizecod / 2);
411
+
412
+ static $AC3frameSizeLookup = array();
413
+ if (empty($AC3frameSizeLookup)) {
414
+ $AC3frameSizeLookup = array (
415
+ 0 => array(128, 138, 192),
416
+ 1 => array(40, 160, 174, 240),
417
+ 2 => array(48, 192, 208, 288),
418
+ 3 => array(56, 224, 242, 336),
419
+ 4 => array(64, 256, 278, 384),
420
+ 5 => array(80, 320, 348, 480),
421
+ 6 => array(96, 384, 416, 576),
422
+ 7 => array(112, 448, 486, 672),
423
+ 8 => array(128, 512, 556, 768),
424
+ 9 => array(160, 640, 696, 960),
425
+ 10 => array(192, 768, 834, 1152),
426
+ 11 => array(224, 896, 974, 1344),
427
+ 12 => array(256, 1024, 1114, 1536),
428
+ 13 => array(320, 1280, 1392, 1920),
429
+ 14 => array(384, 1536, 1670, 2304),
430
+ 15 => array(448, 1792, 1950, 2688),
431
+ 16 => array(512, 2048, 2228, 3072),
432
+ 17 => array(576, 2304, 2506, 3456),
433
+ 18 => array(640, 2560, 2786, 3840)
434
+ );
435
+ }
436
+ if (($fscod == 1) && $padding) {
437
+ // frame lengths are padded by 1 word (16 bits) at 44100
438
+ $AC3frameSizeLookup[$frmsizecod] += 2;
439
+ }
440
+ return (isset($AC3frameSizeLookup[$framesizeid][$fscod]) ? $AC3frameSizeLookup[$framesizeid][$fscod] : false);
441
+ }
442
+
443
+ public static function AC3bitrateLookup($frmsizecod) {
444
+ $framesizeid = floor($frmsizecod / 2);
445
+
446
+ static $AC3bitrateLookup = array(
447
+ 0 => 32000,
448
+ 1 => 40000,
449
+ 2 => 48000,
450
+ 3 => 56000,
451
+ 4 => 64000,
452
+ 5 => 80000,
453
+ 6 => 96000,
454
+ 7 => 112000,
455
+ 8 => 128000,
456
+ 9 => 160000,
457
+ 10 => 192000,
458
+ 11 => 224000,
459
+ 12 => 256000,
460
+ 13 => 320000,
461
+ 14 => 384000,
462
+ 15 => 448000,
463
+ 16 => 512000,
464
+ 17 => 576000,
465
+ 18 => 640000
466
+ );
467
+ return (isset($AC3bitrateLookup[$framesizeid]) ? $AC3bitrateLookup[$framesizeid] : false);
468
+ }
469
+
470
+
471
+ }
472
+
473
+ ?>
includes/lib/getid3/module.audio.mp3.php ADDED
@@ -0,0 +1,2011 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /////////////////////////////////////////////////////////////////
3
+ /// getID3() by James Heinrich <info@getid3.org> //
4
+ // available at http://getid3.sourceforge.net //
5
+ // or http://www.getid3.org //
6
+ /////////////////////////////////////////////////////////////////
7
+ // See readme.txt for more details //
8
+ /////////////////////////////////////////////////////////////////
9
+ // //
10
+ // module.audio.mp3.php //
11
+ // module for analyzing MP3 files //
12
+ // dependencies: NONE //
13
+ // ///
14
+ /////////////////////////////////////////////////////////////////
15
+
16
+
17
+ // number of frames to scan to determine if MPEG-audio sequence is valid
18
+ // Lower this number to 5-20 for faster scanning
19
+ // Increase this number to 50+ for most accurate detection of valid VBR/CBR
20
+ // mpeg-audio streams
21
+ define('GETID3_MP3_VALID_CHECK_FRAMES', 35);
22
+
23
+
24
+ class getid3_mp3 extends getid3_handler
25
+ {
26
+
27
+ var $allow_bruteforce = false; // forces getID3() to scan the file byte-by-byte and log all the valid audio frame headers - extremely slow, unrecommended, but may provide data from otherwise-unusuable files
28
+
29
+ function Analyze() {
30
+ $info = &$this->getid3->info;
31
+
32
+ $initialOffset = $info['avdataoffset'];
33
+
34
+ if (!$this->getOnlyMPEGaudioInfo($info['avdataoffset'])) {
35
+ if ($this->allow_bruteforce) {
36
+ $info['error'][] = 'Rescanning file in BruteForce mode';
37
+ $this->getOnlyMPEGaudioInfoBruteForce($this->getid3->fp, $info);
38
+ }
39
+ }
40
+
41
+
42
+ if (isset($info['mpeg']['audio']['bitrate_mode'])) {
43
+ $info['audio']['bitrate_mode'] = strtolower($info['mpeg']['audio']['bitrate_mode']);
44
+ }
45
+
46
+ if (((isset($info['id3v2']['headerlength']) && ($info['avdataoffset'] > $info['id3v2']['headerlength'])) || (!isset($info['id3v2']) && ($info['avdataoffset'] > 0) && ($info['avdataoffset'] != $initialOffset)))) {
47
+
48
+ $synchoffsetwarning = 'Unknown data before synch ';
49
+ if (isset($info['id3v2']['headerlength'])) {
50
+ $synchoffsetwarning .= '(ID3v2 header ends at '.$info['id3v2']['headerlength'].', then '.($info['avdataoffset'] - $info['id3v2']['headerlength']).' bytes garbage, ';
51
+ } elseif ($initialOffset > 0) {
52
+ $synchoffsetwarning .= '(should be at '.$initialOffset.', ';
53
+ } else {
54
+ $synchoffsetwarning .= '(should be at beginning of file, ';
55
+ }
56
+ $synchoffsetwarning .= 'synch detected at '.$info['avdataoffset'].')';
57
+ if (isset($info['audio']['bitrate_mode']) && ($info['audio']['bitrate_mode'] == 'cbr')) {
58
+
59
+ if (!empty($info['id3v2']['headerlength']) && (($info['avdataoffset'] - $info['id3v2']['headerlength']) == $info['mpeg']['audio']['framelength'])) {
60
+
61
+ $synchoffsetwarning .= '. This is a known problem with some versions of LAME (3.90-3.92) DLL in CBR mode.';
62
+ $info['audio']['codec'] = 'LAME';
63
+ $CurrentDataLAMEversionString = 'LAME3.';
64
+
65
+ } elseif (empty($info['id3v2']['headerlength']) && ($info['avdataoffset'] == $info['mpeg']['audio']['framelength'])) {
66
+
67
+ $synchoffsetwarning .= '. This is a known problem with some versions of LAME (3.90 - 3.92) DLL in CBR mode.';
68
+ $info['audio']['codec'] = 'LAME';
69
+ $CurrentDataLAMEversionString = 'LAME3.';
70
+
71
+ }
72
+
73
+ }
74
+ $info['warning'][] = $synchoffsetwarning;
75
+
76
+ }
77
+
78
+ if (isset($info['mpeg']['audio']['LAME'])) {
79
+ $info['audio']['codec'] = 'LAME';
80
+ if (!empty($info['mpeg']['audio']['LAME']['long_version'])) {
81
+ $info['audio']['encoder'] = rtrim($info['mpeg']['audio']['LAME']['long_version'], "\x00");
82
+ } elseif (!empty($info['mpeg']['audio']['LAME']['short_version'])) {
83
+ $info['audio']['encoder'] = rtrim($info['mpeg']['audio']['LAME']['short_version'], "\x00");
84
+ }
85
+ }
86
+
87
+ $CurrentDataLAMEversionString = (!empty($CurrentDataLAMEversionString) ? $CurrentDataLAMEversionString : (isset($info['audio']['encoder']) ? $info['audio']['encoder'] : ''));
88
+ if (!empty($CurrentDataLAMEversionString) && (substr($CurrentDataLAMEversionString, 0, 6) == 'LAME3.') && !preg_match('[0-9\)]', substr($CurrentDataLAMEversionString, -1))) {
89
+ // a version number of LAME that does not end with a number like "LAME3.92"
90
+ // or with a closing parenthesis like "LAME3.88 (alpha)"
91
+ // or a version of LAME with the LAMEtag-not-filled-in-DLL-mode bug (3.90-3.92)
92
+
93
+ // not sure what the actual last frame length will be, but will be less than or equal to 1441
94
+ $PossiblyLongerLAMEversion_FrameLength = 1441;
95
+
96
+ // Not sure what version of LAME this is - look in padding of last frame for longer version string
97
+ $PossibleLAMEversionStringOffset = $info['avdataend'] - $PossiblyLongerLAMEversion_FrameLength;
98
+ fseek($this->getid3->fp, $PossibleLAMEversionStringOffset);
99
+ $PossiblyLongerLAMEversion_Data = fread($this->getid3->fp, $PossiblyLongerLAMEversion_FrameLength);
100
+ switch (substr($CurrentDataLAMEversionString, -1)) {
101
+ case 'a':
102
+ case 'b':
103
+ // "LAME3.94a" will have a longer version string of "LAME3.94 (alpha)" for example
104
+ // need to trim off "a" to match longer string
105
+ $CurrentDataLAMEversionString = substr($CurrentDataLAMEversionString, 0, -1);
106
+ break;
107
+ }
108
+ if (($PossiblyLongerLAMEversion_String = strstr($PossiblyLongerLAMEversion_Data, $CurrentDataLAMEversionString)) !== false) {
109
+ if (substr($PossiblyLongerLAMEversion_String, 0, strlen($CurrentDataLAMEversionString)) == $CurrentDataLAMEversionString) {
110
+ $PossiblyLongerLAMEversion_NewString = substr($PossiblyLongerLAMEversion_String, 0, strspn($PossiblyLongerLAMEversion_String, 'LAME0123456789., (abcdefghijklmnopqrstuvwxyzJFSOND)')); //"LAME3.90.3" "LAME3.87 (beta 1, Sep 27 2000)" "LAME3.88 (beta)"
111
+ if (empty($info['audio']['encoder']) || (strlen($PossiblyLongerLAMEversion_NewString) > strlen($info['audio']['encoder']))) {
112
+ $info['audio']['encoder'] = $PossiblyLongerLAMEversion_NewString;
113
+ }
114
+ }
115
+ }
116
+ }
117
+ if (!empty($info['audio']['encoder'])) {
118
+ $info['audio']['encoder'] = rtrim($info['audio']['encoder'], "\x00 ");
119
+ }
120
+
121
+ switch (isset($info['mpeg']['audio']['layer']) ? $info['mpeg']['audio']['layer'] : '') {
122
+ case 1:
123
+ case 2:
124
+ $info['audio']['dataformat'] = 'mp'.$info['mpeg']['audio']['layer'];
125
+ break;
126
+ }
127
+ if (isset($info['fileformat']) && ($info['fileformat'] == 'mp3')) {
128
+ switch ($info['audio']['dataformat']) {
129
+ case 'mp1':
130
+ case 'mp2':
131
+ case 'mp3':
132
+ $info['fileformat'] = $info['audio']['dataformat'];
133
+ break;
134
+
135
+ default:
136
+ $info['warning'][] = 'Expecting [audio][dataformat] to be mp1/mp2/mp3 when fileformat == mp3, [audio][dataformat] actually "'.$info['audio']['dataformat'].'"';
137
+ break;
138
+ }
139
+ }
140
+
141
+ if (empty($info['fileformat'])) {
142
+ unset($info['fileformat']);
143
+ unset($info['audio']['bitrate_mode']);
144
+ unset($info['avdataoffset']);
145
+ unset($info['avdataend']);
146
+ return false;
147
+ }
148
+
149
+ $info['mime_type'] = 'audio/mpeg';
150
+ $info['audio']['lossless'] = false;
151
+
152
+ // Calculate playtime
153
+ if (!isset($info['playtime_seconds']) && isset($info['audio']['bitrate']) && ($info['audio']['bitrate'] > 0)) {
154
+ $info['playtime_seconds'] = ($info['avdataend'] - $info['avdataoffset']) * 8 / $info['audio']['bitrate'];
155
+ }
156
+
157
+ $info['audio']['encoder_options'] = $this->GuessEncoderOptions();
158
+
159
+ return true;
160
+ }
161
+
162
+
163
+ function GuessEncoderOptions() {
164
+ // shortcuts
165
+ $info = &$this->getid3->info;
166
+ if (!empty($info['mpeg']['audio'])) {
167
+ $thisfile_mpeg_audio = &$info['mpeg']['audio'];
168
+ if (!empty($thisfile_mpeg_audio['LAME'])) {
169
+ $thisfile_mpeg_audio_lame = &$thisfile_mpeg_audio['LAME'];
170
+ }
171
+ }
172
+
173
+ $encoder_options = '';
174
+ static $NamedPresetBitrates = array(16, 24, 40, 56, 112, 128, 160, 192, 256);
175
+
176
+ if (isset($thisfile_mpeg_audio['VBR_method']) && ($thisfile_mpeg_audio['VBR_method'] == 'Fraunhofer') && !empty($thisfile_mpeg_audio['VBR_quality'])) {
177
+
178
+ $encoder_options = 'VBR q'.$thisfile_mpeg_audio['VBR_quality'];
179
+
180
+ } elseif (!empty($thisfile_mpeg_audio_lame['preset_used']) && (!in_array($thisfile_mpeg_audio_lame['preset_used_id'], $NamedPresetBitrates))) {
181
+
182
+ $encoder_options = $thisfile_mpeg_audio_lame['preset_used'];
183
+
184
+ } elseif (!empty($thisfile_mpeg_audio_lame['vbr_quality'])) {
185
+
186
+ static $KnownEncoderValues = array();
187
+ if (empty($KnownEncoderValues)) {
188
+
189
+ //$KnownEncoderValues[abrbitrate_minbitrate][vbr_quality][raw_vbr_method][raw_noise_shaping][raw_stereo_mode][ath_type][lowpass_frequency] = 'preset name';
190
+ $KnownEncoderValues[0xFF][58][1][1][3][2][20500] = '--alt-preset insane'; // 3.90, 3.90.1, 3.92
191
+ $KnownEncoderValues[0xFF][58][1][1][3][2][20600] = '--alt-preset insane'; // 3.90.2, 3.90.3, 3.91
192
+ $KnownEncoderValues[0xFF][57][1][1][3][4][20500] = '--alt-preset insane'; // 3.94, 3.95
193
+ $KnownEncoderValues['**'][78][3][2][3][2][19500] = '--alt-preset extreme'; // 3.90, 3.90.1, 3.92
194
+ $KnownEncoderValues['**'][78][3][2][3][2][19600] = '--alt-preset extreme'; // 3.90.2, 3.91
195
+ $KnownEncoderValues['**'][78][3][1][3][2][19600] = '--alt-preset extreme'; // 3.90.3
196
+ $KnownEncoderValues['**'][78][4][2][3][2][19500] = '--alt-preset fast extreme'; // 3.90, 3.90.1, 3.92
197
+ $KnownEncoderValues['**'][78][4][2][3][2][19600] = '--alt-preset fast extreme'; // 3.90.2, 3.90.3, 3.91
198
+ $KnownEncoderValues['**'][78][3][2][3][4][19000] = '--alt-preset standard'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92
199
+ $KnownEncoderValues['**'][78][3][1][3][4][19000] = '--alt-preset standard'; // 3.90.3
200
+ $KnownEncoderValues['**'][78][4][2][3][4][19000] = '--alt-preset fast standard'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92
201
+ $KnownEncoderValues['**'][78][4][1][3][4][19000] = '--alt-preset fast standard'; // 3.90.3
202
+ $KnownEncoderValues['**'][88][4][1][3][3][19500] = '--r3mix'; // 3.90, 3.90.1, 3.92
203
+ $KnownEncoderValues['**'][88][4][1][3][3][19600] = '--r3mix'; // 3.90.2, 3.90.3, 3.91
204
+ $KnownEncoderValues['**'][67][4][1][3][4][18000] = '--r3mix'; // 3.94, 3.95
205
+ $KnownEncoderValues['**'][68][3][2][3][4][18000] = '--alt-preset medium'; // 3.90.3
206
+ $KnownEncoderValues['**'][68][4][2][3][4][18000] = '--alt-preset fast medium'; // 3.90.3
207
+
208
+ $KnownEncoderValues[0xFF][99][1][1][1][2][0] = '--preset studio'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92
209
+ $KnownEncoderValues[0xFF][58][2][1][3][2][20600] = '--preset studio'; // 3.90.3, 3.93.1
210
+ $KnownEncoderValues[0xFF][58][2][1][3][2][20500] = '--preset studio'; // 3.93
211
+ $KnownEncoderValues[0xFF][57][2][1][3][4][20500] = '--preset studio'; // 3.94, 3.95
212
+ $KnownEncoderValues[0xC0][88][1][1][1][2][0] = '--preset cd'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92
213
+ $KnownEncoderValues[0xC0][58][2][2][3][2][19600] = '--preset cd'; // 3.90.3, 3.93.1
214
+ $KnownEncoderValues[0xC0][58][2][2][3][2][19500] = '--preset cd'; // 3.93
215
+ $KnownEncoderValues[0xC0][57][2][1][3][4][19500] = '--preset cd'; // 3.94, 3.95
216
+ $KnownEncoderValues[0xA0][78][1][1][3][2][18000] = '--preset hifi'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92
217
+ $KnownEncoderValues[0xA0][58][2][2][3][2][18000] = '--preset hifi'; // 3.90.3, 3.93, 3.93.1
218
+ $KnownEncoderValues[0xA0][57][2][1][3][4][18000] = '--preset hifi'; // 3.94, 3.95
219
+ $KnownEncoderValues[0x80][67][1][1][3][2][18000] = '--preset tape'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92
220
+ $KnownEncoderValues[0x80][67][1][1][3][2][15000] = '--preset radio'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92
221
+ $KnownEncoderValues[0x70][67][1][1][3][2][15000] = '--preset fm'; // 3.90, 3.90.1, 3.90.2, 3.91, 3.92
222
+ $KnownEncoderValues[0x70][58][2][2][3][2][16000] = '--preset tape/radio/fm'; // 3.90.3, 3.93, 3.93.1
223
+ $KnownEncoderValues[0x70][57][2][1][3][4][16000] = '--preset tape/radio/fm'; // 3.94, 3.95
224
+ $KnownEncoderValues[0x38][58][2][2][0][2][10000] = '--preset voice'; // 3.90.3, 3.93, 3.93.1
225
+ $KnownEncoderValues[0x38][57][2][1][0][4][15000] = '--preset voice'; // 3.94, 3.95
226
+ $KnownEncoderValues[0x38][57][2][1][0][4][16000] = '--preset voice'; // 3.94a14
227
+ $KnownEncoderValues[0x28][65][1][1][0][2][7500] = '--preset mw-us'; // 3.90, 3.90.1, 3.92
228
+ $KnownEncoderValues[0x28][65][1][1][0][2][7600] = '--preset mw-us'; // 3.90.2, 3.91
229
+ $KnownEncoderValues[0x28][58][2][2][0][2][7000] = '--preset mw-us'; // 3.90.3, 3.93, 3.93.1
230
+ $KnownEncoderValues[0x28][57][2][1][0][4][10500] = '--preset mw-us'; // 3.94, 3.95
231
+ $KnownEncoderValues[0x28][57][2][1][0][4][11200] = '--preset mw-us'; // 3.94a14
232
+ $KnownEncoderValues[0x28][57][2][1][0][4][8800] = '--preset mw-us'; // 3.94a15
233
+ $KnownEncoderValues[0x18][58][2][2][0][2][4000] = '--preset phon+/lw/mw-eu/sw'; // 3.90.3, 3.93.1
234
+ $KnownEncoderValues[0x18][58][2][2][0][2][3900] = '--preset phon+/lw/mw-eu/sw'; // 3.93
235
+ $KnownEncoderValues[0x18][57][2][1][0][4][5900] = '--preset phon+/lw/mw-eu/sw'; // 3.94, 3.95
236
+ $KnownEncoderValues[0x18][57][2][1][0][4][6200] = '--preset phon+/lw/mw-eu/sw'; // 3.94a14
237
+ $KnownEncoderValues[0x18][57][2][1][0][4][3200] = '--preset phon+/lw/mw-eu/sw'; // 3.94a15
238
+ $KnownEncoderValues[0x10][58][2][2][0][2][3800] = '--preset phone'; // 3.90.3, 3.93.1
239
+ $KnownEncoderValues[0x10][58][2][2][0][2][3700] = '--preset phone'; // 3.93
240
+ $KnownEncoderValues[0x10][57][2][1][0][4][5600] = '--preset phone'; // 3.94, 3.95
241
+ }
242
+
243
+ if (isset($KnownEncoderValues[$thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate']][$thisfile_mpeg_audio_lame['vbr_quality']][$thisfile_mpeg_audio_lame['raw']['vbr_method']][$thisfile_mpeg_audio_lame['raw']['noise_shaping']][$thisfile_mpeg_audio_lame['raw']['stereo_mode']][$thisfile_mpeg_audio_lame['ath_type']][$thisfile_mpeg_audio_lame['lowpass_frequency']])) {
244
+
245
+ $encoder_options = $KnownEncoderValues[$thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate']][$thisfile_mpeg_audio_lame['vbr_quality']][$thisfile_mpeg_audio_lame['raw']['vbr_method']][$thisfile_mpeg_audio_lame['raw']['noise_shaping']][$thisfile_mpeg_audio_lame['raw']['stereo_mode']][$thisfile_mpeg_audio_lame['ath_type']][$thisfile_mpeg_audio_lame['lowpass_frequency']];
246
+
247
+ } elseif (isset($KnownEncoderValues['**'][$thisfile_mpeg_audio_lame['vbr_quality']][$thisfile_mpeg_audio_lame['raw']['vbr_method']][$thisfile_mpeg_audio_lame['raw']['noise_shaping']][$thisfile_mpeg_audio_lame['raw']['stereo_mode']][$thisfile_mpeg_audio_lame['ath_type']][$thisfile_mpeg_audio_lame['lowpass_frequency']])) {
248
+
249
+ $encoder_options = $KnownEncoderValues['**'][$thisfile_mpeg_audio_lame['vbr_quality']][$thisfile_mpeg_audio_lame['raw']['vbr_method']][$thisfile_mpeg_audio_lame['raw']['noise_shaping']][$thisfile_mpeg_audio_lame['raw']['stereo_mode']][$thisfile_mpeg_audio_lame['ath_type']][$thisfile_mpeg_audio_lame['lowpass_frequency']];
250
+
251
+ } elseif ($info['audio']['bitrate_mode'] == 'vbr') {
252
+
253
+ // http://gabriel.mp3-tech.org/mp3infotag.html
254
+ // int Quality = (100 - 10 * gfp->VBR_q - gfp->quality)h
255
+
256
+
257
+ $LAME_V_value = 10 - ceil($thisfile_mpeg_audio_lame['vbr_quality'] / 10);
258
+ $LAME_q_value = 100 - $thisfile_mpeg_audio_lame['vbr_quality'] - ($LAME_V_value * 10);
259
+ $encoder_options = '-V'.$LAME_V_value.' -q'.$LAME_q_value;
260
+
261
+ } elseif ($info['audio']['bitrate_mode'] == 'cbr') {
262
+
263
+ $encoder_options = strtoupper($info['audio']['bitrate_mode']).ceil($info['audio']['bitrate'] / 1000);
264
+
265
+ } else {
266
+
267
+ $encoder_options = strtoupper($info['audio']['bitrate_mode']);
268
+
269
+ }
270
+
271
+ } elseif (!empty($thisfile_mpeg_audio_lame['bitrate_abr'])) {
272
+
273
+ $encoder_options = 'ABR'.$thisfile_mpeg_audio_lame['bitrate_abr'];
274
+
275
+ } elseif (!empty($info['audio']['bitrate'])) {
276
+
277
+ if ($info['audio']['bitrate_mode'] == 'cbr') {
278
+ $encoder_options = strtoupper($info['audio']['bitrate_mode']).ceil($info['audio']['bitrate'] / 1000);
279
+ } else {
280
+ $encoder_options = strtoupper($info['audio']['bitrate_mode']);
281
+ }
282
+
283
+ }
284
+ if (!empty($thisfile_mpeg_audio_lame['bitrate_min'])) {
285
+ $encoder_options .= ' -b'.$thisfile_mpeg_audio_lame['bitrate_min'];
286
+ }
287
+
288
+ if (!empty($thisfile_mpeg_audio_lame['encoding_flags']['nogap_prev']) || !empty($thisfile_mpeg_audio_lame['encoding_flags']['nogap_next'])) {
289
+ $encoder_options .= ' --nogap';
290
+ }
291
+
292
+ if (!empty($thisfile_mpeg_audio_lame['lowpass_frequency'])) {
293
+ $ExplodedOptions = explode(' ', $encoder_options, 4);
294
+ if ($ExplodedOptions[0] == '--r3mix') {
295
+ $ExplodedOptions[1] = 'r3mix';
296
+ }
297
+ switch ($ExplodedOptions[0]) {
298
+ case '--preset':
299
+ case '--alt-preset':
300
+ case '--r3mix':
301
+ if ($ExplodedOptions[1] == 'fast') {
302
+ $ExplodedOptions[1] .= ' '.$ExplodedOptions[2];
303
+ }
304
+ switch ($ExplodedOptions[1]) {
305
+ case 'portable':
306
+ case 'medium':
307
+ case 'standard':
308
+ case 'extreme':
309
+ case 'insane':
310
+ case 'fast portable':
311
+ case 'fast medium':
312
+ case 'fast standard':
313
+ case 'fast extreme':
314
+ case 'fast insane':
315
+ case 'r3mix':
316
+ static $ExpectedLowpass = array(
317
+ 'insane|20500' => 20500,
318
+ 'insane|20600' => 20600, // 3.90.2, 3.90.3, 3.91
319
+ 'medium|18000' => 18000,
320
+ 'fast medium|18000' => 18000,
321
+ 'extreme|19500' => 19500, // 3.90, 3.90.1, 3.92, 3.95
322
+ 'extreme|19600' => 19600, // 3.90.2, 3.90.3, 3.91, 3.93.1
323
+ 'fast extreme|19500' => 19500, // 3.90, 3.90.1, 3.92, 3.95
324
+ 'fast extreme|19600' => 19600, // 3.90.2, 3.90.3, 3.91, 3.93.1
325
+ 'standard|19000' => 19000,
326
+ 'fast standard|19000' => 19000,
327
+ 'r3mix|19500' => 19500, // 3.90, 3.90.1, 3.92
328
+ 'r3mix|19600' => 19600, // 3.90.2, 3.90.3, 3.91
329
+ 'r3mix|18000' => 18000, // 3.94, 3.95
330
+ );
331
+ if (!isset($ExpectedLowpass[$ExplodedOptions[1].'|'.$thisfile_mpeg_audio_lame['lowpass_frequency']]) && ($thisfile_mpeg_audio_lame['lowpass_frequency'] < 22050) && (round($thisfile_mpeg_audio_lame['lowpass_frequency'] / 1000) < round($thisfile_mpeg_audio['sample_rate'] / 2000))) {
332
+ $encoder_options .= ' --lowpass '.$thisfile_mpeg_audio_lame['lowpass_frequency'];
333
+ }
334
+ break;
335
+
336
+ default:
337
+ break;
338
+ }
339
+ break;
340
+ }
341
+ }
342
+
343
+ if (isset($thisfile_mpeg_audio_lame['raw']['source_sample_freq'])) {
344
+ if (($thisfile_mpeg_audio['sample_rate'] == 44100) && ($thisfile_mpeg_audio_lame['raw']['source_sample_freq'] != 1)) {
345
+ $encoder_options .= ' --resample 44100';
346
+ } elseif (($thisfile_mpeg_audio['sample_rate'] == 48000) && ($thisfile_mpeg_audio_lame['raw']['source_sample_freq'] != 2)) {
347
+ $encoder_options .= ' --resample 48000';
348
+ } elseif ($thisfile_mpeg_audio['sample_rate'] < 44100) {
349
+ switch ($thisfile_mpeg_audio_lame['raw']['source_sample_freq']) {
350
+ case 0: // <= 32000
351
+ // may or may not be same as source frequency - ignore
352
+ break;
353
+ case 1: // 44100
354
+ case 2: // 48000
355
+ case 3: // 48000+
356
+ $ExplodedOptions = explode(' ', $encoder_options, 4);
357
+ switch ($ExplodedOptions[0]) {
358
+ case '--preset':
359
+ case '--alt-preset':
360
+ switch ($ExplodedOptions[1]) {
361
+ case 'fast':
362
+ case 'portable':
363
+ case 'medium':
364
+ case 'standard':
365
+ case 'extreme':
366
+ case 'insane':
367
+ $encoder_options .= ' --resample '.$thisfile_mpeg_audio['sample_rate'];
368
+ break;
369
+
370
+ default:
371
+ static $ExpectedResampledRate = array(
372
+ 'phon+/lw/mw-eu/sw|16000' => 16000,
373
+ 'mw-us|24000' => 24000, // 3.95
374
+ 'mw-us|32000' => 32000, // 3.93
375
+ 'mw-us|16000' => 16000, // 3.92
376
+ 'phone|16000' => 16000,
377
+ 'phone|11025' => 11025, // 3.94a15
378
+ 'radio|32000' => 32000, // 3.94a15
379
+ 'fm/radio|32000' => 32000, // 3.92
380
+ 'fm|32000' => 32000, // 3.90
381
+ 'voice|32000' => 32000);
382
+ if (!isset($ExpectedResampledRate[$ExplodedOptions[1].'|'.$thisfile_mpeg_audio['sample_rate']])) {
383
+ $encoder_options .= ' --resample '.$thisfile_mpeg_audio['sample_rate'];
384
+ }
385
+ break;
386
+ }
387
+ break;
388
+
389
+ case '--r3mix':
390
+ default:
391
+ $encoder_options .= ' --resample '.$thisfile_mpeg_audio['sample_rate'];
392
+ break;
393
+ }
394
+ break;
395
+ }
396
+ }
397
+ }
398
+ if (empty($encoder_options) && !empty($info['audio']['bitrate']) && !empty($info['audio']['bitrate_mode'])) {
399
+ //$encoder_options = strtoupper($info['audio']['bitrate_mode']).ceil($info['audio']['bitrate'] / 1000);
400
+ $encoder_options = strtoupper($info['audio']['bitrate_mode']);
401
+ }
402
+
403
+ return $encoder_options;
404
+ }
405
+
406
+
407
+ function decodeMPEGaudioHeader($offset, &$info, $recursivesearch=true, $ScanAsCBR=false, $FastMPEGheaderScan=false) {
408
+ static $MPEGaudioVersionLookup;
409
+ static $MPEGaudioLayerLookup;
410
+ static $MPEGaudioBitrateLookup;
411
+ static $MPEGaudioFrequencyLookup;
412
+ static $MPEGaudioChannelModeLookup;
413
+ static $MPEGaudioModeExtensionLookup;
414
+ static $MPEGaudioEmphasisLookup;
415
+ if (empty($MPEGaudioVersionLookup)) {
416
+ $MPEGaudioVersionLookup = getid3_mp3::MPEGaudioVersionArray();
417
+ $MPEGaudioLayerLookup = getid3_mp3::MPEGaudioLayerArray();
418
+ $MPEGaudioBitrateLookup = getid3_mp3::MPEGaudioBitrateArray();
419
+ $MPEGaudioFrequencyLookup = getid3_mp3::MPEGaudioFrequencyArray();
420
+ $MPEGaudioChannelModeLookup = getid3_mp3::MPEGaudioChannelModeArray();
421
+ $MPEGaudioModeExtensionLookup = getid3_mp3::MPEGaudioModeExtensionArray();
422
+ $MPEGaudioEmphasisLookup = getid3_mp3::MPEGaudioEmphasisArray();
423
+ }
424
+
425
+ if (fseek($this->getid3->fp, $offset, SEEK_SET) != 0) {
426
+ $info['error'][] = 'decodeMPEGaudioHeader() failed to seek to next offset at '.$offset;
427
+ return false;
428
+ }
429
+ //$headerstring = fread($this->getid3->fp, 1441); // worst-case max length = 32kHz @ 320kbps layer 3 = 1441 bytes/frame
430
+ $headerstring = fread($this->getid3->fp, 226); // LAME header at offset 36 + 190 bytes of Xing/LAME data
431
+
432
+ // MP3 audio frame structure:
433
+ // $aa $aa $aa $aa [$bb $bb] $cc...
434
+ // where $aa..$aa is the four-byte mpeg-audio header (below)
435
+ // $bb $bb is the optional 2-byte CRC
436
+ // and $cc... is the audio data
437
+
438
+ $head4 = substr($headerstring, 0, 4);
439
+
440
+ static $MPEGaudioHeaderDecodeCache = array();
441
+ if (isset($MPEGaudioHeaderDecodeCache[$head4])) {
442
+ $MPEGheaderRawArray = $MPEGaudioHeaderDecodeCache[$head4];
443
+ } else {
444
+ $MPEGheaderRawArray = getid3_mp3::MPEGaudioHeaderDecode($head4);
445
+ $MPEGaudioHeaderDecodeCache[$head4] = $MPEGheaderRawArray;
446
+ }
447
+
448
+ static $MPEGaudioHeaderValidCache = array();
449
+ if (!isset($MPEGaudioHeaderValidCache[$head4])) { // Not in cache
450
+ //$MPEGaudioHeaderValidCache[$head4] = getid3_mp3::MPEGaudioHeaderValid($MPEGheaderRawArray, false, true); // allow badly-formatted freeformat (from LAME 3.90 - 3.93.1)
451
+ $MPEGaudioHeaderValidCache[$head4] = getid3_mp3::MPEGaudioHeaderValid($MPEGheaderRawArray, false, false);
452
+ }
453
+
454
+ // shortcut
455
+ if (!isset($info['mpeg']['audio'])) {
456
+ $info['mpeg']['audio'] = array();
457
+ }
458
+ $thisfile_mpeg_audio = &$info['mpeg']['audio'];
459
+
460
+
461
+ if ($MPEGaudioHeaderValidCache[$head4]) {
462
+ $thisfile_mpeg_audio['raw'] = $MPEGheaderRawArray;
463
+ } else {
464
+ $info['error'][] = 'Invalid MPEG audio header ('.getid3_lib::PrintHexBytes($head4).') at offset '.$offset;
465
+ return false;
466
+ }
467
+
468
+ if (!$FastMPEGheaderScan) {
469
+ $thisfile_mpeg_audio['version'] = $MPEGaudioVersionLookup[$thisfile_mpeg_audio['raw']['version']];
470
+ $thisfile_mpeg_audio['layer'] = $MPEGaudioLayerLookup[$thisfile_mpeg_audio['raw']['layer']];
471
+
472
+ $thisfile_mpeg_audio['channelmode'] = $MPEGaudioChannelModeLookup[$thisfile_mpeg_audio['raw']['channelmode']];
473
+ $thisfile_mpeg_audio['channels'] = (($thisfile_mpeg_audio['channelmode'] == 'mono') ? 1 : 2);
474
+ $thisfile_mpeg_audio['sample_rate'] = $MPEGaudioFrequencyLookup[$thisfile_mpeg_audio['version']][$thisfile_mpeg_audio['raw']['sample_rate']];
475
+ $thisfile_mpeg_audio['protection'] = !$thisfile_mpeg_audio['raw']['protection'];
476
+ $thisfile_mpeg_audio['private'] = (bool) $thisfile_mpeg_audio['raw']['private'];
477
+ $thisfile_mpeg_audio['modeextension'] = $MPEGaudioModeExtensionLookup[$thisfile_mpeg_audio['layer']][$thisfile_mpeg_audio['raw']['modeextension']];
478
+ $thisfile_mpeg_audio['copyright'] = (bool) $thisfile_mpeg_audio['raw']['copyright'];
479
+ $thisfile_mpeg_audio['original'] = (bool) $thisfile_mpeg_audio['raw']['original'];
480
+ $thisfile_mpeg_audio['emphasis'] = $MPEGaudioEmphasisLookup[$thisfile_mpeg_audio['raw']['emphasis']];
481
+
482
+ $info['audio']['channels'] = $thisfile_mpeg_audio['channels'];
483
+ $info['audio']['sample_rate'] = $thisfile_mpeg_audio['sample_rate'];
484
+
485
+ if ($thisfile_mpeg_audio['protection']) {
486
+ $thisfile_mpeg_audio['crc'] = getid3_lib::BigEndian2Int(substr($headerstring, 4, 2));
487
+ }
488
+ }
489
+
490
+ if ($thisfile_mpeg_audio['raw']['bitrate'] == 15) {
491
+ // http://www.hydrogenaudio.org/?act=ST&f=16&t=9682&st=0
492
+ $info['warning'][] = 'Invalid bitrate index (15), this is a known bug in free-format MP3s encoded by LAME v3.90 - 3.93.1';
493
+ $thisfile_mpeg_audio['raw']['bitrate'] = 0;
494
+ }
495
+ $thisfile_mpeg_audio['padding'] = (bool) $thisfile_mpeg_audio['raw']['padding'];
496
+ $thisfile_mpeg_audio['bitrate'] = $MPEGaudioBitrateLookup[$thisfile_mpeg_audio['version']][$thisfile_mpeg_audio['layer']][$thisfile_mpeg_audio['raw']['bitrate']];
497
+
498
+ if (($thisfile_mpeg_audio['bitrate'] == 'free') && ($offset == $info['avdataoffset'])) {
499
+ // only skip multiple frame check if free-format bitstream found at beginning of file
500
+ // otherwise is quite possibly simply corrupted data
501
+ $recursivesearch = false;
502
+ }
503
+
504
+ // For Layer 2 there are some combinations of bitrate and mode which are not allowed.
505
+ if (!$FastMPEGheaderScan && ($thisfile_mpeg_audio['layer'] == '2')) {
506
+
507
+ $info['audio']['dataformat'] = 'mp2';
508
+ switch ($thisfile_mpeg_audio['channelmode']) {
509
+
510
+ case 'mono':
511
+ if (($thisfile_mpeg_audio['bitrate'] == 'free') || ($thisfile_mpeg_audio['bitrate'] <= 192000)) {
512
+ // these are ok
513
+ } else {
514
+ $info['error'][] = $thisfile_mpeg_audio['bitrate'].'kbps not allowed in Layer 2, '.$thisfile_mpeg_audio['channelmode'].'.';
515
+ return false;
516
+ }
517
+ break;
518
+
519
+ case 'stereo':
520
+ case 'joint stereo':
521
+ case 'dual channel':
522
+ if (($thisfile_mpeg_audio['bitrate'] == 'free') || ($thisfile_mpeg_audio['bitrate'] == 64000) || ($thisfile_mpeg_audio['bitrate'] >= 96000)) {
523
+ // these are ok
524
+ } else {
525
+ $info['error'][] = intval(round($thisfile_mpeg_audio['bitrate'] / 1000)).'kbps not allowed in Layer 2, '.$thisfile_mpeg_audio['channelmode'].'.';
526
+ return false;
527
+ }
528
+ break;
529
+
530
+ }
531
+
532
+ }
533
+
534
+
535
+ if ($info['audio']['sample_rate'] > 0) {
536
+ $thisfile_mpeg_audio['framelength'] = getid3_mp3::MPEGaudioFrameLength($thisfile_mpeg_audio['bitrate'], $thisfile_mpeg_audio['version'], $thisfile_mpeg_audio['layer'], (int) $thisfile_mpeg_audio['padding'], $info['audio']['sample_rate']);
537
+ }
538
+
539
+ $nextframetestoffset = $offset + 1;
540
+ if ($thisfile_mpeg_audio['bitrate'] != 'free') {
541
+
542
+ $info['audio']['bitrate'] = $thisfile_mpeg_audio['bitrate'];
543
+
544
+ if (isset($thisfile_mpeg_audio['framelength'])) {
545
+ $nextframetestoffset = $offset + $thisfile_mpeg_audio['framelength'];
546
+ } else {
547
+ $info['error'][] = 'Frame at offset('.$offset.') is has an invalid frame length.';
548
+ return false;
549
+ }
550
+
551
+ }
552
+
553
+ $ExpectedNumberOfAudioBytes = 0;
554
+
555
+ ////////////////////////////////////////////////////////////////////////////////////
556
+ // Variable-bitrate headers
557
+
558
+ if (substr($headerstring, 4 + 32, 4) == 'VBRI') {
559
+ // Fraunhofer VBR header is hardcoded 'VBRI' at offset 0x24 (36)
560
+ // specs taken from http://minnie.tuhs.org/pipermail/mp3encoder/2001-January/001800.html
561
+
562
+ $thisfile_mpeg_audio['bitrate_mode'] = 'vbr';
563
+ $thisfile_mpeg_audio['VBR_method'] = 'Fraunhofer';
564
+ $info['audio']['codec'] = 'Fraunhofer';
565
+
566
+ $SideInfoData = substr($headerstring, 4 + 2, 32);
567
+
568
+ $FraunhoferVBROffset = 36;
569
+
570
+ $thisfile_mpeg_audio['VBR_encoder_version'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 4, 2)); // VbriVersion
571
+ $thisfile_mpeg_audio['VBR_encoder_delay'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 6, 2)); // VbriDelay
572
+ $thisfile_mpeg_audio['VBR_quality'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 8, 2)); // VbriQuality
573
+ $thisfile_mpeg_audio['VBR_bytes'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 10, 4)); // VbriStreamBytes
574
+ $thisfile_mpeg_audio['VBR_frames'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 14, 4)); // VbriStreamFrames
575
+ $thisfile_mpeg_audio['VBR_seek_offsets'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 18, 2)); // VbriTableSize
576
+ $thisfile_mpeg_audio['VBR_seek_scale'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 20, 2)); // VbriTableScale
577
+ $thisfile_mpeg_audio['VBR_entry_bytes'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 22, 2)); // VbriEntryBytes
578
+ $thisfile_mpeg_audio['VBR_entry_frames'] = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset + 24, 2)); // VbriEntryFrames
579
+
580
+ $ExpectedNumberOfAudioBytes = $thisfile_mpeg_audio['VBR_bytes'];
581
+
582
+ $previousbyteoffset = $offset;
583
+ for ($i = 0; $i < $thisfile_mpeg_audio['VBR_seek_offsets']; $i++) {
584
+ $Fraunhofer_OffsetN = getid3_lib::BigEndian2Int(substr($headerstring, $FraunhoferVBROffset, $thisfile_mpeg_audio['VBR_entry_bytes']));
585
+ $FraunhoferVBROffset += $thisfile_mpeg_audio['VBR_entry_bytes'];
586
+ $thisfile_mpeg_audio['VBR_offsets_relative'][$i] = ($Fraunhofer_OffsetN * $thisfile_mpeg_audio['VBR_seek_scale']);
587
+ $thisfile_mpeg_audio['VBR_offsets_absolute'][$i] = ($Fraunhofer_OffsetN * $thisfile_mpeg_audio['VBR_seek_scale']) + $previousbyteoffset;
588
+ $previousbyteoffset += $Fraunhofer_OffsetN;
589
+ }
590
+
591
+
592
+ } else {
593
+
594
+ // Xing VBR header is hardcoded 'Xing' at a offset 0x0D (13), 0x15 (21) or 0x24 (36)
595
+ // depending on MPEG layer and number of channels
596
+
597
+ $VBRidOffset = getid3_mp3::XingVBRidOffset($thisfile_mpeg_audio['version'], $thisfile_mpeg_audio['channelmode']);
598
+ $SideInfoData = substr($headerstring, 4 + 2, $VBRidOffset - 4);
599
+
600
+ if ((substr($headerstring, $VBRidOffset, strlen('Xing')) == 'Xing') || (substr($headerstring, $VBRidOffset, strlen('Info')) == 'Info')) {
601
+ // 'Xing' is traditional Xing VBR frame
602
+ // 'Info' is LAME-encoded CBR (This was done to avoid CBR files to be recognized as traditional Xing VBR files by some decoders.)
603
+ // 'Info' *can* legally be used to specify a VBR file as well, however.
604
+
605
+ // http://www.multiweb.cz/twoinches/MP3inside.htm
606
+ //00..03 = "Xing" or "Info"
607
+ //04..07 = Flags:
608
+ // 0x01 Frames Flag set if value for number of frames in file is stored
609
+ // 0x02 Bytes Flag set if value for filesize in bytes is stored
610
+ // 0x04 TOC Flag set if values for TOC are stored
611
+ // 0x08 VBR Scale Flag set if values for VBR scale is stored
612
+ //08..11 Frames: Number of frames in file (including the first Xing/Info one)
613
+ //12..15 Bytes: File length in Bytes
614
+ //16..115 TOC (Table of Contents):
615
+ // Contains of 100 indexes (one Byte length) for easier lookup in file. Approximately solves problem with moving inside file.
616
+ // Each Byte has a value according this formula:
617
+ // (TOC[i] / 256) * fileLenInBytes
618
+ // So if song lasts eg. 240 sec. and you want to jump to 60. sec. (and file is 5 000 000 Bytes length) you can use:
619
+ // TOC[(60/240)*100] = TOC[25]
620
+ // and corresponding Byte in file is then approximately at:
621
+ // (TOC[25]/256) * 5000000
622
+ //116..119 VBR Scale
623
+
624
+
625
+ // should be safe to leave this at 'vbr' and let it be overriden to 'cbr' if a CBR preset/mode is used by LAME
626
+ // if (substr($headerstring, $VBRidOffset, strlen('Info')) == 'Xing') {
627
+ $thisfile_mpeg_audio['bitrate_mode'] = 'vbr';
628
+ $thisfile_mpeg_audio['VBR_method'] = 'Xing';
629
+ // } else {
630
+ // $ScanAsCBR = true;
631
+ // $thisfile_mpeg_audio['bitrate_mode'] = 'cbr';
632
+ // }
633
+
634
+ $thisfile_mpeg_audio['xing_flags_raw'] = getid3_lib::BigEndian2Int(substr($headerstring, $VBRidOffset + 4, 4));
635
+
636
+ $thisfile_mpeg_audio['xing_flags']['frames'] = (bool) ($thisfile_mpeg_audio['xing_flags_raw'] & 0x00000001);
637
+ $thisfile_mpeg_audio['xing_flags']['bytes'] = (bool) ($thisfile_mpeg_audio['xing_flags_raw'] & 0x00000002);
638
+ $thisfile_mpeg_audio['xing_flags']['toc'] = (bool) ($thisfile_mpeg_audio['xing_flags_raw'] & 0x00000004);
639
+ $thisfile_mpeg_audio['xing_flags']['vbr_scale'] = (bool) ($thisfile_mpeg_audio['xing_flags_raw'] & 0x00000008);
640
+
641
+ if ($thisfile_mpeg_audio['xing_flags']['frames']) {
642
+ $thisfile_mpeg_audio['VBR_frames'] = getid3_lib::BigEndian2Int(substr($headerstring, $VBRidOffset + 8, 4));
643
+ //$thisfile_mpeg_audio['VBR_frames']--; // don't count header Xing/Info frame
644
+ }
645
+ if ($thisfile_mpeg_audio['xing_flags']['bytes']) {
646
+ $thisfile_mpeg_audio['VBR_bytes'] = getid3_lib::BigEndian2Int(substr($headerstring, $VBRidOffset + 12, 4));
647
+ }
648
+
649
+ //if (($thisfile_mpeg_audio['bitrate'] == 'free') && !empty($thisfile_mpeg_audio['VBR_frames']) && !empty($thisfile_mpeg_audio['VBR_bytes'])) {
650
+ if (!empty($thisfile_mpeg_audio['VBR_frames']) && !empty($thisfile_mpeg_audio['VBR_bytes'])) {
651
+
652
+ $framelengthfloat = $thisfile_mpeg_audio['VBR_bytes'] / $thisfile_mpeg_audio['VBR_frames'];
653
+
654
+ if ($thisfile_mpeg_audio['layer'] == '1') {
655
+ // BitRate = (((FrameLengthInBytes / 4) - Padding) * SampleRate) / 12
656
+ //$info['audio']['bitrate'] = ((($framelengthfloat / 4) - intval($thisfile_mpeg_audio['padding'])) * $thisfile_mpeg_audio['sample_rate']) / 12;
657
+ $info['audio']['bitrate'] = ($framelengthfloat / 4) * $thisfile_mpeg_audio['sample_rate'] * (2 / $info['audio']['channels']) / 12;
658
+ } else {
659
+ // Bitrate = ((FrameLengthInBytes - Padding) * SampleRate) / 144
660
+ //$info['audio']['bitrate'] = (($framelengthfloat - intval($thisfile_mpeg_audio['padding'])) * $thisfile_mpeg_audio['sample_rate']) / 144;
661
+ $info['audio']['bitrate'] = $framelengthfloat * $thisfile_mpeg_audio['sample_rate'] * (2 / $info['audio']['channels']) / 144;
662
+ }
663
+ $thisfile_mpeg_audio['framelength'] = floor($framelengthfloat);
664
+ }
665
+
666
+ if ($thisfile_mpeg_audio['xing_flags']['toc']) {
667
+ $LAMEtocData = substr($headerstring, $VBRidOffset + 16, 100);
668
+ for ($i = 0; $i < 100; $i++) {
669
+ $thisfile_mpeg_audio['toc'][$i] = ord($LAMEtocData{$i});
670
+ }
671
+ }
672
+ if ($thisfile_mpeg_audio['xing_flags']['vbr_scale']) {
673
+ $thisfile_mpeg_audio['VBR_scale'] = getid3_lib::BigEndian2Int(substr($headerstring, $VBRidOffset + 116, 4));
674
+ }
675
+
676
+
677
+ // http://gabriel.mp3-tech.org/mp3infotag.html
678
+ if (substr($headerstring, $VBRidOffset + 120, 4) == 'LAME') {
679
+
680
+ // shortcut
681
+ $thisfile_mpeg_audio['LAME'] = array();
682
+ $thisfile_mpeg_audio_lame = &$thisfile_mpeg_audio['LAME'];
683
+
684
+
685
+ $thisfile_mpeg_audio_lame['long_version'] = substr($headerstring, $VBRidOffset + 120, 20);
686
+ $thisfile_mpeg_audio_lame['short_version'] = substr($thisfile_mpeg_audio_lame['long_version'], 0, 9);
687
+
688
+ if ($thisfile_mpeg_audio_lame['short_version'] >= 'LAME3.90') {
689
+
690
+ // extra 11 chars are not part of version string when LAMEtag present
691
+ unset($thisfile_mpeg_audio_lame['long_version']);
692
+
693
+ // It the LAME tag was only introduced in LAME v3.90
694
+ // http://www.hydrogenaudio.org/?act=ST&f=15&t=9933
695
+
696
+ // Offsets of various bytes in http://gabriel.mp3-tech.org/mp3infotag.html
697
+ // are assuming a 'Xing' identifier offset of 0x24, which is the case for
698
+ // MPEG-1 non-mono, but not for other combinations
699
+ $LAMEtagOffsetContant = $VBRidOffset - 0x24;
700
+
701
+ // shortcuts
702
+ $thisfile_mpeg_audio_lame['RGAD'] = array('track'=>array(), 'album'=>array());
703
+ $thisfile_mpeg_audio_lame_RGAD = &$thisfile_mpeg_audio_lame['RGAD'];
704
+ $thisfile_mpeg_audio_lame_RGAD_track = &$thisfile_mpeg_audio_lame_RGAD['track'];
705
+ $thisfile_mpeg_audio_lame_RGAD_album = &$thisfile_mpeg_audio_lame_RGAD['album'];
706
+ $thisfile_mpeg_audio_lame['raw'] = array();
707
+ $thisfile_mpeg_audio_lame_raw = &$thisfile_mpeg_audio_lame['raw'];
708
+
709
+ // byte $9B VBR Quality
710
+ // This field is there to indicate a quality level, although the scale was not precised in the original Xing specifications.
711
+ // Actually overwrites original Xing bytes
712
+ unset($thisfile_mpeg_audio['VBR_scale']);
713
+ $thisfile_mpeg_audio_lame['vbr_quality'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0x9B, 1));
714
+
715
+ // bytes $9C-$A4 Encoder short VersionString
716
+ $thisfile_mpeg_audio_lame['short_version'] = substr($headerstring, $LAMEtagOffsetContant + 0x9C, 9);
717
+
718
+ // byte $A5 Info Tag revision + VBR method
719
+ $LAMEtagRevisionVBRmethod = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xA5, 1));
720
+
721
+ $thisfile_mpeg_audio_lame['tag_revision'] = ($LAMEtagRevisionVBRmethod & 0xF0) >> 4;
722
+ $thisfile_mpeg_audio_lame_raw['vbr_method'] = $LAMEtagRevisionVBRmethod & 0x0F;
723
+ $thisfile_mpeg_audio_lame['vbr_method'] = getid3_mp3::LAMEvbrMethodLookup($thisfile_mpeg_audio_lame_raw['vbr_method']);
724
+ $thisfile_mpeg_audio['bitrate_mode'] = substr($thisfile_mpeg_audio_lame['vbr_method'], 0, 3); // usually either 'cbr' or 'vbr', but truncates 'vbr-old / vbr-rh' to 'vbr'
725
+
726
+ // byte $A6 Lowpass filter value
727
+ $thisfile_mpeg_audio_lame['lowpass_frequency'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xA6, 1)) * 100;
728
+
729
+ // bytes $A7-$AE Replay Gain
730
+ // http://privatewww.essex.ac.uk/~djmrob/replaygain/rg_data_format.html
731
+ // bytes $A7-$AA : 32 bit floating point "Peak signal amplitude"
732
+ if ($thisfile_mpeg_audio_lame['short_version'] >= 'LAME3.94b') {
733
+ // LAME 3.94a16 and later - 9.23 fixed point
734
+ // ie 0x0059E2EE / (2^23) = 5890798 / 8388608 = 0.7022378444671630859375
735
+ $thisfile_mpeg_audio_lame_RGAD['peak_amplitude'] = (float) ((getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xA7, 4))) / 8388608);
736
+ } else {
737
+ // LAME 3.94a15 and earlier - 32-bit floating point
738
+ // Actually 3.94a16 will fall in here too and be WRONG, but is hard to detect 3.94a16 vs 3.94a15
739
+ $thisfile_mpeg_audio_lame_RGAD['peak_amplitude'] = getid3_lib::LittleEndian2Float(substr($headerstring, $LAMEtagOffsetContant + 0xA7, 4));
740
+ }
741
+ if ($thisfile_mpeg_audio_lame_RGAD['peak_amplitude'] == 0) {
742
+ unset($thisfile_mpeg_audio_lame_RGAD['peak_amplitude']);
743
+ } else {
744
+ $thisfile_mpeg_audio_lame_RGAD['peak_db'] = getid3_lib::RGADamplitude2dB($thisfile_mpeg_audio_lame_RGAD['peak_amplitude']);
745
+ }
746
+
747
+ $thisfile_mpeg_audio_lame_raw['RGAD_track'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xAB, 2));
748
+ $thisfile_mpeg_audio_lame_raw['RGAD_album'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xAD, 2));
749
+
750
+
751
+ if ($thisfile_mpeg_audio_lame_raw['RGAD_track'] != 0) {
752
+
753
+ $thisfile_mpeg_audio_lame_RGAD_track['raw']['name'] = ($thisfile_mpeg_audio_lame_raw['RGAD_track'] & 0xE000) >> 13;
754
+ $thisfile_mpeg_audio_lame_RGAD_track['raw']['originator'] = ($thisfile_mpeg_audio_lame_raw['RGAD_track'] & 0x1C00) >> 10;
755
+ $thisfile_mpeg_audio_lame_RGAD_track['raw']['sign_bit'] = ($thisfile_mpeg_audio_lame_raw['RGAD_track'] & 0x0200) >> 9;
756
+ $thisfile_mpeg_audio_lame_RGAD_track['raw']['gain_adjust'] = $thisfile_mpeg_audio_lame_raw['RGAD_track'] & 0x01FF;
757
+ $thisfile_mpeg_audio_lame_RGAD_track['name'] = getid3_lib::RGADnameLookup($thisfile_mpeg_audio_lame_RGAD_track['raw']['name']);
758
+ $thisfile_mpeg_audio_lame_RGAD_track['originator'] = getid3_lib::RGADoriginatorLookup($thisfile_mpeg_audio_lame_RGAD_track['raw']['originator']);
759
+ $thisfile_mpeg_audio_lame_RGAD_track['gain_db'] = getid3_lib::RGADadjustmentLookup($thisfile_mpeg_audio_lame_RGAD_track['raw']['gain_adjust'], $thisfile_mpeg_audio_lame_RGAD_track['raw']['sign_bit']);
760
+
761
+ if (!empty($thisfile_mpeg_audio_lame_RGAD['peak_amplitude'])) {
762
+ $info['replay_gain']['track']['peak'] = $thisfile_mpeg_audio_lame_RGAD['peak_amplitude'];
763
+ }
764
+ $info['replay_gain']['track']['originator'] = $thisfile_mpeg_audio_lame_RGAD_track['originator'];
765
+ $info['replay_gain']['track']['adjustment'] = $thisfile_mpeg_audio_lame_RGAD_track['gain_db'];
766
+ } else {
767
+ unset($thisfile_mpeg_audio_lame_RGAD['track']);
768
+ }
769
+ if ($thisfile_mpeg_audio_lame_raw['RGAD_album'] != 0) {
770
+
771
+ $thisfile_mpeg_audio_lame_RGAD_album['raw']['name'] = ($thisfile_mpeg_audio_lame_raw['RGAD_album'] & 0xE000) >> 13;
772
+ $thisfile_mpeg_audio_lame_RGAD_album['raw']['originator'] = ($thisfile_mpeg_audio_lame_raw['RGAD_album'] & 0x1C00) >> 10;
773
+ $thisfile_mpeg_audio_lame_RGAD_album['raw']['sign_bit'] = ($thisfile_mpeg_audio_lame_raw['RGAD_album'] & 0x0200) >> 9;
774
+ $thisfile_mpeg_audio_lame_RGAD_album['raw']['gain_adjust'] = $thisfile_mpeg_audio_lame_raw['RGAD_album'] & 0x01FF;
775
+ $thisfile_mpeg_audio_lame_RGAD_album['name'] = getid3_lib::RGADnameLookup($thisfile_mpeg_audio_lame_RGAD_album['raw']['name']);
776
+ $thisfile_mpeg_audio_lame_RGAD_album['originator'] = getid3_lib::RGADoriginatorLookup($thisfile_mpeg_audio_lame_RGAD_album['raw']['originator']);
777
+ $thisfile_mpeg_audio_lame_RGAD_album['gain_db'] = getid3_lib::RGADadjustmentLookup($thisfile_mpeg_audio_lame_RGAD_album['raw']['gain_adjust'], $thisfile_mpeg_audio_lame_RGAD_album['raw']['sign_bit']);
778
+
779
+ if (!empty($thisfile_mpeg_audio_lame_RGAD['peak_amplitude'])) {
780
+ $info['replay_gain']['album']['peak'] = $thisfile_mpeg_audio_lame_RGAD['peak_amplitude'];
781
+ }
782
+ $info['replay_gain']['album']['originator'] = $thisfile_mpeg_audio_lame_RGAD_album['originator'];
783
+ $info['replay_gain']['album']['adjustment'] = $thisfile_mpeg_audio_lame_RGAD_album['gain_db'];
784
+ } else {
785
+ unset($thisfile_mpeg_audio_lame_RGAD['album']);
786
+ }
787
+ if (empty($thisfile_mpeg_audio_lame_RGAD)) {
788
+ unset($thisfile_mpeg_audio_lame['RGAD']);
789
+ }
790
+
791
+
792
+ // byte $AF Encoding flags + ATH Type
793
+ $EncodingFlagsATHtype = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xAF, 1));
794
+ $thisfile_mpeg_audio_lame['encoding_flags']['nspsytune'] = (bool) ($EncodingFlagsATHtype & 0x10);
795
+ $thisfile_mpeg_audio_lame['encoding_flags']['nssafejoint'] = (bool) ($EncodingFlagsATHtype & 0x20);
796
+ $thisfile_mpeg_audio_lame['encoding_flags']['nogap_next'] = (bool) ($EncodingFlagsATHtype & 0x40);
797
+ $thisfile_mpeg_audio_lame['encoding_flags']['nogap_prev'] = (bool) ($EncodingFlagsATHtype & 0x80);
798
+ $thisfile_mpeg_audio_lame['ath_type'] = $EncodingFlagsATHtype & 0x0F;
799
+
800
+ // byte $B0 if ABR {specified bitrate} else {minimal bitrate}
801
+ $thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB0, 1));
802
+ if ($thisfile_mpeg_audio_lame_raw['vbr_method'] == 2) { // Average BitRate (ABR)
803
+ $thisfile_mpeg_audio_lame['bitrate_abr'] = $thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate'];
804
+ } elseif ($thisfile_mpeg_audio_lame_raw['vbr_method'] == 1) { // Constant BitRate (CBR)
805
+ // ignore
806
+ } elseif ($thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate'] > 0) { // Variable BitRate (VBR) - minimum bitrate
807
+ $thisfile_mpeg_audio_lame['bitrate_min'] = $thisfile_mpeg_audio_lame['raw']['abrbitrate_minbitrate'];
808
+ }
809
+
810
+ // bytes $B1-$B3 Encoder delays
811
+ $EncoderDelays = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB1, 3));
812
+ $thisfile_mpeg_audio_lame['encoder_delay'] = ($EncoderDelays & 0xFFF000) >> 12;
813
+ $thisfile_mpeg_audio_lame['end_padding'] = $EncoderDelays & 0x000FFF;
814
+
815
+ // byte $B4 Misc
816
+ $MiscByte = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB4, 1));
817
+ $thisfile_mpeg_audio_lame_raw['noise_shaping'] = ($MiscByte & 0x03);
818
+ $thisfile_mpeg_audio_lame_raw['stereo_mode'] = ($MiscByte & 0x1C) >> 2;
819
+ $thisfile_mpeg_audio_lame_raw['not_optimal_quality'] = ($MiscByte & 0x20) >> 5;
820
+ $thisfile_mpeg_audio_lame_raw['source_sample_freq'] = ($MiscByte & 0xC0) >> 6;
821
+ $thisfile_mpeg_audio_lame['noise_shaping'] = $thisfile_mpeg_audio_lame_raw['noise_shaping'];
822
+ $thisfile_mpeg_audio_lame['stereo_mode'] = getid3_mp3::LAMEmiscStereoModeLookup($thisfile_mpeg_audio_lame_raw['stereo_mode']);
823
+ $thisfile_mpeg_audio_lame['not_optimal_quality'] = (bool) $thisfile_mpeg_audio_lame_raw['not_optimal_quality'];
824
+ $thisfile_mpeg_audio_lame['source_sample_freq'] = getid3_mp3::LAMEmiscSourceSampleFrequencyLookup($thisfile_mpeg_audio_lame_raw['source_sample_freq']);
825
+
826
+ // byte $B5 MP3 Gain
827
+ $thisfile_mpeg_audio_lame_raw['mp3_gain'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB5, 1), false, true);
828
+ $thisfile_mpeg_audio_lame['mp3_gain_db'] = (getid3_lib::RGADamplitude2dB(2) / 4) * $thisfile_mpeg_audio_lame_raw['mp3_gain'];
829
+ $thisfile_mpeg_audio_lame['mp3_gain_factor'] = pow(2, ($thisfile_mpeg_audio_lame['mp3_gain_db'] / 6));
830
+
831
+ // bytes $B6-$B7 Preset and surround info
832
+ $PresetSurroundBytes = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB6, 2));
833
+ // Reserved = ($PresetSurroundBytes & 0xC000);
834
+ $thisfile_mpeg_audio_lame_raw['surround_info'] = ($PresetSurroundBytes & 0x3800);
835
+ $thisfile_mpeg_audio_lame['surround_info'] = getid3_mp3::LAMEsurroundInfoLookup($thisfile_mpeg_audio_lame_raw['surround_info']);
836
+ $thisfile_mpeg_audio_lame['preset_used_id'] = ($PresetSurroundBytes & 0x07FF);
837
+ $thisfile_mpeg_audio_lame['preset_used'] = getid3_mp3::LAMEpresetUsedLookup($thisfile_mpeg_audio_lame);
838
+ if (!empty($thisfile_mpeg_audio_lame['preset_used_id']) && empty($thisfile_mpeg_audio_lame['preset_used'])) {
839
+ $info['warning'][] = 'Unknown LAME preset used ('.$thisfile_mpeg_audio_lame['preset_used_id'].') - please report to info@getid3.org';
840
+ }
841
+ if (($thisfile_mpeg_audio_lame['short_version'] == 'LAME3.90.') && !empty($thisfile_mpeg_audio_lame['preset_used_id'])) {
842
+ // this may change if 3.90.4 ever comes out
843
+ $thisfile_mpeg_audio_lame['short_version'] = 'LAME3.90.3';
844
+ }
845
+
846
+ // bytes $B8-$BB MusicLength
847
+ $thisfile_mpeg_audio_lame['audio_bytes'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xB8, 4));
848
+ $ExpectedNumberOfAudioBytes = (($thisfile_mpeg_audio_lame['audio_bytes'] > 0) ? $thisfile_mpeg_audio_lame['audio_bytes'] : $thisfile_mpeg_audio['VBR_bytes']);
849
+
850
+ // bytes $BC-$BD MusicCRC
851
+ $thisfile_mpeg_audio_lame['music_crc'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xBC, 2));
852
+
853
+ // bytes $BE-$BF CRC-16 of Info Tag
854
+ $thisfile_mpeg_audio_lame['lame_tag_crc'] = getid3_lib::BigEndian2Int(substr($headerstring, $LAMEtagOffsetContant + 0xBE, 2));
855
+
856
+
857
+ // LAME CBR
858
+ if ($thisfile_mpeg_audio_lame_raw['vbr_method'] == 1) {
859
+
860
+ $thisfile_mpeg_audio['bitrate_mode'] = 'cbr';
861
+ $thisfile_mpeg_audio['bitrate'] = getid3_mp3::ClosestStandardMP3Bitrate($thisfile_mpeg_audio['bitrate']);
862
+ $info['audio']['bitrate'] = $thisfile_mpeg_audio['bitrate'];
863
+ //if (empty($thisfile_mpeg_audio['bitrate']) || (!empty($thisfile_mpeg_audio_lame['bitrate_min']) && ($thisfile_mpeg_audio_lame['bitrate_min'] != 255))) {
864
+ // $thisfile_mpeg_audio['bitrate'] = $thisfile_mpeg_audio_lame['bitrate_min'];
865
+ //}
866
+
867
+ }
868
+
869
+ }
870
+ }
871
+
872
+ } else {
873
+
874
+ // not Fraunhofer or Xing VBR methods, most likely CBR (but could be VBR with no header)
875
+ $thisfile_mpeg_audio['bitrate_mode'] = 'cbr';
876
+ if ($recursivesearch) {
877
+ $thisfile_mpeg_audio['bitrate_mode'] = 'vbr';
878
+ if ($this->RecursiveFrameScanning($offset, $nextframetestoffset, true)) {
879
+ $recursivesearch = false;
880
+ $thisfile_mpeg_audio['bitrate_mode'] = 'cbr';
881
+ }
882
+ if ($thisfile_mpeg_audio['bitrate_mode'] == 'vbr') {
883
+ $info['warning'][] = 'VBR file with no VBR header. Bitrate values calculated from actual frame bitrates.';
884
+ }
885
+ }
886
+
887
+ }
888
+
889
+ }
890
+
891
+ if (($ExpectedNumberOfAudioBytes > 0) && ($ExpectedNumberOfAudioBytes != ($info['avdataend'] - $info['avdataoffset']))) {
892
+ if ($ExpectedNumberOfAudioBytes > ($info['avdataend'] - $info['avdataoffset'])) {
893
+ if (isset($info['fileformat']) && ($info['fileformat'] == 'riff')) {
894
+ // ignore, audio data is broken into chunks so will always be data "missing"
895
+ } elseif (($ExpectedNumberOfAudioBytes - ($info['avdataend'] - $info['avdataoffset'])) == 1) {
896
+ $info['warning'][] = 'Last byte of data truncated (this is a known bug in Meracl ID3 Tag Writer before v1.3.5)';
897
+ } else {
898
+ $info['warning'][] = 'Probable truncated file: expecting '.$ExpectedNumberOfAudioBytes.' bytes of audio data, only found '.($info['avdataend'] - $info['avdataoffset']).' (short by '.($ExpectedNumberOfAudioBytes - ($info['avdataend'] - $info['avdataoffset'])).' bytes)';
899
+ }
900
+ } else {
901
+ if ((($info['avdataend'] - $info['avdataoffset']) - $ExpectedNumberOfAudioBytes) == 1) {
902
+ // $prenullbytefileoffset = ftell($this->getid3->fp);
903
+ // fseek($this->getid3->fp, $info['avdataend'], SEEK_SET);
904
+ // $PossibleNullByte = fread($this->getid3->fp, 1);
905
+ // fseek($this->getid3->fp, $prenullbytefileoffset, SEEK_SET);
906
+ // if ($PossibleNullByte === "\x00") {
907
+ $info['avdataend']--;
908
+ // $info['warning'][] = 'Extra null byte at end of MP3 data assumed to be RIFF padding and therefore ignored';
909
+ // } else {
910
+ // $info['warning'][] = 'Too much data in file: expecting '.$ExpectedNumberOfAudioBytes.' bytes of audio data, found '.($info['avdataend'] - $info['avdataoffset']).' ('.(($info['avdataend'] - $info['avdataoffset']) - $ExpectedNumberOfAudioBytes).' bytes too many)';
911
+ // }
912
+ } else {
913
+ $info['warning'][] = 'Too much data in file: expecting '.$ExpectedNumberOfAudioBytes.' bytes of audio data, found '.($info['avdataend'] - $info['avdataoffset']).' ('.(($info['avdataend'] - $info['avdataoffset']) - $ExpectedNumberOfAudioBytes).' bytes too many)';
914
+ }
915
+ }
916
+ }
917
+
918
+ if (($thisfile_mpeg_audio['bitrate'] == 'free') && empty($info['audio']['bitrate'])) {
919
+ if (($offset == $info['avdataoffset']) && empty($thisfile_mpeg_audio['VBR_frames'])) {
920
+ $framebytelength = $this->FreeFormatFrameLength($offset, true);
921
+ if ($framebytelength > 0) {
922
+ $thisfile_mpeg_audio['framelength'] = $framebytelength;
923
+ if ($thisfile_mpeg_audio['layer'] == '1') {
924
+ // BitRate = (((FrameLengthInBytes / 4) - Padding) * SampleRate) / 12
925
+ $info['audio']['bitrate'] = ((($framebytelength / 4) - intval($thisfile_mpeg_audio['padding'])) * $thisfile_mpeg_audio['sample_rate']) / 12;
926
+ } else {
927
+ // Bitrate = ((FrameLengthInBytes - Padding) * SampleRate) / 144
928
+ $info['audio']['bitrate'] = (($framebytelength - intval($thisfile_mpeg_audio['padding'])) * $thisfile_mpeg_audio['sample_rate']) / 144;
929
+ }
930
+ } else {
931
+ $info['error'][] = 'Error calculating frame length of free-format MP3 without Xing/LAME header';
932
+ }
933
+ }
934
+ }
935
+
936
+ if (isset($thisfile_mpeg_audio['VBR_frames']) ? $thisfile_mpeg_audio['VBR_frames'] : '') {
937
+ switch ($thisfile_mpeg_audio['bitrate_mode']) {
938
+ case 'vbr':
939
+ case 'abr':
940
+ $bytes_per_frame = 1152;
941
+ if (($thisfile_mpeg_audio['version'] == '1') && ($thisfile_mpeg_audio['layer'] == 1)) {
942
+ $bytes_per_frame = 384;
943
+ } elseif ((($thisfile_mpeg_audio['version'] == '2') || ($thisfile_mpeg_audio['version'] == '2.5')) && ($thisfile_mpeg_audio['layer'] == 3)) {
944
+ $bytes_per_frame = 576;
945
+ }
946
+ $thisfile_mpeg_audio['VBR_bitrate'] = (isset($thisfile_mpeg_audio['VBR_bytes']) ? (($thisfile_mpeg_audio['VBR_bytes'] / $thisfile_mpeg_audio['VBR_frames']) * 8) * ($info['audio']['sample_rate'] / $bytes_per_frame) : 0);
947
+ if ($thisfile_mpeg_audio['VBR_bitrate'] > 0) {
948
+ $info['audio']['bitrate'] = $thisfile_mpeg_audio['VBR_bitrate'];
949
+ $thisfile_mpeg_audio['bitrate'] = $thisfile_mpeg_audio['VBR_bitrate']; // to avoid confusion
950
+ }
951
+ break;
952
+ }
953
+ }
954
+
955
+ // End variable-bitrate headers
956
+ ////////////////////////////////////////////////////////////////////////////////////
957
+
958
+ if ($recursivesearch) {
959
+
960
+ if (!$this->RecursiveFrameScanning($offset, $nextframetestoffset, $ScanAsCBR)) {
961
+ return false;
962
+ }
963
+
964
+ }
965
+
966
+
967
+ //if (false) {
968
+ // // experimental side info parsing section - not returning anything useful yet
969
+ //
970
+ // $SideInfoBitstream = getid3_lib::BigEndian2Bin($SideInfoData);
971
+ // $SideInfoOffset = 0;
972
+ //
973
+ // if ($thisfile_mpeg_audio['version'] == '1') {
974
+ // if ($thisfile_mpeg_audio['channelmode'] == 'mono') {
975
+ // // MPEG-1 (mono)
976
+ // $thisfile_mpeg_audio['side_info']['main_data_begin'] = substr($SideInfoBitstream, $SideInfoOffset, 9);
977
+ // $SideInfoOffset += 9;
978
+ // $SideInfoOffset += 5;
979
+ // } else {
980
+ // // MPEG-1 (stereo, joint-stereo, dual-channel)
981
+ // $thisfile_mpeg_audio['side_info']['main_data_begin'] = substr($SideInfoBitstream, $SideInfoOffset, 9);
982
+ // $SideInfoOffset += 9;
983
+ // $SideInfoOffset += 3;
984
+ // }
985
+ // } else { // 2 or 2.5
986
+ // if ($thisfile_mpeg_audio['channelmode'] == 'mono') {
987
+ // // MPEG-2, MPEG-2.5 (mono)
988
+ // $thisfile_mpeg_audio['side_info']['main_data_begin'] = substr($SideInfoBitstream, $SideInfoOffset, 8);
989
+ // $SideInfoOffset += 8;
990
+ // $SideInfoOffset += 1;
991
+ // } else {
992
+ // // MPEG-2, MPEG-2.5 (stereo, joint-stereo, dual-channel)
993
+ // $thisfile_mpeg_audio['side_info']['main_data_begin'] = substr($SideInfoBitstream, $SideInfoOffset, 8);
994
+ // $SideInfoOffset += 8;
995
+ // $SideInfoOffset += 2;
996
+ // }
997
+ // }
998
+ //
999
+ // if ($thisfile_mpeg_audio['version'] == '1') {
1000
+ // for ($channel = 0; $channel < $info['audio']['channels']; $channel++) {
1001
+ // for ($scfsi_band = 0; $scfsi_band < 4; $scfsi_band++) {
1002
+ // $thisfile_mpeg_audio['scfsi'][$channel][$scfsi_band] = substr($SideInfoBitstream, $SideInfoOffset, 1);
1003
+ // $SideInfoOffset += 2;
1004
+ // }
1005
+ // }
1006
+ // }
1007
+ // for ($granule = 0; $granule < (($thisfile_mpeg_audio['version'] == '1') ? 2 : 1); $granule++) {
1008
+ // for ($channel = 0; $channel < $info['audio']['channels']; $channel++) {
1009
+ // $thisfile_mpeg_audio['part2_3_length'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 12);
1010
+ // $SideInfoOffset += 12;
1011
+ // $thisfile_mpeg_audio['big_values'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 9);
1012
+ // $SideInfoOffset += 9;
1013
+ // $thisfile_mpeg_audio['global_gain'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 8);
1014
+ // $SideInfoOffset += 8;
1015
+ // if ($thisfile_mpeg_audio['version'] == '1') {
1016
+ // $thisfile_mpeg_audio['scalefac_compress'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 4);
1017
+ // $SideInfoOffset += 4;
1018
+ // } else {
1019
+ // $thisfile_mpeg_audio['scalefac_compress'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 9);
1020
+ // $SideInfoOffset += 9;
1021
+ // }
1022
+ // $thisfile_mpeg_audio['window_switching_flag'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1);
1023
+ // $SideInfoOffset += 1;
1024
+ //
1025
+ // if ($thisfile_mpeg_audio['window_switching_flag'][$granule][$channel] == '1') {
1026
+ //
1027
+ // $thisfile_mpeg_audio['block_type'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 2);
1028
+ // $SideInfoOffset += 2;
1029
+ // $thisfile_mpeg_audio['mixed_block_flag'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1);
1030
+ // $SideInfoOffset += 1;
1031
+ //
1032
+ // for ($region = 0; $region < 2; $region++) {
1033
+ // $thisfile_mpeg_audio['table_select'][$granule][$channel][$region] = substr($SideInfoBitstream, $SideInfoOffset, 5);
1034
+ // $SideInfoOffset += 5;
1035
+ // }
1036
+ // $thisfile_mpeg_audio['table_select'][$granule][$channel][2] = 0;
1037
+ //
1038
+ // for ($window = 0; $window < 3; $window++) {
1039
+ // $thisfile_mpeg_audio['subblock_gain'][$granule][$channel][$window] = substr($SideInfoBitstream, $SideInfoOffset, 3);
1040
+ // $SideInfoOffset += 3;
1041
+ // }
1042
+ //
1043
+ // } else {
1044
+ //
1045
+ // for ($region = 0; $region < 3; $region++) {
1046
+ // $thisfile_mpeg_audio['table_select'][$granule][$channel][$region] = substr($SideInfoBitstream, $SideInfoOffset, 5);
1047
+ // $SideInfoOffset += 5;
1048
+ // }
1049
+ //
1050
+ // $thisfile_mpeg_audio['region0_count'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 4);
1051
+ // $SideInfoOffset += 4;
1052
+ // $thisfile_mpeg_audio['region1_count'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 3);
1053
+ // $SideInfoOffset += 3;
1054
+ // $thisfile_mpeg_audio['block_type'][$granule][$channel] = 0;
1055
+ // }
1056
+ //
1057
+ // if ($thisfile_mpeg_audio['version'] == '1') {
1058
+ // $thisfile_mpeg_audio['preflag'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1);
1059
+ // $SideInfoOffset += 1;
1060
+ // }
1061
+ // $thisfile_mpeg_audio['scalefac_scale'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1);
1062
+ // $SideInfoOffset += 1;
1063
+ // $thisfile_mpeg_audio['count1table_select'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1);
1064
+ // $SideInfoOffset += 1;
1065
+ // }
1066
+ // }
1067
+ //}
1068
+
1069
+ return true;
1070
+ }
1071
+
1072
+ function RecursiveFrameScanning(&$offset, &$nextframetestoffset, $ScanAsCBR) {
1073
+ $info = &$this->getid3->info;
1074
+ $firstframetestarray = array('error'=>'', 'warning'=>'', 'avdataend'=>$info['avdataend'], 'avdataoffset'=>$info['avdataoffset']);
1075
+ $this->decodeMPEGaudioHeader($offset, $firstframetestarray, false);
1076
+
1077
+ for ($i = 0; $i < GETID3_MP3_VALID_CHECK_FRAMES; $i++) {
1078
+ // check next GETID3_MP3_VALID_CHECK_FRAMES frames for validity, to make sure we haven't run across a false synch
1079
+ if (($nextframetestoffset + 4) >= $info['avdataend']) {
1080
+ // end of file
1081
+ return true;
1082
+ }
1083
+
1084
+ $nextframetestarray = array('error'=>'', 'warning'=>'', 'avdataend'=>$info['avdataend'], 'avdataoffset'=>$info['avdataoffset']);
1085
+ if ($this->decodeMPEGaudioHeader($nextframetestoffset, $nextframetestarray, false)) {
1086
+ if ($ScanAsCBR) {
1087
+ // force CBR mode, used for trying to pick out invalid audio streams with valid(?) VBR headers, or VBR streams with no VBR header
1088
+ if (!isset($nextframetestarray['mpeg']['audio']['bitrate']) || !isset($firstframetestarray['mpeg']['audio']['bitrate']) || ($nextframetestarray['mpeg']['audio']['bitrate'] != $firstframetestarray['mpeg']['audio']['bitrate'])) {
1089
+ return false;
1090
+ }
1091
+ }
1092
+
1093
+
1094
+ // next frame is OK, get ready to check the one after that
1095
+ if (isset($nextframetestarray['mpeg']['audio']['framelength']) && ($nextframetestarray['mpeg']['audio']['framelength'] > 0)) {
1096
+ $nextframetestoffset += $nextframetestarray['mpeg']['audio']['framelength'];
1097
+ } else {
1098
+ $info['error'][] = 'Frame at offset ('.$offset.') is has an invalid frame length.';
1099
+ return false;
1100
+ }
1101
+
1102
+ } elseif (!empty($firstframetestarray['mpeg']['audio']['framelength']) && (($nextframetestoffset + $firstframetestarray['mpeg']['audio']['framelength']) > $info['avdataend'])) {
1103
+
1104
+ // it's not the end of the file, but there's not enough data left for another frame, so assume it's garbage/padding and return OK
1105
+ return true;
1106
+
1107
+ } else {
1108
+
1109
+ // next frame is not valid, note the error and fail, so scanning can contiue for a valid frame sequence
1110
+ $info['warning'][] = 'Frame at offset ('.$offset.') is valid, but the next one at ('.$nextframetestoffset.') is not.';
1111
+
1112
+ return false;
1113
+ }
1114
+ }
1115
+ return true;
1116
+ }
1117
+
1118
+ function FreeFormatFrameLength($offset, $deepscan=false) {
1119
+ $info = &$this->getid3->info;
1120
+
1121
+ fseek($this->getid3->fp, $offset, SEEK_SET);
1122
+ $MPEGaudioData = fread($this->getid3->fp, 32768);
1123
+
1124
+ $SyncPattern1 = substr($MPEGaudioData, 0, 4);
1125
+ // may be different pattern due to padding
1126
+ $SyncPattern2 = $SyncPattern1{0}.$SyncPattern1{1}.chr(ord($SyncPattern1{2}) | 0x02).$SyncPattern1{3};
1127
+ if ($SyncPattern2 === $SyncPattern1) {
1128
+ $SyncPattern2 = $SyncPattern1{0}.$SyncPattern1{1}.chr(ord($SyncPattern1{2}) & 0xFD).$SyncPattern1{3};
1129
+ }
1130
+
1131
+ $framelength = false;
1132
+ $framelength1 = strpos($MPEGaudioData, $SyncPattern1, 4);
1133
+ $framelength2 = strpos($MPEGaudioData, $SyncPattern2, 4);
1134
+ if ($framelength1 > 4) {
1135
+ $framelength = $framelength1;
1136
+ }
1137
+ if (($framelength2 > 4) && ($framelength2 < $framelength1)) {
1138
+ $framelength = $framelength2;
1139
+ }
1140
+ if (!$framelength) {
1141
+
1142
+ // LAME 3.88 has a different value for modeextension on the first frame vs the rest
1143
+ $framelength1 = strpos($MPEGaudioData, substr($SyncPattern1, 0, 3), 4);
1144
+ $framelength2 = strpos($MPEGaudioData, substr($SyncPattern2, 0, 3), 4);
1145
+
1146
+ if ($framelength1 > 4) {
1147
+ $framelength = $framelength1;
1148
+ }
1149
+ if (($framelength2 > 4) && ($framelength2 < $framelength1)) {
1150
+ $framelength = $framelength2;
1151
+ }
1152
+ if (!$framelength) {
1153
+ $info['error'][] = 'Cannot find next free-format synch pattern ('.getid3_lib::PrintHexBytes($SyncPattern1).' or '.getid3_lib::PrintHexBytes($SyncPattern2).') after offset '.$offset;
1154
+ return false;
1155
+ } else {
1156
+ $info['warning'][] = 'ModeExtension varies between first frame and other frames (known free-format issue in LAME 3.88)';
1157
+ $info['audio']['codec'] = 'LAME';
1158
+ $info['audio']['encoder'] = 'LAME3.88';
1159
+ $SyncPattern1 = substr($SyncPattern1, 0, 3);
1160
+ $SyncPattern2 = substr($SyncPattern2, 0, 3);
1161
+ }
1162
+ }
1163
+
1164
+ if ($deepscan) {
1165
+
1166
+ $ActualFrameLengthValues = array();
1167
+ $nextoffset = $offset + $framelength;
1168
+ while ($nextoffset < ($info['avdataend'] - 6)) {
1169
+ fseek($this->getid3->fp, $nextoffset - 1, SEEK_SET);
1170
+ $NextSyncPattern = fread($this->getid3->fp, 6);
1171
+ if ((substr($NextSyncPattern, 1, strlen($SyncPattern1)) == $SyncPattern1) || (substr($NextSyncPattern, 1, strlen($SyncPattern2)) == $SyncPattern2)) {
1172
+ // good - found where expected
1173
+ $ActualFrameLengthValues[] = $framelength;
1174
+ } elseif ((substr($NextSyncPattern, 0, strlen($SyncPattern1)) == $SyncPattern1) || (substr($NextSyncPattern, 0, strlen($SyncPattern2)) == $SyncPattern2)) {
1175
+ // ok - found one byte earlier than expected (last frame wasn't padded, first frame was)
1176
+ $ActualFrameLengthValues[] = ($framelength - 1);
1177
+ $nextoffset--;
1178
+ } elseif ((substr($NextSyncPattern, 2, strlen($SyncPattern1)) == $SyncPattern1) || (substr($NextSyncPattern, 2, strlen($SyncPattern2)) == $SyncPattern2)) {
1179
+ // ok - found one byte later than expected (last frame was padded, first frame wasn't)
1180
+ $ActualFrameLengthValues[] = ($framelength + 1);
1181
+ $nextoffset++;
1182
+ } else {
1183
+ $info['error'][] = 'Did not find expected free-format sync pattern at offset '.$nextoffset;
1184
+ return false;
1185
+ }
1186
+ $nextoffset += $framelength;
1187
+ }
1188
+ if (count($ActualFrameLengthValues) > 0) {
1189
+ $framelength = intval(round(array_sum($ActualFrameLengthValues) / count($ActualFrameLengthValues)));
1190
+ }
1191
+ }
1192
+ return $framelength;
1193
+ }
1194
+
1195
+ function getOnlyMPEGaudioInfoBruteForce() {
1196
+ $MPEGaudioHeaderDecodeCache = array();
1197
+ $MPEGaudioHeaderValidCache = array();
1198
+ $MPEGaudioHeaderLengthCache = array();
1199
+ $MPEGaudioVersionLookup = getid3_mp3::MPEGaudioVersionArray();
1200
+ $MPEGaudioLayerLookup = getid3_mp3::MPEGaudioLayerArray();
1201
+ $MPEGaudioBitrateLookup = getid3_mp3::MPEGaudioBitrateArray();
1202
+ $MPEGaudioFrequencyLookup = getid3_mp3::MPEGaudioFrequencyArray();
1203
+ $MPEGaudioChannelModeLookup = getid3_mp3::MPEGaudioChannelModeArray();
1204
+ $MPEGaudioModeExtensionLookup = getid3_mp3::MPEGaudioModeExtensionArray();
1205
+ $MPEGaudioEmphasisLookup = getid3_mp3::MPEGaudioEmphasisArray();
1206
+ $LongMPEGversionLookup = array();
1207
+ $LongMPEGlayerLookup = array();
1208
+ $LongMPEGbitrateLookup = array();
1209
+ $LongMPEGpaddingLookup = array();
1210
+ $LongMPEGfrequencyLookup = array();
1211
+ $Distribution['bitrate'] = array();
1212
+ $Distribution['frequency'] = array();
1213
+ $Distribution['layer'] = array();
1214
+ $Distribution['version'] = array();
1215
+ $Distribution['padding'] = array();
1216
+
1217
+ $info = &$this->getid3->info;
1218
+ fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
1219
+
1220
+ $max_frames_scan = 5000;
1221
+ $frames_scanned = 0;
1222
+
1223
+ $previousvalidframe = $info['avdataoffset'];
1224
+ while (ftell($this->getid3->fp) < $info['avdataend']) {
1225
+ set_time_limit(30);
1226
+ $head4 = fread($this->getid3->fp, 4);
1227
+ if (strlen($head4) < 4) {
1228
+ break;
1229
+ }
1230
+ if ($head4{0} != "\xFF") {
1231
+ for ($i = 1; $i < 4; $i++) {
1232
+ if ($head4{$i} == "\xFF") {
1233
+ fseek($this->getid3->fp, $i - 4, SEEK_CUR);
1234
+ continue 2;
1235
+ }
1236
+ }
1237
+ continue;
1238
+ }
1239
+ if (!isset($MPEGaudioHeaderDecodeCache[$head4])) {
1240
+ $MPEGaudioHeaderDecodeCache[$head4] = getid3_mp3::MPEGaudioHeaderDecode($head4);
1241
+ }
1242
+ if (!isset($MPEGaudioHeaderValidCache[$head4])) {
1243
+ $MPEGaudioHeaderValidCache[$head4] = getid3_mp3::MPEGaudioHeaderValid($MPEGaudioHeaderDecodeCache[$head4], false, false);
1244
+ }
1245
+ if ($MPEGaudioHeaderValidCache[$head4]) {
1246
+
1247
+ if (!isset($MPEGaudioHeaderLengthCache[$head4])) {
1248
+ $LongMPEGversionLookup[$head4] = $MPEGaudioVersionLookup[$MPEGaudioHeaderDecodeCache[$head4]['version']];
1249
+ $LongMPEGlayerLookup[$head4] = $MPEGaudioLayerLookup[$MPEGaudioHeaderDecodeCache[$head4]['layer']];
1250
+ $LongMPEGbitrateLookup[$head4] = $MPEGaudioBitrateLookup[$LongMPEGversionLookup[$head4]][$LongMPEGlayerLookup[$head4]][$MPEGaudioHeaderDecodeCache[$head4]['bitrate']];
1251
+ $LongMPEGpaddingLookup[$head4] = (bool) $MPEGaudioHeaderDecodeCache[$head4]['padding'];
1252
+ $LongMPEGfrequencyLookup[$head4] = $MPEGaudioFrequencyLookup[$LongMPEGversionLookup[$head4]][$MPEGaudioHeaderDecodeCache[$head4]['sample_rate']];
1253
+ $MPEGaudioHeaderLengthCache[$head4] = getid3_mp3::MPEGaudioFrameLength(
1254
+ $LongMPEGbitrateLookup[$head4],
1255
+ $LongMPEGversionLookup[$head4],
1256
+ $LongMPEGlayerLookup[$head4],
1257
+ $LongMPEGpaddingLookup[$head4],
1258
+ $LongMPEGfrequencyLookup[$head4]);
1259
+ }
1260
+ if ($MPEGaudioHeaderLengthCache[$head4] > 4) {
1261
+ $WhereWeWere = ftell($this->getid3->fp);
1262
+ fseek($this->getid3->fp, $MPEGaudioHeaderLengthCache[$head4] - 4, SEEK_CUR);
1263
+ $next4 = fread($this->getid3->fp, 4);
1264
+ if ($next4{0} == "\xFF") {
1265
+ if (!isset($MPEGaudioHeaderDecodeCache[$next4])) {
1266
+ $MPEGaudioHeaderDecodeCache[$next4] = getid3_mp3::MPEGaudioHeaderDecode($next4);
1267
+ }
1268
+ if (!isset($MPEGaudioHeaderValidCache[$next4])) {
1269
+ $MPEGaudioHeaderValidCache[$next4] = getid3_mp3::MPEGaudioHeaderValid($MPEGaudioHeaderDecodeCache[$next4], false, false);
1270
+ }
1271
+ if ($MPEGaudioHeaderValidCache[$next4]) {
1272
+ fseek($this->getid3->fp, -4, SEEK_CUR);
1273
+
1274
+ getid3_lib::safe_inc($Distribution['bitrate'][$LongMPEGbitrateLookup[$head4]]);
1275
+ getid3_lib::safe_inc($Distribution['layer'][$LongMPEGlayerLookup[$head4]]);
1276
+ getid3_lib::safe_inc($Distribution['version'][$LongMPEGversionLookup[$head4]]);
1277
+ getid3_lib::safe_inc($Distribution['padding'][intval($LongMPEGpaddingLookup[$head4])]);
1278
+ getid3_lib::safe_inc($Distribution['frequency'][$LongMPEGfrequencyLookup[$head4]]);
1279
+ if ($max_frames_scan && (++$frames_scanned >= $max_frames_scan)) {
1280
+ $pct_data_scanned = (ftell($this->getid3->fp) - $info['avdataoffset']) / ($info['avdataend'] - $info['avdataoffset']);
1281
+ $info['warning'][] = 'too many MPEG audio frames to scan, only scanned first '.$max_frames_scan.' frames ('.number_format($pct_data_scanned * 100, 1).'% of file) and extrapolated distribution, playtime and bitrate may be incorrect.';
1282
+ foreach ($Distribution as $key1 => $value1) {
1283
+ foreach ($value1 as $key2 => $value2) {
1284
+ $Distribution[$key1][$key2] = round($value2 / $pct_data_scanned);
1285
+ }
1286
+ }
1287
+ break;
1288
+ }
1289
+ continue;
1290
+ }
1291
+ }
1292
+ unset($next4);
1293
+ fseek($this->getid3->fp, $WhereWeWere - 3, SEEK_SET);
1294
+ }
1295
+
1296
+ }
1297
+ }
1298
+ foreach ($Distribution as $key => $value) {
1299
+ ksort($Distribution[$key], SORT_NUMERIC);
1300
+ }
1301
+ ksort($Distribution['version'], SORT_STRING);
1302
+ $info['mpeg']['audio']['bitrate_distribution'] = $Distribution['bitrate'];
1303
+ $info['mpeg']['audio']['frequency_distribution'] = $Distribution['frequency'];
1304
+ $info['mpeg']['audio']['layer_distribution'] = $Distribution['layer'];
1305
+ $info['mpeg']['audio']['version_distribution'] = $Distribution['version'];
1306
+ $info['mpeg']['audio']['padding_distribution'] = $Distribution['padding'];
1307
+ if (count($Distribution['version']) > 1) {
1308
+ $info['error'][] = 'Corrupt file - more than one MPEG version detected';
1309
+ }
1310
+ if (count($Distribution['layer']) > 1) {
1311
+ $info['error'][] = 'Corrupt file - more than one MPEG layer detected';
1312
+ }
1313
+ if (count($Distribution['frequency']) > 1) {
1314
+ $info['error'][] = 'Corrupt file - more than one MPEG sample rate detected';
1315
+ }
1316
+
1317
+
1318
+ $bittotal = 0;
1319
+ foreach ($Distribution['bitrate'] as $bitratevalue => $bitratecount) {
1320
+ if ($bitratevalue != 'free') {
1321
+ $bittotal += ($bitratevalue * $bitratecount);
1322
+ }
1323
+ }
1324
+ $info['mpeg']['audio']['frame_count'] = array_sum($Distribution['bitrate']);
1325
+ if ($info['mpeg']['audio']['frame_count'] == 0) {
1326
+ $info['error'][] = 'no MPEG audio frames found';
1327
+ return false;
1328
+ }
1329
+ $info['mpeg']['audio']['bitrate'] = ($bittotal / $info['mpeg']['audio']['frame_count']);
1330
+ $info['mpeg']['audio']['bitrate_mode'] = ((count($Distribution['bitrate']) > 0) ? 'vbr' : 'cbr');
1331
+ $info['mpeg']['audio']['sample_rate'] = getid3_lib::array_max($Distribution['frequency'], true);
1332
+
1333
+ $info['audio']['bitrate'] = $info['mpeg']['audio']['bitrate'];
1334
+ $info['audio']['bitrate_mode'] = $info['mpeg']['audio']['bitrate_mode'];
1335
+ $info['audio']['sample_rate'] = $info['mpeg']['audio']['sample_rate'];
1336
+ $info['audio']['dataformat'] = 'mp'.getid3_lib::array_max($Distribution['layer'], true);
1337
+ $info['fileformat'] = $info['audio']['dataformat'];
1338
+
1339
+ return true;
1340
+ }
1341
+
1342
+
1343
+ function getOnlyMPEGaudioInfo($avdataoffset, $BitrateHistogram=false) {
1344
+ // looks for synch, decodes MPEG audio header
1345
+
1346
+ $info = &$this->getid3->info;
1347
+
1348
+ static $MPEGaudioVersionLookup;
1349
+ static $MPEGaudioLayerLookup;
1350
+ static $MPEGaudioBitrateLookup;
1351
+ if (empty($MPEGaudioVersionLookup)) {
1352
+ $MPEGaudioVersionLookup = getid3_mp3::MPEGaudioVersionArray();
1353
+ $MPEGaudioLayerLookup = getid3_mp3::MPEGaudioLayerArray();
1354
+ $MPEGaudioBitrateLookup = getid3_mp3::MPEGaudioBitrateArray();
1355
+
1356
+ }
1357
+
1358
+ fseek($this->getid3->fp, $avdataoffset, SEEK_SET);
1359
+ $sync_seek_buffer_size = min(128 * 1024, $info['avdataend'] - $avdataoffset);
1360
+ if ($sync_seek_buffer_size <= 0) {
1361
+ $info['error'][] = 'Invalid $sync_seek_buffer_size at offset '.$avdataoffset;
1362
+ return false;
1363
+ }
1364
+ $header = fread($this->getid3->fp, $sync_seek_buffer_size);
1365
+ $sync_seek_buffer_size = strlen($header);
1366
+ $SynchSeekOffset = 0;
1367
+ while ($SynchSeekOffset < $sync_seek_buffer_size) {
1368
+ if ((($avdataoffset + $SynchSeekOffset) < $info['avdataend']) && !feof($this->getid3->fp)) {
1369
+
1370
+ if ($SynchSeekOffset > $sync_seek_buffer_size) {
1371
+ // if a synch's not found within the first 128k bytes, then give up
1372
+ $info['error'][] = 'Could not find valid MPEG audio synch within the first '.round($sync_seek_buffer_size / 1024).'kB';
1373
+ if (isset($info['audio']['bitrate'])) {
1374
+ unset($info['audio']['bitrate']);
1375
+ }
1376
+ if (isset($info['mpeg']['audio'])) {
1377
+ unset($info['mpeg']['audio']);
1378
+ }
1379
+ if (empty($info['mpeg'])) {
1380
+ unset($info['mpeg']);
1381
+ }
1382
+ return false;
1383
+
1384
+ } elseif (feof($this->getid3->fp)) {
1385
+
1386
+ $info['error'][] = 'Could not find valid MPEG audio synch before end of file';
1387
+ if (isset($info['audio']['bitrate'])) {
1388
+ unset($info['audio']['bitrate']);
1389
+ }
1390
+ if (isset($info['mpeg']['audio'])) {
1391
+ unset($info['mpeg']['audio']);
1392
+ }
1393
+ if (isset($info['mpeg']) && (!is_array($info['mpeg']) || (count($info['mpeg']) == 0))) {
1394
+ unset($info['mpeg']);
1395
+ }
1396
+ return false;
1397
+ }
1398
+ }
1399
+
1400
+ if (($SynchSeekOffset + 1) >= strlen($header)) {
1401
+ $info['error'][] = 'Could not find valid MPEG synch before end of file';
1402
+ return false;
1403
+ }
1404
+
1405
+ if (($header{$SynchSeekOffset} == "\xFF") && ($header{($SynchSeekOffset + 1)} > "\xE0")) { // synch detected
1406
+ if (!isset($FirstFrameThisfileInfo) && !isset($info['mpeg']['audio'])) {
1407
+ $FirstFrameThisfileInfo = $info;
1408
+ $FirstFrameAVDataOffset = $avdataoffset + $SynchSeekOffset;
1409
+ if (!$this->decodeMPEGaudioHeader($FirstFrameAVDataOffset, $FirstFrameThisfileInfo, false)) {
1410
+ // if this is the first valid MPEG-audio frame, save it in case it's a VBR header frame and there's
1411
+ // garbage between this frame and a valid sequence of MPEG-audio frames, to be restored below
1412
+ unset($FirstFrameThisfileInfo);
1413
+ }
1414
+ }
1415
+
1416
+ $dummy = $info; // only overwrite real data if valid header found
1417
+ if ($this->decodeMPEGaudioHeader($avdataoffset + $SynchSeekOffset, $dummy, true)) {
1418
+ $info = $dummy;
1419
+ $info['avdataoffset'] = $avdataoffset + $SynchSeekOffset;
1420
+ switch (isset($info['fileformat']) ? $info['fileformat'] : '') {
1421
+ case '':
1422
+ case 'id3':
1423
+ case 'ape':
1424
+ case 'mp3':
1425
+ $info['fileformat'] = 'mp3';
1426
+ $info['audio']['dataformat'] = 'mp3';
1427
+ break;
1428
+ }
1429
+ if (isset($FirstFrameThisfileInfo['mpeg']['audio']['bitrate_mode']) && ($FirstFrameThisfileInfo['mpeg']['audio']['bitrate_mode'] == 'vbr')) {
1430
+ if (!(abs($info['audio']['bitrate'] - $FirstFrameThisfileInfo['audio']['bitrate']) <= 1)) {
1431
+ // If there is garbage data between a valid VBR header frame and a sequence
1432
+ // of valid MPEG-audio frames the VBR data is no longer discarded.
1433
+ $info = $FirstFrameThisfileInfo;
1434
+ $info['avdataoffset'] = $FirstFrameAVDataOffset;
1435
+ $info['fileformat'] = 'mp3';
1436
+ $info['audio']['dataformat'] = 'mp3';
1437
+ $dummy = $info;
1438
+ unset($dummy['mpeg']['audio']);
1439
+ $GarbageOffsetStart = $FirstFrameAVDataOffset + $FirstFrameThisfileInfo['mpeg']['audio']['framelength'];
1440
+ $GarbageOffsetEnd = $avdataoffset + $SynchSeekOffset;
1441
+ if ($this->decodeMPEGaudioHeader($GarbageOffsetEnd, $dummy, true, true)) {
1442
+ $info = $dummy;
1443
+ $info['avdataoffset'] = $GarbageOffsetEnd;
1444
+ $info['warning'][] = 'apparently-valid VBR header not used because could not find '.GETID3_MP3_VALID_CHECK_FRAMES.' consecutive MPEG-audio frames immediately after VBR header (garbage data for '.($GarbageOffsetEnd - $GarbageOffsetStart).' bytes between '.$GarbageOffsetStart.' and '.$GarbageOffsetEnd.'), but did find valid CBR stream starting at '.$GarbageOffsetEnd;
1445
+ } else {
1446
+ $info['warning'][] = 'using data from VBR header even though could not find '.GETID3_MP3_VALID_CHECK_FRAMES.' consecutive MPEG-audio frames immediately after VBR header (garbage data for '.($GarbageOffsetEnd - $GarbageOffsetStart).' bytes between '.$GarbageOffsetStart.' and '.$GarbageOffsetEnd.')';
1447
+ }
1448
+ }
1449
+ }
1450
+ if (isset($info['mpeg']['audio']['bitrate_mode']) && ($info['mpeg']['audio']['bitrate_mode'] == 'vbr') && !isset($info['mpeg']['audio']['VBR_method'])) {
1451
+ // VBR file with no VBR header
1452
+ $BitrateHistogram = true;
1453
+ }
1454
+
1455
+ if ($BitrateHistogram) {
1456
+
1457
+ $info['mpeg']['audio']['stereo_distribution'] = array('stereo'=>0, 'joint stereo'=>0, 'dual channel'=>0, 'mono'=>0);
1458
+ $info['mpeg']['audio']['version_distribution'] = array('1'=>0, '2'=>0, '2.5'=>0);
1459
+
1460
+ if ($info['mpeg']['audio']['version'] == '1') {
1461
+ if ($info['mpeg']['audio']['layer'] == 3) {
1462
+ $info['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 32000=>0, 40000=>0, 48000=>0, 56000=>0, 64000=>0, 80000=>0, 96000=>0, 112000=>0, 128000=>0, 160000=>0, 192000=>0, 224000=>0, 256000=>0, 320000=>0);
1463
+ } elseif ($info['mpeg']['audio']['layer'] == 2) {
1464
+ $info['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 32000=>0, 48000=>0, 56000=>0, 64000=>0, 80000=>0, 96000=>0, 112000=>0, 128000=>0, 160000=>0, 192000=>0, 224000=>0, 256000=>0, 320000=>0, 384000=>0);
1465
+ } elseif ($info['mpeg']['audio']['layer'] == 1) {
1466
+ $info['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 32000=>0, 64000=>0, 96000=>0, 128000=>0, 160000=>0, 192000=>0, 224000=>0, 256000=>0, 288000=>0, 320000=>0, 352000=>0, 384000=>0, 416000=>0, 448000=>0);
1467
+ }
1468
+ } elseif ($info['mpeg']['audio']['layer'] == 1) {
1469
+ $info['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 32000=>0, 48000=>0, 56000=>0, 64000=>0, 80000=>0, 96000=>0, 112000=>0, 128000=>0, 144000=>0, 160000=>0, 176000=>0, 192000=>0, 224000=>0, 256000=>0);
1470
+ } else {
1471
+ $info['mpeg']['audio']['bitrate_distribution'] = array('free'=>0, 8000=>0, 16000=>0, 24000=>0, 32000=>0, 40000=>0, 48000=>0, 56000=>0, 64000=>0, 80000=>0, 96000=>0, 112000=>0, 128000=>0, 144000=>0, 160000=>0);
1472
+ }
1473
+
1474
+ $dummy = array('error'=>$info['error'], 'warning'=>$info['warning'], 'avdataend'=>$info['avdataend'], 'avdataoffset'=>$info['avdataoffset']);
1475
+ $synchstartoffset = $info['avdataoffset'];
1476
+ fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET);
1477
+
1478
+ // you can play with these numbers:
1479
+ $max_frames_scan = 50000;
1480
+ $max_scan_segments = 10;
1481
+
1482
+ // don't play with these numbers:
1483
+ $FastMode = false;
1484
+ $SynchErrorsFound = 0;
1485
+ $frames_scanned = 0;
1486
+ $this_scan_segment = 0;
1487
+ $frames_scan_per_segment = ceil($max_frames_scan / $max_scan_segments);
1488
+ $pct_data_scanned = 0;
1489
+ for ($current_segment = 0; $current_segment < $max_scan_segments; $current_segment++) {
1490
+ $frames_scanned_this_segment = 0;
1491
+ if (ftell($this->getid3->fp) >= $info['avdataend']) {
1492
+ break;
1493
+ }
1494
+ $scan_start_offset[$current_segment] = max(ftell($this->getid3->fp), $info['avdataoffset'] + round($current_segment * (($info['avdataend'] - $info['avdataoffset']) / $max_scan_segments)));
1495
+ if ($current_segment > 0) {
1496
+ fseek($this->getid3->fp, $scan_start_offset[$current_segment], SEEK_SET);
1497
+ $buffer_4k = fread($this->getid3->fp, 4096);
1498
+ for ($j = 0; $j < (strlen($buffer_4k) - 4); $j++) {
1499
+ if (($buffer_4k{$j} == "\xFF") && ($buffer_4k{($j + 1)} > "\xE0")) { // synch detected
1500
+ if ($this->decodeMPEGaudioHeader($scan_start_offset[$current_segment] + $j, $dummy, false, false, $FastMode)) {
1501
+ $calculated_next_offset = $scan_start_offset[$current_segment] + $j + $dummy['mpeg']['audio']['framelength'];
1502
+ if ($this->decodeMPEGaudioHeader($calculated_next_offset, $dummy, false, false, $FastMode)) {
1503
+ $scan_start_offset[$current_segment] += $j;
1504
+ break;
1505
+ }
1506
+ }
1507
+ }
1508
+ }
1509
+ }
1510
+ $synchstartoffset = $scan_start_offset[$current_segment];
1511
+ while ($this->decodeMPEGaudioHeader($synchstartoffset, $dummy, false, false, $FastMode)) {
1512
+ $FastMode = true;
1513
+ $thisframebitrate = $MPEGaudioBitrateLookup[$MPEGaudioVersionLookup[$dummy['mpeg']['audio']['raw']['version']]][$MPEGaudioLayerLookup[$dummy['mpeg']['audio']['raw']['layer']]][$dummy['mpeg']['audio']['raw']['bitrate']];
1514
+
1515
+ if (empty($dummy['mpeg']['audio']['framelength'])) {
1516
+ $SynchErrorsFound++;
1517
+ $synchstartoffset++;
1518
+ } else {
1519
+ getid3_lib::safe_inc($info['mpeg']['audio']['bitrate_distribution'][$thisframebitrate]);
1520
+ getid3_lib::safe_inc($info['mpeg']['audio']['stereo_distribution'][$dummy['mpeg']['audio']['channelmode']]);
1521
+ getid3_lib::safe_inc($info['mpeg']['audio']['version_distribution'][$dummy['mpeg']['audio']['version']]);
1522
+ $synchstartoffset += $dummy['mpeg']['audio']['framelength'];
1523
+ }
1524
+ $frames_scanned++;
1525
+ if ($frames_scan_per_segment && (++$frames_scanned_this_segment >= $frames_scan_per_segment)) {
1526
+ $this_pct_scanned = (ftell($this->getid3->fp) - $scan_start_offset[$current_segment]) / ($info['avdataend'] - $info['avdataoffset']);
1527
+ if (($current_segment == 0) && (($this_pct_scanned * $max_scan_segments) >= 1)) {
1528
+ // file likely contains < $max_frames_scan, just scan as one segment
1529
+ $max_scan_segments = 1;
1530
+ $frames_scan_per_segment = $max_frames_scan;
1531
+ } else {
1532
+ $pct_data_scanned += $this_pct_scanned;
1533
+ break;
1534
+ }
1535
+ }
1536
+ }
1537
+ }
1538
+ if ($pct_data_scanned > 0) {
1539
+ $info['warning'][] = 'too many MPEG audio frames to scan, only scanned '.$frames_scanned.' frames in '.$max_scan_segments.' segments ('.number_format($pct_data_scanned * 100, 1).'% of file) and extrapolated distribution, playtime and bitrate may be incorrect.';
1540
+ foreach ($info['mpeg']['audio'] as $key1 => $value1) {
1541
+ if (!preg_match('#_distribution$#i', $key1)) {
1542
+ continue;
1543
+ }
1544
+ foreach ($value1 as $key2 => $value2) {
1545
+ $info['mpeg']['audio'][$key1][$key2] = round($value2 / $pct_data_scanned);
1546
+ }
1547
+ }
1548
+ }
1549
+
1550
+ if ($SynchErrorsFound > 0) {
1551
+ $info['warning'][] = 'Found '.$SynchErrorsFound.' synch errors in histogram analysis';
1552
+ //return false;
1553
+ }
1554
+
1555
+ $bittotal = 0;
1556
+ $framecounter = 0;
1557
+ foreach ($info['mpeg']['audio']['bitrate_distribution'] as $bitratevalue => $bitratecount) {
1558
+ $framecounter += $bitratecount;
1559
+ if ($bitratevalue != 'free') {
1560
+ $bittotal += ($bitratevalue * $bitratecount);
1561
+ }
1562
+ }
1563
+ if ($framecounter == 0) {
1564
+ $info['error'][] = 'Corrupt MP3 file: framecounter == zero';
1565
+ return false;
1566
+ }
1567
+ $info['mpeg']['audio']['frame_count'] = getid3_lib::CastAsInt($framecounter);
1568
+ $info['mpeg']['audio']['bitrate'] = ($bittotal / $framecounter);
1569
+
1570
+ $info['audio']['bitrate'] = $info['mpeg']['audio']['bitrate'];
1571
+
1572
+
1573
+ // Definitively set VBR vs CBR, even if the Xing/LAME/VBRI header says differently
1574
+ $distinct_bitrates = 0;
1575
+ foreach ($info['mpeg']['audio']['bitrate_distribution'] as $bitrate_value => $bitrate_count) {
1576
+ if ($bitrate_count > 0) {
1577
+ $distinct_bitrates++;
1578
+ }
1579
+ }
1580
+ if ($distinct_bitrates > 1) {
1581
+ $info['mpeg']['audio']['bitrate_mode'] = 'vbr';
1582
+ } else {
1583
+ $info['mpeg']['audio']['bitrate_mode'] = 'cbr';
1584
+ }
1585
+ $info['audio']['bitrate_mode'] = $info['mpeg']['audio']['bitrate_mode'];
1586
+
1587
+ }
1588
+
1589
+ break; // exit while()
1590
+ }
1591
+ }
1592
+
1593
+ $SynchSeekOffset++;
1594
+ if (($avdataoffset + $SynchSeekOffset) >= $info['avdataend']) {
1595
+ // end of file/data
1596
+
1597
+ if (empty($info['mpeg']['audio'])) {
1598
+
1599
+ $info['error'][] = 'could not find valid MPEG synch before end of file';
1600
+ if (isset($info['audio']['bitrate'])) {
1601
+ unset($info['audio']['bitrate']);
1602
+ }
1603
+ if (isset($info['mpeg']['audio'])) {
1604
+ unset($info['mpeg']['audio']);
1605
+ }
1606
+ if (isset($info['mpeg']) && (!is_array($info['mpeg']) || empty($info['mpeg']))) {
1607
+ unset($info['mpeg']);
1608
+ }
1609
+ return false;
1610
+
1611
+ }
1612
+ break;
1613
+ }
1614
+
1615
+ }
1616
+ $info['audio']['channels'] = $info['mpeg']['audio']['channels'];
1617
+ $info['audio']['channelmode'] = $info['mpeg']['audio']['channelmode'];
1618
+ $info['audio']['sample_rate'] = $info['mpeg']['audio']['sample_rate'];
1619
+ return true;
1620
+ }
1621
+
1622
+
1623
+ static function MPEGaudioVersionArray() {
1624
+ static $MPEGaudioVersion = array('2.5', false, '2', '1');
1625
+ return $MPEGaudioVersion;
1626
+ }
1627
+
1628
+ static function MPEGaudioLayerArray() {
1629
+ static $MPEGaudioLayer = array(false, 3, 2, 1);
1630
+ return $MPEGaudioLayer;
1631
+ }
1632
+
1633
+ static function MPEGaudioBitrateArray() {
1634
+ static $MPEGaudioBitrate;
1635
+ if (empty($MPEGaudioBitrate)) {
1636
+ $MPEGaudioBitrate = array (
1637
+ '1' => array (1 => array('free', 32000, 64000, 96000, 128000, 160000, 192000, 224000, 256000, 288000, 320000, 352000, 384000, 416000, 448000),
1638
+ 2 => array('free', 32000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 160000, 192000, 224000, 256000, 320000, 384000),
1639
+ 3 => array('free', 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 160000, 192000, 224000, 256000, 320000)
1640
+ ),
1641
+
1642
+ '2' => array (1 => array('free', 32000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, 160000, 176000, 192000, 224000, 256000),
1643
+ 2 => array('free', 8000, 16000, 24000, 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, 160000),
1644
+ )
1645
+ );
1646
+ $MPEGaudioBitrate['2'][3] = $MPEGaudioBitrate['2'][2];
1647
+ $MPEGaudioBitrate['2.5'] = $MPEGaudioBitrate['2'];
1648
+ }
1649
+ return $MPEGaudioBitrate;
1650
+ }
1651
+
1652
+ static function MPEGaudioFrequencyArray() {
1653
+ static $MPEGaudioFrequency;
1654
+ if (empty($MPEGaudioFrequency)) {
1655
+ $MPEGaudioFrequency = array (
1656
+ '1' => array(44100, 48000, 32000),
1657
+ '2' => array(22050, 24000, 16000),
1658
+ '2.5' => array(11025, 12000, 8000)
1659
+ );
1660
+ }
1661
+ return $MPEGaudioFrequency;
1662
+ }
1663
+
1664
+ static function MPEGaudioChannelModeArray() {
1665
+ static $MPEGaudioChannelMode = array('stereo', 'joint stereo', 'dual channel', 'mono');
1666
+ return $MPEGaudioChannelMode;
1667
+ }
1668
+
1669
+ static function MPEGaudioModeExtensionArray() {
1670
+ static $MPEGaudioModeExtension;
1671
+ if (empty($MPEGaudioModeExtension)) {
1672
+ $MPEGaudioModeExtension = array (
1673
+ 1 => array('4-31', '8-31', '12-31', '16-31'),
1674
+ 2 => array('4-31', '8-31', '12-31', '16-31'),
1675
+ 3 => array('', 'IS', 'MS', 'IS+MS')
1676
+ );
1677
+ }
1678
+ return $MPEGaudioModeExtension;
1679
+ }
1680
+
1681
+ static function MPEGaudioEmphasisArray() {
1682
+ static $MPEGaudioEmphasis = array('none', '50/15ms', false, 'CCIT J.17');
1683
+ return $MPEGaudioEmphasis;
1684
+ }
1685
+
1686
+ static function MPEGaudioHeaderBytesValid($head4, $allowBitrate15=false) {
1687
+ return getid3_mp3::MPEGaudioHeaderValid(getid3_mp3::MPEGaudioHeaderDecode($head4), false, $allowBitrate15);
1688
+ }
1689
+
1690
+ static function MPEGaudioHeaderValid($rawarray, $echoerrors=false, $allowBitrate15=false) {
1691
+ if (($rawarray['synch'] & 0x0FFE) != 0x0FFE) {
1692
+ return false;
1693
+ }
1694
+
1695
+ static $MPEGaudioVersionLookup;
1696
+ static $MPEGaudioLayerLookup;
1697
+ static $MPEGaudioBitrateLookup;
1698
+ static $MPEGaudioFrequencyLookup;
1699
+ static $MPEGaudioChannelModeLookup;
1700
+ static $MPEGaudioModeExtensionLookup;
1701
+ static $MPEGaudioEmphasisLookup;
1702
+ if (empty($MPEGaudioVersionLookup)) {
1703
+ $MPEGaudioVersionLookup = getid3_mp3::MPEGaudioVersionArray();
1704
+ $MPEGaudioLayerLookup = getid3_mp3::MPEGaudioLayerArray();
1705
+ $MPEGaudioBitrateLookup = getid3_mp3::MPEGaudioBitrateArray();
1706
+ $MPEGaudioFrequencyLookup = getid3_mp3::MPEGaudioFrequencyArray();
1707
+ $MPEGaudioChannelModeLookup = getid3_mp3::MPEGaudioChannelModeArray();
1708
+ $MPEGaudioModeExtensionLookup = getid3_mp3::MPEGaudioModeExtensionArray();
1709
+ $MPEGaudioEmphasisLookup = getid3_mp3::MPEGaudioEmphasisArray();
1710
+ }
1711
+
1712
+ if (isset($MPEGaudioVersionLookup[$rawarray['version']])) {
1713
+ $decodedVersion = $MPEGaudioVersionLookup[$rawarray['version']];
1714
+ } else {
1715
+ echo ($echoerrors ? "\n".'invalid Version ('.$rawarray['version'].')' : '');
1716
+ return false;
1717
+ }
1718
+ if (isset($MPEGaudioLayerLookup[$rawarray['layer']])) {
1719
+ $decodedLayer = $MPEGaudioLayerLookup[$rawarray['layer']];
1720
+ } else {
1721
+ echo ($echoerrors ? "\n".'invalid Layer ('.$rawarray['layer'].')' : '');
1722
+ return false;
1723
+ }
1724
+ if (!isset($MPEGaudioBitrateLookup[$decodedVersion][$decodedLayer][$rawarray['bitrate']])) {
1725
+ echo ($echoerrors ? "\n".'invalid Bitrate ('.$rawarray['bitrate'].')' : '');
1726
+ if ($rawarray['bitrate'] == 15) {
1727
+ // known issue in LAME 3.90 - 3.93.1 where free-format has bitrate ID of 15 instead of 0
1728
+ // let it go through here otherwise file will not be identified
1729
+ if (!$allowBitrate15) {
1730
+ return false;
1731
+ }
1732
+ } else {
1733
+ return false;
1734
+ }
1735
+ }
1736
+ if (!isset($MPEGaudioFrequencyLookup[$decodedVersion][$rawarray['sample_rate']])) {
1737
+ echo ($echoerrors ? "\n".'invalid Frequency ('.$rawarray['sample_rate'].')' : '');
1738
+ return false;
1739
+ }
1740
+ if (!isset($MPEGaudioChannelModeLookup[$rawarray['channelmode']])) {
1741
+ echo ($echoerrors ? "\n".'invalid ChannelMode ('.$rawarray['channelmode'].')' : '');
1742
+ return false;
1743
+ }
1744
+ if (!isset($MPEGaudioModeExtensionLookup[$decodedLayer][$rawarray['modeextension']])) {
1745
+ echo ($echoerrors ? "\n".'invalid Mode Extension ('.$rawarray['modeextension'].')' : '');
1746
+ return false;
1747
+ }
1748
+ if (!isset($MPEGaudioEmphasisLookup[$rawarray['emphasis']])) {
1749
+ echo ($echoerrors ? "\n".'invalid Emphasis ('.$rawarray['emphasis'].')' : '');
1750
+ return false;
1751
+ }
1752
+ // These are just either set or not set, you can't mess that up :)
1753
+ // $rawarray['protection'];
1754
+ // $rawarray['padding'];
1755
+ // $rawarray['private'];
1756
+ // $rawarray['copyright'];
1757
+ // $rawarray['original'];
1758
+
1759
+ return true;
1760
+ }
1761
+
1762
+ static function MPEGaudioHeaderDecode($Header4Bytes) {
1763
+ // AAAA AAAA AAAB BCCD EEEE FFGH IIJJ KLMM
1764
+ // A - Frame sync (all bits set)
1765
+ // B - MPEG Audio version ID
1766
+ // C - Layer description
1767
+ // D - Protection bit
1768
+ // E - Bitrate index
1769
+ // F - Sampling rate frequency index
1770
+ // G - Padding bit
1771
+ // H - Private bit
1772
+ // I - Channel Mode
1773
+ // J - Mode extension (Only if Joint stereo)
1774
+ // K - Copyright
1775
+ // L - Original
1776
+ // M - Emphasis
1777
+
1778
+ if (strlen($Header4Bytes) != 4) {
1779
+ return false;
1780
+ }
1781
+
1782
+ $MPEGrawHeader['synch'] = (getid3_lib::BigEndian2Int(substr($Header4Bytes, 0, 2)) & 0xFFE0) >> 4;
1783
+ $MPEGrawHeader['version'] = (ord($Header4Bytes{1}) & 0x18) >> 3; // BB
1784
+ $MPEGrawHeader['layer'] = (ord($Header4Bytes{1}) & 0x06) >> 1; // CC
1785
+ $MPEGrawHeader['protection'] = (ord($Header4Bytes{1}) & 0x01); // D
1786
+ $MPEGrawHeader['bitrate'] = (ord($Header4Bytes{2}) & 0xF0) >> 4; // EEEE
1787
+ $MPEGrawHeader['sample_rate'] = (ord($Header4Bytes{2}) & 0x0C) >> 2; // FF
1788
+ $MPEGrawHeader['padding'] = (ord($Header4Bytes{2}) & 0x02) >> 1; // G
1789
+ $MPEGrawHeader['private'] = (ord($Header4Bytes{2}) & 0x01); // H
1790
+ $MPEGrawHeader['channelmode'] = (ord($Header4Bytes{3}) & 0xC0) >> 6; // II
1791
+ $MPEGrawHeader['modeextension'] = (ord($Header4Bytes{3}) & 0x30) >> 4; // JJ
1792
+ $MPEGrawHeader['copyright'] = (ord($Header4Bytes{3}) & 0x08) >> 3; // K
1793
+ $MPEGrawHeader['original'] = (ord($Header4Bytes{3}) & 0x04) >> 2; // L
1794
+ $MPEGrawHeader['emphasis'] = (ord($Header4Bytes{3}) & 0x03); // MM
1795
+
1796
+ return $MPEGrawHeader;
1797
+ }
1798
+
1799
+ static function MPEGaudioFrameLength(&$bitrate, &$version, &$layer, $padding, &$samplerate) {
1800
+ static $AudioFrameLengthCache = array();
1801
+
1802
+ if (!isset($AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate])) {
1803
+ $AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate] = false;
1804
+ if ($bitrate != 'free') {
1805
+
1806
+ if ($version == '1') {
1807
+
1808
+ if ($layer == '1') {
1809
+
1810
+ // For Layer I slot is 32 bits long
1811
+ $FrameLengthCoefficient = 48;
1812
+ $SlotLength = 4;
1813
+
1814
+ } else { // Layer 2 / 3
1815
+
1816
+ // for Layer 2 and Layer 3 slot is 8 bits long.
1817
+ $FrameLengthCoefficient = 144;
1818
+ $SlotLength = 1;
1819
+
1820
+ }
1821
+
1822
+ } else { // MPEG-2 / MPEG-2.5
1823
+
1824
+ if ($layer == '1') {
1825
+
1826
+ // For Layer I slot is 32 bits long
1827
+ $FrameLengthCoefficient = 24;
1828
+ $SlotLength = 4;
1829
+
1830
+ } elseif ($layer == '2') {
1831
+
1832
+ // for Layer 2 and Layer 3 slot is 8 bits long.
1833
+ $FrameLengthCoefficient = 144;
1834
+ $SlotLength = 1;
1835
+
1836
+ } else { // layer 3
1837
+
1838
+ // for Layer 2 and Layer 3 slot is 8 bits long.
1839
+ $FrameLengthCoefficient = 72;
1840
+ $SlotLength = 1;
1841
+
1842
+ }
1843
+
1844
+ }
1845
+
1846
+ // FrameLengthInBytes = ((Coefficient * BitRate) / SampleRate) + Padding
1847
+ if ($samplerate > 0) {
1848
+ $NewFramelength = ($FrameLengthCoefficient * $bitrate) / $samplerate;
1849
+ $NewFramelength = floor($NewFramelength / $SlotLength) * $SlotLength; // round to next-lower multiple of SlotLength (1 byte for Layer 2/3, 4 bytes for Layer I)
1850
+ if ($padding) {
1851
+ $NewFramelength += $SlotLength;
1852
+ }
1853
+ $AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate] = (int) $NewFramelength;
1854
+ }
1855
+ }
1856
+ }
1857
+ return $AudioFrameLengthCache[$bitrate][$version][$layer][$padding][$samplerate];
1858
+ }
1859
+
1860
+ static function ClosestStandardMP3Bitrate($bit_rate) {
1861
+ static $standard_bit_rates = array (320000, 256000, 224000, 192000, 160000, 128000, 112000, 96000, 80000, 64000, 56000, 48000, 40000, 32000, 24000, 16000, 8000);
1862
+ static $bit_rate_table = array (0=>'-');
1863
+ $round_bit_rate = intval(round($bit_rate, -3));
1864
+ if (!isset($bit_rate_table[$round_bit_rate])) {
1865
+ if ($round_bit_rate > max($standard_bit_rates)) {
1866
+ $bit_rate_table[$round_bit_rate] = round($bit_rate, 2 - strlen($bit_rate));
1867
+ } else {
1868
+ $bit_rate_table[$round_bit_rate] = max($standard_bit_rates);
1869
+ foreach ($standard_bit_rates as $standard_bit_rate) {
1870
+ if ($round_bit_rate >= $standard_bit_rate + (($bit_rate_table[$round_bit_rate] - $standard_bit_rate) / 2)) {
1871
+ break;
1872
+ }
1873
+ $bit_rate_table[$round_bit_rate] = $standard_bit_rate;
1874
+ }
1875
+ }
1876
+ }
1877
+ return $bit_rate_table[$round_bit_rate];
1878
+ }
1879
+
1880
+ static function XingVBRidOffset($version, $channelmode) {
1881
+ static $XingVBRidOffsetCache = array();
1882
+ if (empty($XingVBRidOffset)) {
1883
+ $XingVBRidOffset = array (
1884
+ '1' => array ('mono' => 0x15, // 4 + 17 = 21
1885
+ 'stereo' => 0x24, // 4 + 32 = 36
1886
+ 'joint stereo' => 0x24,
1887
+ 'dual channel' => 0x24
1888
+ ),
1889
+
1890
+ '2' => array ('mono' => 0x0D, // 4 + 9 = 13
1891
+ 'stereo' => 0x15, // 4 + 17 = 21
1892
+ 'joint stereo' => 0x15,
1893
+ 'dual channel' => 0x15
1894
+ ),
1895
+
1896
+ '2.5' => array ('mono' => 0x15,
1897
+ 'stereo' => 0x15,
1898
+ 'joint stereo' => 0x15,
1899
+ 'dual channel' => 0x15
1900
+ )
1901
+ );
1902
+ }
1903
+ return $XingVBRidOffset[$version][$channelmode];
1904
+ }
1905
+
1906
+ static function LAMEvbrMethodLookup($VBRmethodID) {
1907
+ static $LAMEvbrMethodLookup = array(
1908
+ 0x00 => 'unknown',
1909
+ 0x01 => 'cbr',
1910
+ 0x02 => 'abr',
1911
+ 0x03 => 'vbr-old / vbr-rh',
1912
+ 0x04 => 'vbr-new / vbr-mtrh',
1913
+ 0x05 => 'vbr-mt',
1914
+ 0x06 => 'vbr (full vbr method 4)',
1915
+ 0x08 => 'cbr (constant bitrate 2 pass)',
1916
+ 0x09 => 'abr (2 pass)',
1917
+ 0x0F => 'reserved'
1918
+ );
1919
+ return (isset($LAMEvbrMethodLookup[$VBRmethodID]) ? $LAMEvbrMethodLookup[$VBRmethodID] : '');
1920
+ }
1921
+
1922
+ static function LAMEmiscStereoModeLookup($StereoModeID) {
1923
+ static $LAMEmiscStereoModeLookup = array(
1924
+ 0 => 'mono',
1925
+ 1 => 'stereo',
1926
+ 2 => 'dual mono',
1927
+ 3 => 'joint stereo',
1928
+ 4 => 'forced stereo',
1929
+ 5 => 'auto',
1930
+ 6 => 'intensity stereo',
1931
+ 7 => 'other'
1932
+ );
1933
+ return (isset($LAMEmiscStereoModeLookup[$StereoModeID]) ? $LAMEmiscStereoModeLookup[$StereoModeID] : '');
1934
+ }
1935
+
1936
+ static function LAMEmiscSourceSampleFrequencyLookup($SourceSampleFrequencyID) {
1937
+ static $LAMEmiscSourceSampleFrequencyLookup = array(
1938
+ 0 => '<= 32 kHz',
1939
+ 1 => '44.1 kHz',
1940
+ 2 => '48 kHz',
1941
+ 3 => '> 48kHz'
1942
+ );
1943
+ return (isset($LAMEmiscSourceSampleFrequencyLookup[$SourceSampleFrequencyID]) ? $LAMEmiscSourceSampleFrequencyLookup[$SourceSampleFrequencyID] : '');
1944
+ }
1945
+
1946
+ static function LAMEsurroundInfoLookup($SurroundInfoID) {
1947
+ static $LAMEsurroundInfoLookup = array(
1948
+ 0 => 'no surround info',
1949
+ 1 => 'DPL encoding',
1950
+ 2 => 'DPL2 encoding',
1951
+ 3 => 'Ambisonic encoding'
1952
+ );
1953
+ return (isset($LAMEsurroundInfoLookup[$SurroundInfoID]) ? $LAMEsurroundInfoLookup[$SurroundInfoID] : 'reserved');
1954
+ }
1955
+
1956
+ static function LAMEpresetUsedLookup($LAMEtag) {
1957
+
1958
+ if ($LAMEtag['preset_used_id'] == 0) {
1959
+ // no preset used (LAME >=3.93)
1960
+ // no preset recorded (LAME <3.93)
1961
+ return '';
1962
+ }
1963
+ $LAMEpresetUsedLookup = array();
1964
+
1965
+ ///// THIS PART CANNOT BE STATIC .
1966
+ for ($i = 8; $i <= 320; $i++) {
1967
+ switch ($LAMEtag['vbr_method']) {
1968
+ case 'cbr':
1969
+ $LAMEpresetUsedLookup[$i] = '--alt-preset '.$LAMEtag['vbr_method'].' '.$i;
1970
+ break;
1971
+ case 'abr':
1972
+ default: // other VBR modes shouldn't be here(?)
1973
+ $LAMEpresetUsedLookup[$i] = '--alt-preset '.$i;
1974
+ break;
1975
+ }
1976
+ }
1977
+
1978
+ // named old-style presets (studio, phone, voice, etc) are handled in GuessEncoderOptions()
1979
+
1980
+ // named alt-presets
1981
+ $LAMEpresetUsedLookup[1000] = '--r3mix';
1982
+ $LAMEpresetUsedLookup[1001] = '--alt-preset standard';
1983
+ $LAMEpresetUsedLookup[1002] = '--alt-preset extreme';
1984
+ $LAMEpresetUsedLookup[1003] = '--alt-preset insane';
1985
+ $LAMEpresetUsedLookup[1004] = '--alt-preset fast standard';
1986
+ $LAMEpresetUsedLookup[1005] = '--alt-preset fast extreme';
1987
+ $LAMEpresetUsedLookup[1006] = '--alt-preset medium';
1988
+ $LAMEpresetUsedLookup[1007] = '--alt-preset fast medium';
1989
+
1990
+ // LAME 3.94 additions/changes
1991
+ $LAMEpresetUsedLookup[1010] = '--preset portable'; // 3.94a15 Oct 21 2003
1992
+ $LAMEpresetUsedLookup[1015] = '--preset radio'; // 3.94a15 Oct 21 2003
1993
+
1994
+ $LAMEpresetUsedLookup[320] = '--preset insane'; // 3.94a15 Nov 12 2003
1995
+ $LAMEpresetUsedLookup[410] = '-V9';
1996
+ $LAMEpresetUsedLookup[420] = '-V8';
1997
+ $LAMEpresetUsedLookup[440] = '-V6';
1998
+ $LAMEpresetUsedLookup[430] = '--preset radio'; // 3.94a15 Nov 12 2003
1999
+ $LAMEpresetUsedLookup[450] = '--preset '.(($LAMEtag['raw']['vbr_method'] == 4) ? 'fast ' : '').'portable'; // 3.94a15 Nov 12 2003
2000
+ $LAMEpresetUsedLookup[460] = '--preset '.(($LAMEtag['raw']['vbr_method'] == 4) ? 'fast ' : '').'medium'; // 3.94a15 Nov 12 2003
2001
+ $LAMEpresetUsedLookup[470] = '--r3mix'; // 3.94b1 Dec 18 2003
2002
+ $LAMEpresetUsedLookup[480] = '--preset '.(($LAMEtag['raw']['vbr_method'] == 4) ? 'fast ' : '').'standard'; // 3.94a15 Nov 12 2003
2003
+ $LAMEpresetUsedLookup[490] = '-V1';
2004
+ $LAMEpresetUsedLookup[500] = '--preset '.(($LAMEtag['raw']['vbr_method'] == 4) ? 'fast ' : '').'extreme'; // 3.94a15 Nov 12 2003
2005
+
2006
+ return (isset($LAMEpresetUsedLookup[$LAMEtag['preset_used_id']]) ? $LAMEpresetUsedLookup[$LAMEtag['preset_used_id']] : 'new/unknown preset: '.$LAMEtag['preset_used_id'].' - report to info@getid3.org');
2007
+ }
2008
+
2009
+ }
2010
+
2011
+ ?>
includes/lib/getid3/module.tag.apetag.php ADDED
@@ -0,0 +1,372 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /////////////////////////////////////////////////////////////////
3
+ /// getID3() by James Heinrich <info@getid3.org> //
4
+ // available at http://getid3.sourceforge.net //
5
+ // or http://www.getid3.org //
6
+ /////////////////////////////////////////////////////////////////
7
+ // See readme.txt for more details //
8
+ /////////////////////////////////////////////////////////////////
9
+ // //
10
+ // module.tag.apetag.php //
11
+ // module for analyzing APE tags //
12
+ // dependencies: NONE //
13
+ // ///
14
+ /////////////////////////////////////////////////////////////////
15
+
16
+ class getid3_apetag extends getid3_handler
17
+ {
18
+ var $inline_attachments = true; // true: return full data for all attachments; false: return no data for all attachments; integer: return data for attachments <= than this; string: save as file to this directory
19
+ var $overrideendoffset = 0;
20
+
21
+ function Analyze() {
22
+ $info = &$this->getid3->info;
23
+
24
+ if (!getid3_lib::intValueSupported($info['filesize'])) {
25
+ $info['warning'][] = 'Unable to check for APEtags because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB';
26
+ return false;
27
+ }
28
+
29
+ $id3v1tagsize = 128;
30
+ $apetagheadersize = 32;
31
+ $lyrics3tagsize = 10;
32
+
33
+ if ($this->overrideendoffset == 0) {
34
+
35
+ fseek($this->getid3->fp, 0 - $id3v1tagsize - $apetagheadersize - $lyrics3tagsize, SEEK_END);
36
+ $APEfooterID3v1 = fread($this->getid3->fp, $id3v1tagsize + $apetagheadersize + $lyrics3tagsize);
37
+
38
+ //if (preg_match('/APETAGEX.{24}TAG.{125}$/i', $APEfooterID3v1)) {
39
+ if (substr($APEfooterID3v1, strlen($APEfooterID3v1) - $id3v1tagsize - $apetagheadersize, 8) == 'APETAGEX') {
40
+
41
+ // APE tag found before ID3v1
42
+ $info['ape']['tag_offset_end'] = $info['filesize'] - $id3v1tagsize;
43
+
44
+ //} elseif (preg_match('/APETAGEX.{24}$/i', $APEfooterID3v1)) {
45
+ } elseif (substr($APEfooterID3v1, strlen($APEfooterID3v1) - $apetagheadersize, 8) == 'APETAGEX') {
46
+
47
+ // APE tag found, no ID3v1
48
+ $info['ape']['tag_offset_end'] = $info['filesize'];
49
+
50
+ }
51
+
52
+ } else {
53
+
54
+ fseek($this->getid3->fp, $this->overrideendoffset - $apetagheadersize, SEEK_SET);
55
+ if (fread($this->getid3->fp, 8) == 'APETAGEX') {
56
+ $info['ape']['tag_offset_end'] = $this->overrideendoffset;
57
+ }
58
+
59
+ }
60
+ if (!isset($info['ape']['tag_offset_end'])) {
61
+
62
+ // APE tag not found
63
+ unset($info['ape']);
64
+ return false;
65
+
66
+ }
67
+
68
+ // shortcut
69
+ $thisfile_ape = &$info['ape'];
70
+
71
+ fseek($this->getid3->fp, $thisfile_ape['tag_offset_end'] - $apetagheadersize, SEEK_SET);
72
+ $APEfooterData = fread($this->getid3->fp, 32);
73
+ if (!($thisfile_ape['footer'] = $this->parseAPEheaderFooter($APEfooterData))) {
74
+ $info['error'][] = 'Error parsing APE footer at offset '.$thisfile_ape['tag_offset_end'];
75
+ return false;
76
+ }
77
+
78
+ if (isset($thisfile_ape['footer']['flags']['header']) && $thisfile_ape['footer']['flags']['header']) {
79
+ fseek($this->getid3->fp, $thisfile_ape['tag_offset_end'] - $thisfile_ape['footer']['raw']['tagsize'] - $apetagheadersize, SEEK_SET);
80
+ $thisfile_ape['tag_offset_start'] = ftell($this->getid3->fp);
81
+ $APEtagData = fread($this->getid3->fp, $thisfile_ape['footer']['raw']['tagsize'] + $apetagheadersize);
82
+ } else {
83
+ $thisfile_ape['tag_offset_start'] = $thisfile_ape['tag_offset_end'] - $thisfile_ape['footer']['raw']['tagsize'];
84
+ fseek($this->getid3->fp, $thisfile_ape['tag_offset_start'], SEEK_SET);
85
+ $APEtagData = fread($this->getid3->fp, $thisfile_ape['footer']['raw']['tagsize']);
86
+ }
87
+ $info['avdataend'] = $thisfile_ape['tag_offset_start'];
88
+
89
+ if (isset($info['id3v1']['tag_offset_start']) && ($info['id3v1']['tag_offset_start'] < $thisfile_ape['tag_offset_end'])) {
90
+ $info['warning'][] = 'ID3v1 tag information ignored since it appears to be a false synch in APEtag data';
91
+ unset($info['id3v1']);
92
+ foreach ($info['warning'] as $key => $value) {
93
+ if ($value == 'Some ID3v1 fields do not use NULL characters for padding') {
94
+ unset($info['warning'][$key]);
95
+ sort($info['warning']);
96
+ break;
97
+ }
98
+ }
99
+ }
100
+
101
+ $offset = 0;
102
+ if (isset($thisfile_ape['footer']['flags']['header']) && $thisfile_ape['footer']['flags']['header']) {
103
+ if ($thisfile_ape['header'] = $this->parseAPEheaderFooter(substr($APEtagData, 0, $apetagheadersize))) {
104
+ $offset += $apetagheadersize;
105
+ } else {
106
+ $info['error'][] = 'Error parsing APE header at offset '.$thisfile_ape['tag_offset_start'];
107
+ return false;
108
+ }
109
+ }
110
+
111
+ // shortcut
112
+ $info['replay_gain'] = array();
113
+ $thisfile_replaygain = &$info['replay_gain'];
114
+
115
+ for ($i = 0; $i < $thisfile_ape['footer']['raw']['tag_items']; $i++) {
116
+ $value_size = getid3_lib::LittleEndian2Int(substr($APEtagData, $offset, 4));
117
+ $offset += 4;
118
+ $item_flags = getid3_lib::LittleEndian2Int(substr($APEtagData, $offset, 4));
119
+ $offset += 4;
120
+ if (strstr(substr($APEtagData, $offset), "\x00") === false) {
121
+ $info['error'][] = 'Cannot find null-byte (0x00) seperator between ItemKey #'.$i.' and value. ItemKey starts '.$offset.' bytes into the APE tag, at file offset '.($thisfile_ape['tag_offset_start'] + $offset);
122
+ return false;
123
+ }
124
+ $ItemKeyLength = strpos($APEtagData, "\x00", $offset) - $offset;
125
+ $item_key = strtolower(substr($APEtagData, $offset, $ItemKeyLength));
126
+
127
+ // shortcut
128
+ $thisfile_ape['items'][$item_key] = array();
129
+ $thisfile_ape_items_current = &$thisfile_ape['items'][$item_key];
130
+
131
+ $thisfile_ape_items_current['offset'] = $thisfile_ape['tag_offset_start'] + $offset;
132
+
133
+ $offset += ($ItemKeyLength + 1); // skip 0x00 terminator
134
+ $thisfile_ape_items_current['data'] = substr($APEtagData, $offset, $value_size);
135
+ $offset += $value_size;
136
+
137
+ $thisfile_ape_items_current['flags'] = $this->parseAPEtagFlags($item_flags);
138
+ switch ($thisfile_ape_items_current['flags']['item_contents_raw']) {
139
+ case 0: // UTF-8
140
+ case 3: // Locator (URL, filename, etc), UTF-8 encoded
141
+ $thisfile_ape_items_current['data'] = explode("\x00", trim($thisfile_ape_items_current['data']));
142
+ break;
143
+
144
+ default: // binary data
145
+ break;
146
+ }
147
+
148
+ switch (strtolower($item_key)) {
149
+ case 'replaygain_track_gain':
150
+ $thisfile_replaygain['track']['adjustment'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
151
+ $thisfile_replaygain['track']['originator'] = 'unspecified';
152
+ break;
153
+
154
+ case 'replaygain_track_peak':
155
+ $thisfile_replaygain['track']['peak'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
156
+ $thisfile_replaygain['track']['originator'] = 'unspecified';
157
+ if ($thisfile_replaygain['track']['peak'] <= 0) {
158
+ $info['warning'][] = 'ReplayGain Track peak from APEtag appears invalid: '.$thisfile_replaygain['track']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")';
159
+ }
160
+ break;
161
+
162
+ case 'replaygain_album_gain':
163
+ $thisfile_replaygain['album']['adjustment'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
164
+ $thisfile_replaygain['album']['originator'] = 'unspecified';
165
+ break;
166
+
167
+ case 'replaygain_album_peak':
168
+ $thisfile_replaygain['album']['peak'] = (float) str_replace(',', '.', $thisfile_ape_items_current['data'][0]); // float casting will see "0,95" as zero!
169
+ $thisfile_replaygain['album']['originator'] = 'unspecified';
170
+ if ($thisfile_replaygain['album']['peak'] <= 0) {
171
+ $info['warning'][] = 'ReplayGain Album peak from APEtag appears invalid: '.$thisfile_replaygain['album']['peak'].' (original value = "'.$thisfile_ape_items_current['data'][0].'")';
172
+ }
173
+ break;
174
+
175
+ case 'mp3gain_undo':
176
+ list($mp3gain_undo_left, $mp3gain_undo_right, $mp3gain_undo_wrap) = explode(',', $thisfile_ape_items_current['data'][0]);
177
+ $thisfile_replaygain['mp3gain']['undo_left'] = intval($mp3gain_undo_left);
178
+ $thisfile_replaygain['mp3gain']['undo_right'] = intval($mp3gain_undo_right);
179
+ $thisfile_replaygain['mp3gain']['undo_wrap'] = (($mp3gain_undo_wrap == 'Y') ? true : false);
180
+ break;
181
+
182
+ case 'mp3gain_minmax':
183
+ list($mp3gain_globalgain_min, $mp3gain_globalgain_max) = explode(',', $thisfile_ape_items_current['data'][0]);
184
+ $thisfile_replaygain['mp3gain']['globalgain_track_min'] = intval($mp3gain_globalgain_min);
185
+ $thisfile_replaygain['mp3gain']['globalgain_track_max'] = intval($mp3gain_globalgain_max);
186
+ break;
187
+
188
+ case 'mp3gain_album_minmax':
189
+ list($mp3gain_globalgain_album_min, $mp3gain_globalgain_album_max) = explode(',', $thisfile_ape_items_current['data'][0]);
190
+ $thisfile_replaygain['mp3gain']['globalgain_album_min'] = intval($mp3gain_globalgain_album_min);
191
+ $thisfile_replaygain['mp3gain']['globalgain_album_max'] = intval($mp3gain_globalgain_album_max);
192
+ break;
193
+
194
+ case 'tracknumber':
195
+ if (is_array($thisfile_ape_items_current['data'])) {
196
+ foreach ($thisfile_ape_items_current['data'] as $comment) {
197
+ $thisfile_ape['comments']['track'][] = $comment;
198
+ }
199
+ }
200
+ break;
201
+
202
+ case 'cover art (artist)':
203
+ case 'cover art (back)':
204
+ case 'cover art (band logo)':
205
+ case 'cover art (band)':
206
+ case 'cover art (colored fish)':
207
+ case 'cover art (composer)':
208
+ case 'cover art (conductor)':
209
+ case 'cover art (front)':
210
+ case 'cover art (icon)':
211
+ case 'cover art (illustration)':
212
+ case 'cover art (lead)':
213
+ case 'cover art (leaflet)':
214
+ case 'cover art (lyricist)':
215
+ case 'cover art (media)':
216
+ case 'cover art (movie scene)':
217
+ case 'cover art (other icon)':
218
+ case 'cover art (other)':
219
+ case 'cover art (performance)':
220
+ case 'cover art (publisher logo)':
221
+ case 'cover art (recording)':
222
+ case 'cover art (studio)':
223
+ // list of possible cover arts from http://taglib-sharp.sourcearchive.com/documentation/2.0.3.0-2/Ape_2Tag_8cs-source.html
224
+ list($thisfile_ape_items_current['filename'], $thisfile_ape_items_current['data']) = explode("\x00", $thisfile_ape_items_current['data'], 2);
225
+ $thisfile_ape_items_current['data_offset'] = $thisfile_ape_items_current['offset'] + strlen($thisfile_ape_items_current['filename']."\x00");
226
+ $thisfile_ape_items_current['data_length'] = strlen($thisfile_ape_items_current['data']);
227
+
228
+ $thisfile_ape_items_current['image_mime'] = '';
229
+ $imageinfo = array();
230
+ $imagechunkcheck = getid3_lib::GetDataImageSize($thisfile_ape_items_current['data'], $imageinfo);
231
+ $thisfile_ape_items_current['image_mime'] = image_type_to_mime_type($imagechunkcheck[2]);
232
+
233
+ do {
234
+ if ($this->inline_attachments === false) {
235
+ // skip entirely
236
+ unset($thisfile_ape_items_current['data']);
237
+ break;
238
+ }
239
+ if ($this->inline_attachments === true) {
240
+ // great
241
+ } elseif (is_int($this->inline_attachments)) {
242
+ if ($this->inline_attachments < $thisfile_ape_items_current['data_length']) {
243
+ // too big, skip
244
+ $info['warning'][] = 'attachment at '.$thisfile_ape_items_current['offset'].' is too large to process inline ('.number_format($thisfile_ape_items_current['data_length']).' bytes)';
245
+ unset($thisfile_ape_items_current['data']);
246
+ break;
247
+ }
248
+ } elseif (is_string($this->inline_attachments)) {
249
+ $this->inline_attachments = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->inline_attachments), DIRECTORY_SEPARATOR);
250
+ if (!is_dir($this->inline_attachments) || !is_writable($this->inline_attachments)) {
251
+ // cannot write, skip
252
+ $info['warning'][] = 'attachment at '.$thisfile_ape_items_current['offset'].' cannot be saved to "'.$this->inline_attachments.'" (not writable)';
253
+ unset($thisfile_ape_items_current['data']);
254
+ break;
255
+ }
256
+ }
257
+ // if we get this far, must be OK
258
+ if (is_string($this->inline_attachments)) {
259
+ $destination_filename = $this->inline_attachments.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$thisfile_ape_items_current['data_offset'];
260
+ if (!file_exists($destination_filename) || is_writable($destination_filename)) {
261
+ file_put_contents($destination_filename, $thisfile_ape_items_current['data']);
262
+ } else {
263
+ $info['warning'][] = 'attachment at '.$thisfile_ape_items_current['offset'].' cannot be saved to "'.$destination_filename.'" (not writable)';
264
+ }
265
+ $thisfile_ape_items_current['data_filename'] = $destination_filename;
266
+ unset($thisfile_ape_items_current['data']);
267
+ } else {
268
+ if (!isset($info['ape']['comments']['picture'])) {
269
+ $info['ape']['comments']['picture'] = array();
270
+ }
271
+ $info['ape']['comments']['picture'][] = array('data'=>$thisfile_ape_items_current['data'], 'image_mime'=>$thisfile_ape_items_current['image_mime']);
272
+ }
273
+ } while (false);
274
+ break;
275
+
276
+ default:
277
+ if (is_array($thisfile_ape_items_current['data'])) {
278
+ foreach ($thisfile_ape_items_current['data'] as $comment) {
279
+ $thisfile_ape['comments'][strtolower($item_key)][] = $comment;
280
+ }
281
+ }
282
+ break;
283
+ }
284
+
285
+ }
286
+ if (empty($thisfile_replaygain)) {
287
+ unset($info['replay_gain']);
288
+ }
289
+ return true;
290
+ }
291
+
292
+ function parseAPEheaderFooter($APEheaderFooterData) {
293
+ // http://www.uni-jena.de/~pfk/mpp/sv8/apeheader.html
294
+
295
+ // shortcut
296
+ $headerfooterinfo['raw'] = array();
297
+ $headerfooterinfo_raw = &$headerfooterinfo['raw'];
298
+
299
+ $headerfooterinfo_raw['footer_tag'] = substr($APEheaderFooterData, 0, 8);
300
+ if ($headerfooterinfo_raw['footer_tag'] != 'APETAGEX') {
301
+ return false;
302
+ }
303
+ $headerfooterinfo_raw['version'] = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData, 8, 4));
304
+ $headerfooterinfo_raw['tagsize'] = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData, 12, 4));
305
+ $headerfooterinfo_raw['tag_items'] = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData, 16, 4));
306
+ $headerfooterinfo_raw['global_flags'] = getid3_lib::LittleEndian2Int(substr($APEheaderFooterData, 20, 4));
307
+ $headerfooterinfo_raw['reserved'] = substr($APEheaderFooterData, 24, 8);
308
+
309
+ $headerfooterinfo['tag_version'] = $headerfooterinfo_raw['version'] / 1000;
310
+ if ($headerfooterinfo['tag_version'] >= 2) {
311
+ $headerfooterinfo['flags'] = $this->parseAPEtagFlags($headerfooterinfo_raw['global_flags']);
312
+ }
313
+ return $headerfooterinfo;
314
+ }
315
+
316
+ function parseAPEtagFlags($rawflagint) {
317
+ // "Note: APE Tags 1.0 do not use any of the APE Tag flags.
318
+ // All are set to zero on creation and ignored on reading."
319
+ // http://www.uni-jena.de/~pfk/mpp/sv8/apetagflags.html
320
+ $flags['header'] = (bool) ($rawflagint & 0x80000000);
321
+ $flags['footer'] = (bool) ($rawflagint & 0x40000000);
322
+ $flags['this_is_header'] = (bool) ($rawflagint & 0x20000000);
323
+ $flags['item_contents_raw'] = ($rawflagint & 0x00000006) >> 1;
324
+ $flags['read_only'] = (bool) ($rawflagint & 0x00000001);
325
+
326
+ $flags['item_contents'] = $this->APEcontentTypeFlagLookup($flags['item_contents_raw']);
327
+
328
+ return $flags;
329
+ }
330
+
331
+ function APEcontentTypeFlagLookup($contenttypeid) {
332
+ static $APEcontentTypeFlagLookup = array(
333
+ 0 => 'utf-8',
334
+ 1 => 'binary',
335
+ 2 => 'external',
336
+ 3 => 'reserved'
337
+ );
338
+ return (isset($APEcontentTypeFlagLookup[$contenttypeid]) ? $APEcontentTypeFlagLookup[$contenttypeid] : 'invalid');
339
+ }
340
+
341
+ function APEtagItemIsUTF8Lookup($itemkey) {
342
+ static $APEtagItemIsUTF8Lookup = array(
343
+ 'title',
344
+ 'subtitle',
345
+ 'artist',
346
+ 'album',
347
+ 'debut album',
348
+ 'publisher',
349
+ 'conductor',
350
+ 'track',
351
+ 'composer',
352
+ 'comment',
353
+ 'copyright',
354
+ 'publicationright',
355
+ 'file',
356
+ 'year',
357
+ 'record date',
358
+ 'record location',
359
+ 'genre',
360
+ 'media',
361
+ 'related',
362
+ 'isrc',
363
+ 'abstract',
364
+ 'language',
365
+ 'bibliography'
366
+ );
367
+ return in_array(strtolower($itemkey), $APEtagItemIsUTF8Lookup);
368
+ }
369
+
370
+ }
371
+
372
+ ?>
includes/lib/getid3/module.tag.id3v1.php ADDED
@@ -0,0 +1,362 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /////////////////////////////////////////////////////////////////
3
+ /// getID3() by James Heinrich <info@getid3.org> //
4
+ // available at http://getid3.sourceforge.net //
5
+ // or http://www.getid3.org //
6
+ /////////////////////////////////////////////////////////////////
7
+ // See readme.txt for more details //
8
+ /////////////////////////////////////////////////////////////////
9
+ // //
10
+ // module.tag.id3v1.php //
11
+ // module for analyzing ID3v1 tags //
12
+ // dependencies: NONE //
13
+ // ///
14
+ /////////////////////////////////////////////////////////////////
15
+
16
+
17
+ class getid3_id3v1 extends getid3_handler
18
+ {
19
+
20
+ function Analyze() {
21
+ $info = &$this->getid3->info;
22
+
23
+ if (!getid3_lib::intValueSupported($info['filesize'])) {
24
+ $info['warning'][] = 'Unable to check for ID3v1 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB';
25
+ return false;
26
+ }
27
+
28
+ fseek($this->getid3->fp, -256, SEEK_END);
29
+ $preid3v1 = fread($this->getid3->fp, 128);
30
+ $id3v1tag = fread($this->getid3->fp, 128);
31
+
32
+ if (substr($id3v1tag, 0, 3) == 'TAG') {
33
+
34
+ $info['avdataend'] = $info['filesize'] - 128;
35
+
36
+ $ParsedID3v1['title'] = $this->cutfield(substr($id3v1tag, 3, 30));
37
+ $ParsedID3v1['artist'] = $this->cutfield(substr($id3v1tag, 33, 30));
38
+ $ParsedID3v1['album'] = $this->cutfield(substr($id3v1tag, 63, 30));
39
+ $ParsedID3v1['year'] = $this->cutfield(substr($id3v1tag, 93, 4));
40
+ $ParsedID3v1['comment'] = substr($id3v1tag, 97, 30); // can't remove nulls yet, track detection depends on them
41
+ $ParsedID3v1['genreid'] = ord(substr($id3v1tag, 127, 1));
42
+
43
+ // If second-last byte of comment field is null and last byte of comment field is non-null
44
+ // then this is ID3v1.1 and the comment field is 28 bytes long and the 30th byte is the track number
45
+ if (($id3v1tag{125} === "\x00") && ($id3v1tag{126} !== "\x00")) {
46
+ $ParsedID3v1['track'] = ord(substr($ParsedID3v1['comment'], 29, 1));
47
+ $ParsedID3v1['comment'] = substr($ParsedID3v1['comment'], 0, 28);
48
+ }
49
+ $ParsedID3v1['comment'] = $this->cutfield($ParsedID3v1['comment']);
50
+
51
+ $ParsedID3v1['genre'] = $this->LookupGenreName($ParsedID3v1['genreid']);
52
+ if (!empty($ParsedID3v1['genre'])) {
53
+ unset($ParsedID3v1['genreid']);
54
+ }
55
+ if (isset($ParsedID3v1['genre']) && (empty($ParsedID3v1['genre']) || ($ParsedID3v1['genre'] == 'Unknown'))) {
56
+ unset($ParsedID3v1['genre']);
57
+ }
58
+
59
+ foreach ($ParsedID3v1 as $key => $value) {
60
+ $ParsedID3v1['comments'][$key][0] = $value;
61
+ }
62
+
63
+ // ID3v1 data is supposed to be padded with NULL characters, but some taggers pad with spaces
64
+ $GoodFormatID3v1tag = $this->GenerateID3v1Tag(
65
+ $ParsedID3v1['title'],
66
+ $ParsedID3v1['artist'],
67
+ $ParsedID3v1['album'],
68
+ $ParsedID3v1['year'],
69
+ (isset($ParsedID3v1['genre']) ? $this->LookupGenreID($ParsedID3v1['genre']) : false),
70
+ $ParsedID3v1['comment'],
71
+ (!empty($ParsedID3v1['track']) ? $ParsedID3v1['track'] : ''));
72
+ $ParsedID3v1['padding_valid'] = true;
73
+ if ($id3v1tag !== $GoodFormatID3v1tag) {
74
+ $ParsedID3v1['padding_valid'] = false;
75
+ $info['warning'][] = 'Some ID3v1 fields do not use NULL characters for padding';
76
+ }
77
+
78
+ $ParsedID3v1['tag_offset_end'] = $info['filesize'];
79
+ $ParsedID3v1['tag_offset_start'] = $ParsedID3v1['tag_offset_end'] - 128;
80
+
81
+ $info['id3v1'] = $ParsedID3v1;
82
+ }
83
+
84
+ if (substr($preid3v1, 0, 3) == 'TAG') {
85
+ // The way iTunes handles tags is, well, brain-damaged.
86
+ // It completely ignores v1 if ID3v2 is present.
87
+ // This goes as far as adding a new v1 tag *even if there already is one*
88
+
89
+ // A suspected double-ID3v1 tag has been detected, but it could be that
90
+ // the "TAG" identifier is a legitimate part of an APE or Lyrics3 tag
91
+ if (substr($preid3v1, 96, 8) == 'APETAGEX') {
92
+ // an APE tag footer was found before the last ID3v1, assume false "TAG" synch
93
+ } elseif (substr($preid3v1, 119, 6) == 'LYRICS') {
94
+ // a Lyrics3 tag footer was found before the last ID3v1, assume false "TAG" synch
95
+ } else {
96
+ // APE and Lyrics3 footers not found - assume double ID3v1
97
+ $info['warning'][] = 'Duplicate ID3v1 tag detected - this has been known to happen with iTunes';
98
+ $info['avdataend'] -= 128;
99
+ }
100
+ }
101
+
102
+ return true;
103
+ }
104
+
105
+ static function cutfield($str) {
106
+ return trim(substr($str, 0, strcspn($str, "\x00")));
107
+ }
108
+
109
+ static function ArrayOfGenres($allowSCMPXextended=false) {
110
+ static $GenreLookup = array(
111
+ 0 => 'Blues',
112
+ 1 => 'Classic Rock',
113
+ 2 => 'Country',
114
+ 3 => 'Dance',
115
+ 4 => 'Disco',
116
+ 5 => 'Funk',
117
+ 6 => 'Grunge',
118
+ 7 => 'Hip-Hop',
119
+ 8 => 'Jazz',
120
+ 9 => 'Metal',
121
+ 10 => 'New Age',
122
+ 11 => 'Oldies',
123
+ 12 => 'Other',
124
+ 13 => 'Pop',
125
+ 14 => 'R&B',
126
+ 15 => 'Rap',
127
+ 16 => 'Reggae',
128
+ 17 => 'Rock',
129
+ 18 => 'Techno',
130
+ 19 => 'Industrial',
131
+ 20 => 'Alternative',
132
+ 21 => 'Ska',
133
+ 22 => 'Death Metal',
134
+ 23 => 'Pranks',
135
+ 24 => 'Soundtrack',
136
+ 25 => 'Euro-Techno',
137
+ 26 => 'Ambient',
138
+ 27 => 'Trip-Hop',
139
+ 28 => 'Vocal',
140
+ 29 => 'Jazz+Funk',
141
+ 30 => 'Fusion',
142
+ 31 => 'Trance',
143
+ 32 => 'Classical',
144
+ 33 => 'Instrumental',
145
+ 34 => 'Acid',
146
+ 35 => 'House',
147
+ 36 => 'Game',
148
+ 37 => 'Sound Clip',
149
+ 38 => 'Gospel',
150
+ 39 => 'Noise',
151
+ 40 => 'Alt. Rock',
152
+ 41 => 'Bass',
153
+ 42 => 'Soul',
154
+ 43 => 'Punk',
155
+ 44 => 'Space',
156
+ 45 => 'Meditative',
157
+ 46 => 'Instrumental Pop',
158
+ 47 => 'Instrumental Rock',
159
+ 48 => 'Ethnic',
160
+ 49 => 'Gothic',
161
+ 50 => 'Darkwave',
162
+ 51 => 'Techno-Industrial',
163
+ 52 => 'Electronic',
164
+ 53 => 'Pop-Folk',
165
+ 54 => 'Eurodance',
166
+ 55 => 'Dream',
167
+ 56 => 'Southern Rock',
168
+ 57 => 'Comedy',
169
+ 58 => 'Cult',
170
+ 59 => 'Gangsta Rap',
171
+ 60 => 'Top 40',
172
+ 61 => 'Christian Rap',
173
+ 62 => 'Pop/Funk',
174
+ 63 => 'Jungle',
175
+ 64 => 'Native American',
176
+ 65 => 'Cabaret',
177
+ 66 => 'New Wave',
178
+ 67 => 'Psychedelic',
179
+ 68 => 'Rave',
180
+ 69 => 'Showtunes',
181
+ 70 => 'Trailer',
182
+ 71 => 'Lo-Fi',
183
+ 72 => 'Tribal',
184
+ 73 => 'Acid Punk',
185
+ 74 => 'Acid Jazz',
186
+ 75 => 'Polka',
187
+ 76 => 'Retro',
188
+ 77 => 'Musical',
189
+ 78 => 'Rock & Roll',
190
+ 79 => 'Hard Rock',
191
+ 80 => 'Folk',
192
+ 81 => 'Folk/Rock',
193
+ 82 => 'National Folk',
194
+ 83 => 'Swing',
195
+ 84 => 'Fast-Fusion',
196
+ 85 => 'Bebob',
197
+ 86 => 'Latin',
198
+ 87 => 'Revival',
199
+ 88 => 'Celtic',
200
+ 89 => 'Bluegrass',
201
+ 90 => 'Avantgarde',
202
+ 91 => 'Gothic Rock',
203
+ 92 => 'Progressive Rock',
204
+ 93 => 'Psychedelic Rock',
205
+ 94 => 'Symphonic Rock',
206
+ 95 => 'Slow Rock',
207
+ 96 => 'Big Band',
208
+ 97 => 'Chorus',
209
+ 98 => 'Easy Listening',
210
+ 99 => 'Acoustic',
211
+ 100 => 'Humour',
212
+ 101 => 'Speech',
213
+ 102 => 'Chanson',
214
+ 103 => 'Opera',
215
+ 104 => 'Chamber Music',
216
+ 105 => 'Sonata',
217
+ 106 => 'Symphony',
218
+ 107 => 'Booty Bass',
219
+ 108 => 'Primus',
220
+ 109 => 'Porn Groove',
221
+ 110 => 'Satire',
222
+ 111 => 'Slow Jam',
223
+ 112 => 'Club',
224
+ 113 => 'Tango',
225
+ 114 => 'Samba',
226
+ 115 => 'Folklore',
227
+ 116 => 'Ballad',
228
+ 117 => 'Power Ballad',
229
+ 118 => 'Rhythmic Soul',
230
+ 119 => 'Freestyle',
231
+ 120 => 'Duet',
232
+ 121 => 'Punk Rock',
233
+ 122 => 'Drum Solo',
234
+ 123 => 'A Cappella',
235
+ 124 => 'Euro-House',
236
+ 125 => 'Dance Hall',
237
+ 126 => 'Goa',
238
+ 127 => 'Drum & Bass',
239
+ 128 => 'Club-House',
240
+ 129 => 'Hardcore',
241
+ 130 => 'Terror',
242
+ 131 => 'Indie',
243
+ 132 => 'BritPop',
244
+ 133 => 'Negerpunk',
245
+ 134 => 'Polsk Punk',
246
+ 135 => 'Beat',
247
+ 136 => 'Christian Gangsta Rap',
248
+ 137 => 'Heavy Metal',
249
+ 138 => 'Black Metal',
250
+ 139 => 'Crossover',
251
+ 140 => 'Contemporary Christian',
252
+ 141 => 'Christian Rock',
253
+ 142 => 'Merengue',
254
+ 143 => 'Salsa',
255
+ 144 => 'Thrash Metal',
256
+ 145 => 'Anime',
257
+ 146 => 'JPop',
258
+ 147 => 'Synthpop',
259
+
260
+ 255 => 'Unknown',
261
+
262
+ 'CR' => 'Cover',
263
+ 'RX' => 'Remix'
264
+ );
265
+
266
+ static $GenreLookupSCMPX = array();
267
+ if ($allowSCMPXextended && empty($GenreLookupSCMPX)) {
268
+ $GenreLookupSCMPX = $GenreLookup;
269
+ // http://www.geocities.co.jp/SiliconValley-Oakland/3664/alittle.html#GenreExtended
270
+ // Extended ID3v1 genres invented by SCMPX
271
+ // Note that 255 "Japanese Anime" conflicts with standard "Unknown"
272
+ $GenreLookupSCMPX[240] = 'Sacred';
273
+ $GenreLookupSCMPX[241] = 'Northern Europe';
274
+ $GenreLookupSCMPX[242] = 'Irish & Scottish';
275
+ $GenreLookupSCMPX[243] = 'Scotland';
276
+ $GenreLookupSCMPX[244] = 'Ethnic Europe';
277
+ $GenreLookupSCMPX[245] = 'Enka';
278
+ $GenreLookupSCMPX[246] = 'Children\'s Song';
279
+ $GenreLookupSCMPX[247] = 'Japanese Sky';
280
+ $GenreLookupSCMPX[248] = 'Japanese Heavy Rock';
281
+ $GenreLookupSCMPX[249] = 'Japanese Doom Rock';
282
+ $GenreLookupSCMPX[250] = 'Japanese J-POP';
283
+ $GenreLookupSCMPX[251] = 'Japanese Seiyu';
284
+ $GenreLookupSCMPX[252] = 'Japanese Ambient Techno';
285
+ $GenreLookupSCMPX[253] = 'Japanese Moemoe';
286
+ $GenreLookupSCMPX[254] = 'Japanese Tokusatsu';
287
+ //$GenreLookupSCMPX[255] = 'Japanese Anime';
288
+ }
289
+
290
+ return ($allowSCMPXextended ? $GenreLookupSCMPX : $GenreLookup);
291
+ }
292
+
293
+ static function LookupGenreName($genreid, $allowSCMPXextended=true) {
294
+ switch ($genreid) {
295
+ case 'RX':
296
+ case 'CR':
297
+ break;
298
+ default:
299
+ if (!is_numeric($genreid)) {
300
+ return false;
301
+ }
302
+ $genreid = intval($genreid); // to handle 3 or '3' or '03'
303
+ break;
304
+ }
305
+ $GenreLookup = getid3_id3v1::ArrayOfGenres($allowSCMPXextended);
306
+ return (isset($GenreLookup[$genreid]) ? $GenreLookup[$genreid] : false);
307
+ }
308
+
309
+ static function LookupGenreID($genre, $allowSCMPXextended=false) {
310
+ $GenreLookup = getid3_id3v1::ArrayOfGenres($allowSCMPXextended);
311
+ $LowerCaseNoSpaceSearchTerm = strtolower(str_replace(' ', '', $genre));
312
+ foreach ($GenreLookup as $key => $value) {
313
+ if (strtolower(str_replace(' ', '', $value)) == $LowerCaseNoSpaceSearchTerm) {
314
+ return $key;
315
+ }
316
+ }
317
+ return false;
318
+ }
319
+
320
+ static function StandardiseID3v1GenreName($OriginalGenre) {
321
+ if (($GenreID = getid3_id3v1::LookupGenreID($OriginalGenre)) !== false) {
322
+ return getid3_id3v1::LookupGenreName($GenreID);
323
+ }
324
+ return $OriginalGenre;
325
+ }
326
+
327
+ static function GenerateID3v1Tag($title, $artist, $album, $year, $genreid, $comment, $track='') {
328
+ $ID3v1Tag = 'TAG';
329
+ $ID3v1Tag .= str_pad(trim(substr($title, 0, 30)), 30, "\x00", STR_PAD_RIGHT);
330
+ $ID3v1Tag .= str_pad(trim(substr($artist, 0, 30)), 30, "\x00", STR_PAD_RIGHT);
331
+ $ID3v1Tag .= str_pad(trim(substr($album, 0, 30)), 30, "\x00", STR_PAD_RIGHT);
332
+ $ID3v1Tag .= str_pad(trim(substr($year, 0, 4)), 4, "\x00", STR_PAD_LEFT);
333
+ if (!empty($track) && ($track > 0) && ($track <= 255)) {
334
+ $ID3v1Tag .= str_pad(trim(substr($comment, 0, 28)), 28, "\x00", STR_PAD_RIGHT);
335
+ $ID3v1Tag .= "\x00";
336
+ if (gettype($track) == 'string') {
337
+ $track = (int) $track;
338
+ }
339
+ $ID3v1Tag .= chr($track);
340
+ } else {
341
+ $ID3v1Tag .= str_pad(trim(substr($comment, 0, 30)), 30, "\x00", STR_PAD_RIGHT);
342
+ }
343
+ if (($genreid < 0) || ($genreid > 147)) {
344
+ $genreid = 255; // 'unknown' genre
345
+ }
346
+ switch (gettype($genreid)) {
347
+ case 'string':
348
+ case 'integer':
349
+ $ID3v1Tag .= chr(intval($genreid));
350
+ break;
351
+ default:
352
+ $ID3v1Tag .= chr(255); // 'unknown' genre
353
+ break;
354
+ }
355
+
356
+ return $ID3v1Tag;
357
+ }
358
+
359
+ }
360
+
361
+
362
+ ?>
includes/lib/getid3/module.tag.id3v2.php ADDED
@@ -0,0 +1,3327 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /////////////////////////////////////////////////////////////////
3
+ /// getID3() by James Heinrich <info@getid3.org> //
4
+ // available at http://getid3.sourceforge.net //
5
+ // or http://www.getid3.org //
6
+ /////////////////////////////////////////////////////////////////
7
+ // See readme.txt for more details //
8
+ /////////////////////////////////////////////////////////////////
9
+ /// //
10
+ // module.tag.id3v2.php //
11
+ // module for analyzing ID3v2 tags //
12
+ // dependencies: module.tag.id3v1.php //
13
+ // ///
14
+ /////////////////////////////////////////////////////////////////
15
+
16
+ getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.id3v1.php', __FILE__, true);
17
+
18
+ class getid3_id3v2 extends getid3_handler
19
+ {
20
+ var $inline_attachments = true; // true: return full data for all attachments; false: return no data for all attachments; integer: return data for attachments <= than this; string: save as file to this directory
21
+ var $StartingOffset = 0;
22
+
23
+ function Analyze() {
24
+ $info = &$this->getid3->info;
25
+
26
+ // Overall tag structure:
27
+ // +-----------------------------+
28
+ // | Header (10 bytes) |
29
+ // +-----------------------------+
30
+ // | Extended Header |
31
+ // | (variable length, OPTIONAL) |
32
+ // +-----------------------------+
33
+ // | Frames (variable length) |
34
+ // +-----------------------------+
35
+ // | Padding |
36
+ // | (variable length, OPTIONAL) |
37
+ // +-----------------------------+
38
+ // | Footer (10 bytes, OPTIONAL) |
39
+ // +-----------------------------+
40
+
41
+ // Header
42
+ // ID3v2/file identifier "ID3"
43
+ // ID3v2 version $04 00
44
+ // ID3v2 flags (%ab000000 in v2.2, %abc00000 in v2.3, %abcd0000 in v2.4.x)
45
+ // ID3v2 size 4 * %0xxxxxxx
46
+
47
+
48
+ // shortcuts
49
+ $info['id3v2']['header'] = true;
50
+ $thisfile_id3v2 = &$info['id3v2'];
51
+ $thisfile_id3v2['flags'] = array();
52
+ $thisfile_id3v2_flags = &$thisfile_id3v2['flags'];
53
+
54
+
55
+ fseek($this->getid3->fp, $this->StartingOffset, SEEK_SET);
56
+ $header = fread($this->getid3->fp, 10);
57
+ if (substr($header, 0, 3) == 'ID3' && strlen($header) == 10) {
58
+
59
+ $thisfile_id3v2['majorversion'] = ord($header{3});
60
+ $thisfile_id3v2['minorversion'] = ord($header{4});
61
+
62
+ // shortcut
63
+ $id3v2_majorversion = &$thisfile_id3v2['majorversion'];
64
+
65
+ } else {
66
+
67
+ unset($info['id3v2']);
68
+ return false;
69
+
70
+ }
71
+
72
+ if ($id3v2_majorversion > 4) { // this script probably won't correctly parse ID3v2.5.x and above (if it ever exists)
73
+
74
+ $info['error'][] = 'this script only parses up to ID3v2.4.x - this tag is ID3v2.'.$id3v2_majorversion.'.'.$thisfile_id3v2['minorversion'];
75
+ return false;
76
+
77
+ }
78
+
79
+ $id3_flags = ord($header{5});
80
+ switch ($id3v2_majorversion) {
81
+ case 2:
82
+ // %ab000000 in v2.2
83
+ $thisfile_id3v2_flags['unsynch'] = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
84
+ $thisfile_id3v2_flags['compression'] = (bool) ($id3_flags & 0x40); // b - Compression
85
+ break;
86
+
87
+ case 3:
88
+ // %abc00000 in v2.3
89
+ $thisfile_id3v2_flags['unsynch'] = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
90
+ $thisfile_id3v2_flags['exthead'] = (bool) ($id3_flags & 0x40); // b - Extended header
91
+ $thisfile_id3v2_flags['experim'] = (bool) ($id3_flags & 0x20); // c - Experimental indicator
92
+ break;
93
+
94
+ case 4:
95
+ // %abcd0000 in v2.4
96
+ $thisfile_id3v2_flags['unsynch'] = (bool) ($id3_flags & 0x80); // a - Unsynchronisation
97
+ $thisfile_id3v2_flags['exthead'] = (bool) ($id3_flags & 0x40); // b - Extended header
98
+ $thisfile_id3v2_flags['experim'] = (bool) ($id3_flags & 0x20); // c - Experimental indicator
99
+ $thisfile_id3v2_flags['isfooter'] = (bool) ($id3_flags & 0x10); // d - Footer present
100
+ break;
101
+ }
102
+
103
+ $thisfile_id3v2['headerlength'] = getid3_lib::BigEndian2Int(substr($header, 6, 4), 1) + 10; // length of ID3v2 tag in 10-byte header doesn't include 10-byte header length
104
+
105
+ $thisfile_id3v2['tag_offset_start'] = $this->StartingOffset;
106
+ $thisfile_id3v2['tag_offset_end'] = $thisfile_id3v2['tag_offset_start'] + $thisfile_id3v2['headerlength'];
107
+
108
+
109
+
110
+ // create 'encoding' key - used by getid3::HandleAllTags()
111
+ // in ID3v2 every field can have it's own encoding type
112
+ // so force everything to UTF-8 so it can be handled consistantly
113
+ $thisfile_id3v2['encoding'] = 'UTF-8';
114
+
115
+
116
+ // Frames
117
+
118
+ // All ID3v2 frames consists of one frame header followed by one or more
119
+ // fields containing the actual information. The header is always 10
120
+ // bytes and laid out as follows:
121
+ //
122
+ // Frame ID $xx xx xx xx (four characters)
123
+ // Size 4 * %0xxxxxxx
124
+ // Flags $xx xx
125
+
126
+ $sizeofframes = $thisfile_id3v2['headerlength'] - 10; // not including 10-byte initial header
127
+ if (!empty($thisfile_id3v2['exthead']['length'])) {
128
+ $sizeofframes -= ($thisfile_id3v2['exthead']['length'] + 4);
129
+ }
130
+ if (!empty($thisfile_id3v2_flags['isfooter'])) {
131
+ $sizeofframes -= 10; // footer takes last 10 bytes of ID3v2 header, after frame data, before audio
132
+ }
133
+ if ($sizeofframes > 0) {
134
+
135
+ $framedata = fread($this->getid3->fp, $sizeofframes); // read all frames from file into $framedata variable
136
+
137
+ // if entire frame data is unsynched, de-unsynch it now (ID3v2.3.x)
138
+ if (!empty($thisfile_id3v2_flags['unsynch']) && ($id3v2_majorversion <= 3)) {
139
+ $framedata = $this->DeUnsynchronise($framedata);
140
+ }
141
+ // [in ID3v2.4.0] Unsynchronisation [S:6.1] is done on frame level, instead
142
+ // of on tag level, making it easier to skip frames, increasing the streamability
143
+ // of the tag. The unsynchronisation flag in the header [S:3.1] indicates that
144
+ // there exists an unsynchronised frame, while the new unsynchronisation flag in
145
+ // the frame header [S:4.1.2] indicates unsynchronisation.
146
+
147
+
148
+ //$framedataoffset = 10 + ($thisfile_id3v2['exthead']['length'] ? $thisfile_id3v2['exthead']['length'] + 4 : 0); // how many bytes into the stream - start from after the 10-byte header (and extended header length+4, if present)
149
+ $framedataoffset = 10; // how many bytes into the stream - start from after the 10-byte header
150
+
151
+
152
+ // Extended Header
153
+ if (!empty($thisfile_id3v2_flags['exthead'])) {
154
+ $extended_header_offset = 0;
155
+
156
+ if ($id3v2_majorversion == 3) {
157
+
158
+ // v2.3 definition:
159
+ //Extended header size $xx xx xx xx // 32-bit integer
160
+ //Extended Flags $xx xx
161
+ // %x0000000 %00000000 // v2.3
162
+ // x - CRC data present
163
+ //Size of padding $xx xx xx xx
164
+
165
+ $thisfile_id3v2['exthead']['length'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4), 0);
166
+ $extended_header_offset += 4;
167
+
168
+ $thisfile_id3v2['exthead']['flag_bytes'] = 2;
169
+ $thisfile_id3v2['exthead']['flag_raw'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $thisfile_id3v2['exthead']['flag_bytes']));
170
+ $extended_header_offset += $thisfile_id3v2['exthead']['flag_bytes'];
171
+
172
+ $thisfile_id3v2['exthead']['flags']['crc'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x8000);
173
+
174
+ $thisfile_id3v2['exthead']['padding_size'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4));
175
+ $extended_header_offset += 4;
176
+
177
+ if ($thisfile_id3v2['exthead']['flags']['crc']) {
178
+ $thisfile_id3v2['exthead']['flag_data']['crc'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4));
179
+ $extended_header_offset += 4;
180
+ }
181
+ $extended_header_offset += $thisfile_id3v2['exthead']['padding_size'];
182
+
183
+ } elseif ($id3v2_majorversion == 4) {
184
+
185
+ // v2.4 definition:
186
+ //Extended header size 4 * %0xxxxxxx // 28-bit synchsafe integer
187
+ //Number of flag bytes $01
188
+ //Extended Flags $xx
189
+ // %0bcd0000 // v2.4
190
+ // b - Tag is an update
191
+ // Flag data length $00
192
+ // c - CRC data present
193
+ // Flag data length $05
194
+ // Total frame CRC 5 * %0xxxxxxx
195
+ // d - Tag restrictions
196
+ // Flag data length $01
197
+
198
+ $thisfile_id3v2['exthead']['length'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 4), true);
199
+ $extended_header_offset += 4;
200
+
201
+ $thisfile_id3v2['exthead']['flag_bytes'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should always be 1
202
+ $extended_header_offset += 1;
203
+
204
+ $thisfile_id3v2['exthead']['flag_raw'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $thisfile_id3v2['exthead']['flag_bytes']));
205
+ $extended_header_offset += $thisfile_id3v2['exthead']['flag_bytes'];
206
+
207
+ $thisfile_id3v2['exthead']['flags']['update'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x40);
208
+ $thisfile_id3v2['exthead']['flags']['crc'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x20);
209
+ $thisfile_id3v2['exthead']['flags']['restrictions'] = (bool) ($thisfile_id3v2['exthead']['flag_raw'] & 0x10);
210
+
211
+ if ($thisfile_id3v2['exthead']['flags']['update']) {
212
+ $ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 0
213
+ $extended_header_offset += 1;
214
+ }
215
+
216
+ if ($thisfile_id3v2['exthead']['flags']['crc']) {
217
+ $ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 5
218
+ $extended_header_offset += 1;
219
+ $thisfile_id3v2['exthead']['flag_data']['crc'] = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, $ext_header_chunk_length), true, false);
220
+ $extended_header_offset += $ext_header_chunk_length;
221
+ }
222
+
223
+ if ($thisfile_id3v2['exthead']['flags']['restrictions']) {
224
+ $ext_header_chunk_length = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1)); // should be 1
225
+ $extended_header_offset += 1;
226
+
227
+ // %ppqrrstt
228
+ $restrictions_raw = getid3_lib::BigEndian2Int(substr($framedata, $extended_header_offset, 1));
229
+ $extended_header_offset += 1;
230
+ $thisfile_id3v2['exthead']['flags']['restrictions']['tagsize'] = ($restrictions_raw & 0xC0) >> 6; // p - Tag size restrictions
231
+ $thisfile_id3v2['exthead']['flags']['restrictions']['textenc'] = ($restrictions_raw & 0x20) >> 5; // q - Text encoding restrictions
232
+ $thisfile_id3v2['exthead']['flags']['restrictions']['textsize'] = ($restrictions_raw & 0x18) >> 3; // r - Text fields size restrictions
233
+ $thisfile_id3v2['exthead']['flags']['restrictions']['imgenc'] = ($restrictions_raw & 0x04) >> 2; // s - Image encoding restrictions
234
+ $thisfile_id3v2['exthead']['flags']['restrictions']['imgsize'] = ($restrictions_raw & 0x03) >> 0; // t - Image size restrictions
235
+
236
+ $thisfile_id3v2['exthead']['flags']['restrictions_text']['tagsize'] = $this->LookupExtendedHeaderRestrictionsTagSizeLimits($thisfile_id3v2['exthead']['flags']['restrictions']['tagsize']);
237
+ $thisfile_id3v2['exthead']['flags']['restrictions_text']['textenc'] = $this->LookupExtendedHeaderRestrictionsTextEncodings($thisfile_id3v2['exthead']['flags']['restrictions']['textenc']);
238
+ $thisfile_id3v2['exthead']['flags']['restrictions_text']['textsize'] = $this->LookupExtendedHeaderRestrictionsTextFieldSize($thisfile_id3v2['exthead']['flags']['restrictions']['textsize']);
239
+ $thisfile_id3v2['exthead']['flags']['restrictions_text']['imgenc'] = $this->LookupExtendedHeaderRestrictionsImageEncoding($thisfile_id3v2['exthead']['flags']['restrictions']['imgenc']);
240
+ $thisfile_id3v2['exthead']['flags']['restrictions_text']['imgsize'] = $this->LookupExtendedHeaderRestrictionsImageSizeSize($thisfile_id3v2['exthead']['flags']['restrictions']['imgsize']);
241
+ }
242
+
243
+ if ($thisfile_id3v2['exthead']['length'] != $extended_header_offset) {
244
+ $info['warning'][] = 'ID3v2.4 extended header length mismatch (expecting '.intval($thisfile_id3v2['exthead']['length']).', found '.intval($extended_header_offset).')';
245
+ }
246
+ }
247
+
248
+ $framedataoffset += $extended_header_offset;
249
+ $framedata = substr($framedata, $extended_header_offset);
250
+ } // end extended header
251
+
252
+
253
+ while (isset($framedata) && (strlen($framedata) > 0)) { // cycle through until no more frame data is left to parse
254
+ if (strlen($framedata) <= $this->ID3v2HeaderLength($id3v2_majorversion)) {
255
+ // insufficient room left in ID3v2 header for actual data - must be padding
256
+ $thisfile_id3v2['padding']['start'] = $framedataoffset;
257
+ $thisfile_id3v2['padding']['length'] = strlen($framedata);
258
+ $thisfile_id3v2['padding']['valid'] = true;
259
+ for ($i = 0; $i < $thisfile_id3v2['padding']['length']; $i++) {
260
+ if ($framedata{$i} != "\x00") {
261
+ $thisfile_id3v2['padding']['valid'] = false;
262
+ $thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i;
263
+ $info['warning'][] = 'Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)';
264
+ break;
265
+ }
266
+ }
267
+ break; // skip rest of ID3v2 header
268
+ }
269
+ if ($id3v2_majorversion == 2) {
270
+ // Frame ID $xx xx xx (three characters)
271
+ // Size $xx xx xx (24-bit integer)
272
+ // Flags $xx xx
273
+
274
+ $frame_header = substr($framedata, 0, 6); // take next 6 bytes for header
275
+ $framedata = substr($framedata, 6); // and leave the rest in $framedata
276
+ $frame_name = substr($frame_header, 0, 3);
277
+ $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 3, 3), 0);
278
+ $frame_flags = 0; // not used for anything in ID3v2.2, just set to avoid E_NOTICEs
279
+
280
+ } elseif ($id3v2_majorversion > 2) {
281
+
282
+ // Frame ID $xx xx xx xx (four characters)
283
+ // Size $xx xx xx xx (32-bit integer in v2.3, 28-bit synchsafe in v2.4+)
284
+ // Flags $xx xx
285
+
286
+ $frame_header = substr($framedata, 0, 10); // take next 10 bytes for header
287
+ $framedata = substr($framedata, 10); // and leave the rest in $framedata
288
+
289
+ $frame_name = substr($frame_header, 0, 4);
290
+ if ($id3v2_majorversion == 3) {
291
+ $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0); // 32-bit integer
292
+ } else { // ID3v2.4+
293
+ $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 1); // 32-bit synchsafe integer (28-bit value)
294
+ }
295
+
296
+ if ($frame_size < (strlen($framedata) + 4)) {
297
+ $nextFrameID = substr($framedata, $frame_size, 4);
298
+ if ($this->IsValidID3v2FrameName($nextFrameID, $id3v2_majorversion)) {
299
+ // next frame is OK
300
+ } elseif (($frame_name == "\x00".'MP3') || ($frame_name == "\x00\x00".'MP') || ($frame_name == ' MP3') || ($frame_name == 'MP3e')) {
301
+ // MP3ext known broken frames - "ok" for the purposes of this test
302
+ } elseif (($id3v2_majorversion == 4) && ($this->IsValidID3v2FrameName(substr($framedata, getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0), 4), 3))) {
303
+ $info['warning'][] = 'ID3v2 tag written as ID3v2.4, but with non-synchsafe integers (ID3v2.3 style). Older versions of (Helium2; iTunes) are known culprits of this. Tag has been parsed as ID3v2.3';
304
+ $id3v2_majorversion = 3;
305
+ $frame_size = getid3_lib::BigEndian2Int(substr($frame_header, 4, 4), 0); // 32-bit integer
306
+ }
307
+ }
308
+
309
+
310
+ $frame_flags = getid3_lib::BigEndian2Int(substr($frame_header, 8, 2));
311
+ }
312
+
313
+ if ((($id3v2_majorversion == 2) && ($frame_name == "\x00\x00\x00")) || ($frame_name == "\x00\x00\x00\x00")) {
314
+ // padding encountered
315
+
316
+ $thisfile_id3v2['padding']['start'] = $framedataoffset;
317
+ $thisfile_id3v2['padding']['length'] = strlen($frame_header) + strlen($framedata);
318
+ $thisfile_id3v2['padding']['valid'] = true;
319
+
320
+ $len = strlen($framedata);
321
+ for ($i = 0; $i < $len; $i++) {
322
+ if ($framedata{$i} != "\x00") {
323
+ $thisfile_id3v2['padding']['valid'] = false;
324
+ $thisfile_id3v2['padding']['errorpos'] = $thisfile_id3v2['padding']['start'] + $i;
325
+ $info['warning'][] = 'Invalid ID3v2 padding found at offset '.$thisfile_id3v2['padding']['errorpos'].' (the remaining '.($thisfile_id3v2['padding']['length'] - $i).' bytes are considered invalid)';
326
+ break;
327
+ }
328
+ }
329
+ break; // skip rest of ID3v2 header
330
+ }
331
+
332
+ if ($frame_name == 'COM ') {
333
+ $info['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))). [Note: this particular error has been known to happen with tags edited by iTunes (versions "X v2.0.3", "v3.0.1" are known-guilty, probably others too)]';
334
+ $frame_name = 'COMM';
335
+ }
336
+ if (($frame_size <= strlen($framedata)) && ($this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion))) {
337
+
338
+ unset($parsedFrame);
339
+ $parsedFrame['frame_name'] = $frame_name;
340
+ $parsedFrame['frame_flags_raw'] = $frame_flags;
341
+ $parsedFrame['data'] = substr($framedata, 0, $frame_size);
342
+ $parsedFrame['datalength'] = getid3_lib::CastAsInt($frame_size);
343
+ $parsedFrame['dataoffset'] = $framedataoffset;
344
+
345
+ $this->ParseID3v2Frame($parsedFrame);
346
+ $thisfile_id3v2[$frame_name][] = $parsedFrame;
347
+
348
+ $framedata = substr($framedata, $frame_size);
349
+
350
+ } else { // invalid frame length or FrameID
351
+
352
+ if ($frame_size <= strlen($framedata)) {
353
+
354
+ if ($this->IsValidID3v2FrameName(substr($framedata, $frame_size, 4), $id3v2_majorversion)) {
355
+
356
+ // next frame is valid, just skip the current frame
357
+ $framedata = substr($framedata, $frame_size);
358
+ $info['warning'][] = 'Next ID3v2 frame is valid, skipping current frame.';
359
+
360
+ } else {
361
+
362
+ // next frame is invalid too, abort processing
363
+ //unset($framedata);
364
+ $framedata = null;
365
+ $info['error'][] = 'Next ID3v2 frame is also invalid, aborting processing.';
366
+
367
+ }
368
+
369
+ } elseif ($frame_size == strlen($framedata)) {
370
+
371
+ // this is the last frame, just skip
372
+ $info['warning'][] = 'This was the last ID3v2 frame.';
373
+
374
+ } else {
375
+
376
+ // next frame is invalid too, abort processing
377
+ //unset($framedata);
378
+ $framedata = null;
379
+ $info['warning'][] = 'Invalid ID3v2 frame size, aborting.';
380
+
381
+ }
382
+ if (!$this->IsValidID3v2FrameName($frame_name, $id3v2_majorversion)) {
383
+
384
+ switch ($frame_name) {
385
+ case "\x00\x00".'MP':
386
+ case "\x00".'MP3':
387
+ case ' MP3':
388
+ case 'MP3e':
389
+ case "\x00".'MP':
390
+ case ' MP':
391
+ case 'MP3':
392
+ $info['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))). [Note: this particular error has been known to happen with tags edited by "MP3ext (www.mutschler.de/mp3ext/)"]';
393
+ break;
394
+
395
+ default:
396
+ $info['warning'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: !IsValidID3v2FrameName("'.str_replace("\x00", ' ', $frame_name).'", '.$id3v2_majorversion.'))).';
397
+ break;
398
+ }
399
+
400
+ } elseif (!isset($framedata) || ($frame_size > strlen($framedata))) {
401
+
402
+ $info['error'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag). (ERROR: $frame_size ('.$frame_size.') > strlen($framedata) ('.(isset($framedata) ? strlen($framedata) : 'null').')).';
403
+
404
+ } else {
405
+
406
+ $info['error'][] = 'error parsing "'.$frame_name.'" ('.$framedataoffset.' bytes into the ID3v2.'.$id3v2_majorversion.' tag).';
407
+
408
+ }
409
+
410
+ }
411
+ $framedataoffset += ($frame_size + $this->ID3v2HeaderLength($id3v2_majorversion));
412
+
413
+ }
414
+
415
+ }
416
+
417
+
418
+ // Footer
419
+
420
+ // The footer is a copy of the header, but with a different identifier.
421
+ // ID3v2 identifier "3DI"
422
+ // ID3v2 version $04 00
423
+ // ID3v2 flags %abcd0000
424
+ // ID3v2 size 4 * %0xxxxxxx
425
+
426
+ if (isset($thisfile_id3v2_flags['isfooter']) && $thisfile_id3v2_flags['isfooter']) {
427
+ $footer = fread($this->getid3->fp, 10);
428
+ if (substr($footer, 0, 3) == '3DI') {
429
+ $thisfile_id3v2['footer'] = true;
430
+ $thisfile_id3v2['majorversion_footer'] = ord($footer{3});
431
+ $thisfile_id3v2['minorversion_footer'] = ord($footer{4});
432
+ }
433
+ if ($thisfile_id3v2['majorversion_footer'] <= 4) {
434
+ $id3_flags = ord(substr($footer{5}));
435
+ $thisfile_id3v2_flags['unsynch_footer'] = (bool) ($id3_flags & 0x80);
436
+ $thisfile_id3v2_flags['extfoot_footer'] = (bool) ($id3_flags & 0x40);
437
+ $thisfile_id3v2_flags['experim_footer'] = (bool) ($id3_flags & 0x20);
438
+ $thisfile_id3v2_flags['isfooter_footer'] = (bool) ($id3_flags & 0x10);
439
+
440
+ $thisfile_id3v2['footerlength'] = getid3_lib::BigEndian2Int(substr($footer, 6, 4), 1);
441
+ }
442
+ } // end footer
443
+
444
+ if (isset($thisfile_id3v2['comments']['genre'])) {
445
+ foreach ($thisfile_id3v2['comments']['genre'] as $key => $value) {
446
+ unset($thisfile_id3v2['comments']['genre'][$key]);
447
+ $thisfile_id3v2['comments'] = getid3_lib::array_merge_noclobber($thisfile_id3v2['comments'], array('genre'=>$this->ParseID3v2GenreString($value)));
448
+ }
449
+ }
450
+
451
+ if (isset($thisfile_id3v2['comments']['track'])) {
452
+ foreach ($thisfile_id3v2['comments']['track'] as $key => $value) {
453
+ if (strstr($value, '/')) {
454
+ list($thisfile_id3v2['comments']['tracknum'][$key], $thisfile_id3v2['comments']['totaltracks'][$key]) = explode('/', $thisfile_id3v2['comments']['track'][$key]);
455
+ }
456
+ }
457
+ }
458
+
459
+ if (!isset($thisfile_id3v2['comments']['year']) && !empty($thisfile_id3v2['comments']['recording_time'][0]) && preg_match('#^([0-9]{4})#', trim($thisfile_id3v2['comments']['recording_time'][0]), $matches)) {
460
+ $thisfile_id3v2['comments']['year'] = array($matches[1]);
461
+ }
462
+
463
+
464
+ if (!empty($thisfile_id3v2['TXXX'])) {
465
+ // MediaMonkey does this, maybe others: write a blank RGAD frame, but put replay-gain adjustment values in TXXX frames
466
+ foreach ($thisfile_id3v2['TXXX'] as $txxx_array) {
467
+ switch ($txxx_array['description']) {
468
+ case 'replaygain_track_gain':
469
+ if (empty($info['replay_gain']['track']['adjustment']) && !empty($txxx_array['data'])) {
470
+ $info['replay_gain']['track']['adjustment'] = floatval(trim(str_replace('dB', '', $txxx_array['data'])));
471
+ }
472
+ break;
473
+ case 'replaygain_track_peak':
474
+ if (empty($info['replay_gain']['track']['peak']) && !empty($txxx_array['data'])) {
475
+ $info['replay_gain']['track']['peak'] = floatval($txxx_array['data']);
476
+ }
477
+ break;
478
+ case 'replaygain_album_gain':
479
+ if (empty($info['replay_gain']['album']['adjustment']) && !empty($txxx_array['data'])) {
480
+ $info['replay_gain']['album']['adjustment'] = floatval(trim(str_replace('dB', '', $txxx_array['data'])));
481
+ }
482
+ break;
483
+ }
484
+ }
485
+ }
486
+
487
+
488
+ // Set avdataoffset
489
+ $info['avdataoffset'] = $thisfile_id3v2['headerlength'];
490
+ if (isset($thisfile_id3v2['footer'])) {
491
+ $info['avdataoffset'] += 10;
492
+ }
493
+
494
+ return true;
495
+ }
496
+
497
+
498
+ function ParseID3v2GenreString($genrestring) {
499
+ // Parse genres into arrays of genreName and genreID
500
+ // ID3v2.2.x, ID3v2.3.x: '(21)' or '(4)Eurodisco' or '(51)(39)' or '(55)((I think...)'
501
+ // ID3v2.4.x: '21' $00 'Eurodisco' $00
502
+ $clean_genres = array();
503
+ if (strpos($genrestring, "\x00") === false) {
504
+ $genrestring = preg_replace('#\(([0-9]{1,3})\)#', '$1'."\x00", $genrestring);
505
+ }
506
+ $genre_elements = explode("\x00", $genrestring);
507
+ foreach ($genre_elements as $element) {
508
+ $element = trim($element);
509
+ if ($element) {
510
+ if (preg_match('#^[0-9]{1,3}#', $element)) {
511
+ $clean_genres[] = getid3_id3v1::LookupGenreName($element);
512
+ } else {
513
+ $clean_genres[] = str_replace('((', '(', $element);
514
+ }
515
+ }
516
+ }
517
+ return $clean_genres;
518
+ }
519
+
520
+
521
+ function ParseID3v2Frame(&$parsedFrame) {
522
+
523
+ // shortcuts
524
+ $info = &$this->getid3->info;
525
+ $id3v2_majorversion = $info['id3v2']['majorversion'];
526
+
527
+ $parsedFrame['framenamelong'] = $this->FrameNameLongLookup($parsedFrame['frame_name']);
528
+ if (empty($parsedFrame['framenamelong'])) {
529
+ unset($parsedFrame['framenamelong']);
530
+ }
531
+ $parsedFrame['framenameshort'] = $this->FrameNameShortLookup($parsedFrame['frame_name']);
532
+ if (empty($parsedFrame['framenameshort'])) {
533
+ unset($parsedFrame['framenameshort']);
534
+ }
535
+
536
+ if ($id3v2_majorversion >= 3) { // frame flags are not part of the ID3v2.2 standard
537
+ if ($id3v2_majorversion == 3) {
538
+ // Frame Header Flags
539
+ // %abc00000 %ijk00000
540
+ $parsedFrame['flags']['TagAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x8000); // a - Tag alter preservation
541
+ $parsedFrame['flags']['FileAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x4000); // b - File alter preservation
542
+ $parsedFrame['flags']['ReadOnly'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x2000); // c - Read only
543
+ $parsedFrame['flags']['compression'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0080); // i - Compression
544
+ $parsedFrame['flags']['Encryption'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0040); // j - Encryption
545
+ $parsedFrame['flags']['GroupingIdentity'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0020); // k - Grouping identity
546
+
547
+ } elseif ($id3v2_majorversion == 4) {
548
+ // Frame Header Flags
549
+ // %0abc0000 %0h00kmnp
550
+ $parsedFrame['flags']['TagAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x4000); // a - Tag alter preservation
551
+ $parsedFrame['flags']['FileAlterPreservation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x2000); // b - File alter preservation
552
+ $parsedFrame['flags']['ReadOnly'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x1000); // c - Read only
553
+ $parsedFrame['flags']['GroupingIdentity'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0040); // h - Grouping identity
554
+ $parsedFrame['flags']['compression'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0008); // k - Compression
555
+ $parsedFrame['flags']['Encryption'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0004); // m - Encryption
556
+ $parsedFrame['flags']['Unsynchronisation'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0002); // n - Unsynchronisation
557
+ $parsedFrame['flags']['DataLengthIndicator'] = (bool) ($parsedFrame['frame_flags_raw'] & 0x0001); // p - Data length indicator
558
+
559
+ // Frame-level de-unsynchronisation - ID3v2.4
560
+ if ($parsedFrame['flags']['Unsynchronisation']) {
561
+ $parsedFrame['data'] = $this->DeUnsynchronise($parsedFrame['data']);
562
+ }
563
+
564
+ if ($parsedFrame['flags']['DataLengthIndicator']) {
565
+ $parsedFrame['data_length_indicator'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 4), 1);
566
+ $parsedFrame['data'] = substr($parsedFrame['data'], 4);
567
+ }
568
+ }
569
+
570
+ // Frame-level de-compression
571
+ if ($parsedFrame['flags']['compression']) {
572
+ $parsedFrame['decompressed_size'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 4));
573
+ if (!function_exists('gzuncompress')) {
574
+ $info['warning'][] = 'gzuncompress() support required to decompress ID3v2 frame "'.$parsedFrame['frame_name'].'"';
575
+ } else {
576
+ if ($decompresseddata = @gzuncompress(substr($parsedFrame['data'], 4))) {
577
+ //if ($decompresseddata = @gzuncompress($parsedFrame['data'])) {
578
+ $parsedFrame['data'] = $decompresseddata;
579
+ unset($decompresseddata);
580
+ } else {
581
+ $info['warning'][] = 'gzuncompress() failed on compressed contents of ID3v2 frame "'.$parsedFrame['frame_name'].'"';
582
+ }
583
+ }
584
+ }
585
+ }
586
+
587
+ if (!empty($parsedFrame['flags']['DataLengthIndicator'])) {
588
+ if ($parsedFrame['data_length_indicator'] != strlen($parsedFrame['data'])) {
589
+ $info['warning'][] = 'ID3v2 frame "'.$parsedFrame['frame_name'].'" should be '.$parsedFrame['data_length_indicator'].' bytes long according to DataLengthIndicator, but found '.strlen($parsedFrame['data']).' bytes of data';
590
+ }
591
+ }
592
+
593
+ if (isset($parsedFrame['datalength']) && ($parsedFrame['datalength'] == 0)) {
594
+
595
+ $warning = 'Frame "'.$parsedFrame['frame_name'].'" at offset '.$parsedFrame['dataoffset'].' has no data portion';
596
+ switch ($parsedFrame['frame_name']) {
597
+ case 'WCOM':
598
+ $warning .= ' (this is known to happen with files tagged by RioPort)';
599
+ break;
600
+
601
+ default:
602
+ break;
603
+ }
604
+ $info['warning'][] = $warning;
605
+
606
+ } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'UFID')) || // 4.1 UFID Unique file identifier
607
+ (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'UFI'))) { // 4.1 UFI Unique file identifier
608
+ // There may be more than one 'UFID' frame in a tag,
609
+ // but only one with the same 'Owner identifier'.
610
+ // <Header for 'Unique file identifier', ID: 'UFID'>
611
+ // Owner identifier <text string> $00
612
+ // Identifier <up to 64 bytes binary data>
613
+ $exploded = explode("\x00", $parsedFrame['data'], 2);
614
+ $parsedFrame['ownerid'] = (isset($exploded[0]) ? $exploded[0] : '');
615
+ $parsedFrame['data'] = (isset($exploded[1]) ? $exploded[1] : '');
616
+
617
+ } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'TXXX')) || // 4.2.2 TXXX User defined text information frame
618
+ (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'TXX'))) { // 4.2.2 TXX User defined text information frame
619
+ // There may be more than one 'TXXX' frame in each tag,
620
+ // but only one with the same description.
621
+ // <Header for 'User defined text information frame', ID: 'TXXX'>
622
+ // Text encoding $xx
623
+ // Description <text string according to encoding> $00 (00)
624
+ // Value <text string according to encoding>
625
+
626
+ $frame_offset = 0;
627
+ $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
628
+
629
+ if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
630
+ $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
631
+ }
632
+ $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
633
+ if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
634
+ $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
635
+ }
636
+ $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
637
+ if (ord($frame_description) === 0) {
638
+ $frame_description = '';
639
+ }
640
+ $parsedFrame['encodingid'] = $frame_textencoding;
641
+ $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
642
+
643
+ $parsedFrame['description'] = $frame_description;
644
+ $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
645
+ if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
646
+ $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = trim(getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']));
647
+ }
648
+ //unset($parsedFrame['data']); do not unset, may be needed elsewhere, e.g. for replaygain
649
+
650
+
651
+ } elseif ($parsedFrame['frame_name']{0} == 'T') { // 4.2. T??[?] Text information frame
652
+ // There may only be one text information frame of its kind in an tag.
653
+ // <Header for 'Text information frame', ID: 'T000' - 'TZZZ',
654
+ // excluding 'TXXX' described in 4.2.6.>
655
+ // Text encoding $xx
656
+ // Information <text string(s) according to encoding>
657
+
658
+ $frame_offset = 0;
659
+ $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
660
+ if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
661
+ $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
662
+ }
663
+
664
+ $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
665
+
666
+ $parsedFrame['encodingid'] = $frame_textencoding;
667
+ $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
668
+
669
+ if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
670
+ $string = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
671
+ $string = rtrim($string, "\x00"); // remove possible terminating null (put by encoding id or software bug)
672
+ $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $string;
673
+ unset($string);
674
+ }
675
+
676
+ } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'WXXX')) || // 4.3.2 WXXX User defined URL link frame
677
+ (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'WXX'))) { // 4.3.2 WXX User defined URL link frame
678
+ // There may be more than one 'WXXX' frame in each tag,
679
+ // but only one with the same description
680
+ // <Header for 'User defined URL link frame', ID: 'WXXX'>
681
+ // Text encoding $xx
682
+ // Description <text string according to encoding> $00 (00)
683
+ // URL <text string>
684
+
685
+ $frame_offset = 0;
686
+ $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
687
+ if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
688
+ $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
689
+ }
690
+ $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
691
+ if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
692
+ $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
693
+ }
694
+ $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
695
+
696
+ if (ord($frame_description) === 0) {
697
+ $frame_description = '';
698
+ }
699
+ $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
700
+
701
+ $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding));
702
+ if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
703
+ $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
704
+ }
705
+ if ($frame_terminatorpos) {
706
+ // there are null bytes after the data - this is not according to spec
707
+ // only use data up to first null byte
708
+ $frame_urldata = (string) substr($parsedFrame['data'], 0, $frame_terminatorpos);
709
+ } else {
710
+ // no null bytes following data, just use all data
711
+ $frame_urldata = (string) $parsedFrame['data'];
712
+ }
713
+
714
+ $parsedFrame['encodingid'] = $frame_textencoding;
715
+ $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
716
+
717
+ $parsedFrame['url'] = $frame_urldata;
718
+ $parsedFrame['description'] = $frame_description;
719
+ if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
720
+ $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['url']);
721
+ }
722
+ unset($parsedFrame['data']);
723
+
724
+
725
+ } elseif ($parsedFrame['frame_name']{0} == 'W') { // 4.3. W??? URL link frames
726
+ // There may only be one URL link frame of its kind in a tag,
727
+ // except when stated otherwise in the frame description
728
+ // <Header for 'URL link frame', ID: 'W000' - 'WZZZ', excluding 'WXXX'
729
+ // described in 4.3.2.>
730
+ // URL <text string>
731
+
732
+ $parsedFrame['url'] = trim($parsedFrame['data']);
733
+ if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
734
+ $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['url'];
735
+ }
736
+ unset($parsedFrame['data']);
737
+
738
+
739
+ } elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'IPLS')) || // 4.4 IPLS Involved people list (ID3v2.3 only)
740
+ (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'IPL'))) { // 4.4 IPL Involved people list (ID3v2.2 only)
741
+ // There may only be one 'IPL' frame in each tag
742
+ // <Header for 'User defined URL link frame', ID: 'IPL'>
743
+ // Text encoding $xx
744
+ // People list strings <textstrings>
745
+
746
+ $frame_offset = 0;
747
+ $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
748
+ if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
749
+ $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
750
+ }
751
+ $parsedFrame['encodingid'] = $frame_textencoding;
752
+ $parsedFrame['encoding'] = $this->TextEncodingNameLookup($parsedFrame['encodingid']);
753
+
754
+ $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
755
+ if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
756
+ $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
757
+ }
758
+
759
+
760
+ } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'MCDI')) || // 4.4 MCDI Music CD identifier
761
+ (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'MCI'))) { // 4.5 MCI Music CD identifier
762
+ // There may only be one 'MCDI' frame in each tag
763
+ // <Header for 'Music CD identifier', ID: 'MCDI'>
764
+ // CD TOC <binary data>
765
+
766
+ if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
767
+ $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data'];
768
+ }
769
+
770
+
771
+ } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'ETCO')) || // 4.5 ETCO Event timing codes
772
+ (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'ETC'))) { // 4.6 ETC Event timing codes
773
+ // There may only be one 'ETCO' frame in each tag
774
+ // <Header for 'Event timing codes', ID: 'ETCO'>
775
+ // Time stamp format $xx
776
+ // Where time stamp format is:
777
+ // $01 (32-bit value) MPEG frames from beginning of file
778
+ // $02 (32-bit value) milliseconds from beginning of file
779
+ // Followed by a list of key events in the following format:
780
+ // Type of event $xx
781
+ // Time stamp $xx (xx ...)
782
+ // The 'Time stamp' is set to zero if directly at the beginning of the sound
783
+ // or after the previous event. All events MUST be sorted in chronological order.
784
+
785
+ $frame_offset = 0;
786
+ $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
787
+
788
+ while ($frame_offset < strlen($parsedFrame['data'])) {
789
+ $parsedFrame['typeid'] = substr($parsedFrame['data'], $frame_offset++, 1);
790
+ $parsedFrame['type'] = $this->ETCOEventLookup($parsedFrame['typeid']);
791
+ $parsedFrame['timestamp'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
792
+ $frame_offset += 4;
793
+ }
794
+ unset($parsedFrame['data']);
795
+
796
+
797
+ } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'MLLT')) || // 4.6 MLLT MPEG location lookup table
798
+ (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'MLL'))) { // 4.7 MLL MPEG location lookup table
799
+ // There may only be one 'MLLT' frame in each tag
800
+ // <Header for 'Location lookup table', ID: 'MLLT'>
801
+ // MPEG frames between reference $xx xx
802
+ // Bytes between reference $xx xx xx
803
+ // Milliseconds between reference $xx xx xx
804
+ // Bits for bytes deviation $xx
805
+ // Bits for milliseconds dev. $xx
806
+ // Then for every reference the following data is included;
807
+ // Deviation in bytes %xxx....
808
+ // Deviation in milliseconds %xxx....
809
+
810
+ $frame_offset = 0;
811
+ $parsedFrame['framesbetweenreferences'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 0, 2));
812
+ $parsedFrame['bytesbetweenreferences'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 2, 3));
813
+ $parsedFrame['msbetweenreferences'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 5, 3));
814
+ $parsedFrame['bitsforbytesdeviation'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 8, 1));
815
+ $parsedFrame['bitsformsdeviation'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], 9, 1));
816
+ $parsedFrame['data'] = substr($parsedFrame['data'], 10);
817
+ while ($frame_offset < strlen($parsedFrame['data'])) {
818
+ $deviationbitstream .= getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1));
819
+ }
820
+ $reference_counter = 0;
821
+ while (strlen($deviationbitstream) > 0) {
822
+ $parsedFrame[$reference_counter]['bytedeviation'] = bindec(substr($deviationbitstream, 0, $parsedFrame['bitsforbytesdeviation']));
823
+ $parsedFrame[$reference_counter]['msdeviation'] = bindec(substr($deviationbitstream, $parsedFrame['bitsforbytesdeviation'], $parsedFrame['bitsformsdeviation']));
824
+ $deviationbitstream = substr($deviationbitstream, $parsedFrame['bitsforbytesdeviation'] + $parsedFrame['bitsformsdeviation']);
825
+ $reference_counter++;
826
+ }
827
+ unset($parsedFrame['data']);
828
+
829
+
830
+ } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'SYTC')) || // 4.7 SYTC Synchronised tempo codes
831
+ (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'STC'))) { // 4.8 STC Synchronised tempo codes
832
+ // There may only be one 'SYTC' frame in each tag
833
+ // <Header for 'Synchronised tempo codes', ID: 'SYTC'>
834
+ // Time stamp format $xx
835
+ // Tempo data <binary data>
836
+ // Where time stamp format is:
837
+ // $01 (32-bit value) MPEG frames from beginning of file
838
+ // $02 (32-bit value) milliseconds from beginning of file
839
+
840
+ $frame_offset = 0;
841
+ $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
842
+ $timestamp_counter = 0;
843
+ while ($frame_offset < strlen($parsedFrame['data'])) {
844
+ $parsedFrame[$timestamp_counter]['tempo'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
845
+ if ($parsedFrame[$timestamp_counter]['tempo'] == 255) {
846
+ $parsedFrame[$timestamp_counter]['tempo'] += ord(substr($parsedFrame['data'], $frame_offset++, 1));
847
+ }
848
+ $parsedFrame[$timestamp_counter]['timestamp'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
849
+ $frame_offset += 4;
850
+ $timestamp_counter++;
851
+ }
852
+ unset($parsedFrame['data']);
853
+
854
+
855
+ } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'USLT')) || // 4.8 USLT Unsynchronised lyric/text transcription
856
+ (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'ULT'))) { // 4.9 ULT Unsynchronised lyric/text transcription
857
+ // There may be more than one 'Unsynchronised lyrics/text transcription' frame
858
+ // in each tag, but only one with the same language and content descriptor.
859
+ // <Header for 'Unsynchronised lyrics/text transcription', ID: 'USLT'>
860
+ // Text encoding $xx
861
+ // Language $xx xx xx
862
+ // Content descriptor <text string according to encoding> $00 (00)
863
+ // Lyrics/text <full text string according to encoding>
864
+
865
+ $frame_offset = 0;
866
+ $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
867
+ if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
868
+ $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
869
+ }
870
+ $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
871
+ $frame_offset += 3;
872
+ $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
873
+ if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
874
+ $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
875
+ }
876
+ $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
877
+ if (ord($frame_description) === 0) {
878
+ $frame_description = '';
879
+ }
880
+ $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
881
+
882
+ $parsedFrame['encodingid'] = $frame_textencoding;
883
+ $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
884
+
885
+ $parsedFrame['data'] = $parsedFrame['data'];
886
+ $parsedFrame['language'] = $frame_language;
887
+ $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
888
+ $parsedFrame['description'] = $frame_description;
889
+ if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
890
+ $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
891
+ }
892
+ unset($parsedFrame['data']);
893
+
894
+
895
+ } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'SYLT')) || // 4.9 SYLT Synchronised lyric/text
896
+ (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'SLT'))) { // 4.10 SLT Synchronised lyric/text
897
+ // There may be more than one 'SYLT' frame in each tag,
898
+ // but only one with the same language and content descriptor.
899
+ // <Header for 'Synchronised lyrics/text', ID: 'SYLT'>
900
+ // Text encoding $xx
901
+ // Language $xx xx xx
902
+ // Time stamp format $xx
903
+ // $01 (32-bit value) MPEG frames from beginning of file
904
+ // $02 (32-bit value) milliseconds from beginning of file
905
+ // Content type $xx
906
+ // Content descriptor <text string according to encoding> $00 (00)
907
+ // Terminated text to be synced (typically a syllable)
908
+ // Sync identifier (terminator to above string) $00 (00)
909
+ // Time stamp $xx (xx ...)
910
+
911
+ $frame_offset = 0;
912
+ $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
913
+ if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
914
+ $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
915
+ }
916
+ $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
917
+ $frame_offset += 3;
918
+ $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
919
+ $parsedFrame['contenttypeid'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
920
+ $parsedFrame['contenttype'] = $this->SYTLContentTypeLookup($parsedFrame['contenttypeid']);
921
+ $parsedFrame['encodingid'] = $frame_textencoding;
922
+ $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
923
+
924
+ $parsedFrame['language'] = $frame_language;
925
+ $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
926
+
927
+ $timestampindex = 0;
928
+ $frame_remainingdata = substr($parsedFrame['data'], $frame_offset);
929
+ while (strlen($frame_remainingdata)) {
930
+ $frame_offset = 0;
931
+ $frame_terminatorpos = strpos($frame_remainingdata, $this->TextEncodingTerminatorLookup($frame_textencoding));
932
+ if ($frame_terminatorpos === false) {
933
+ $frame_remainingdata = '';
934
+ } else {
935
+ if (ord(substr($frame_remainingdata, $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
936
+ $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
937
+ }
938
+ $parsedFrame['lyrics'][$timestampindex]['data'] = substr($frame_remainingdata, $frame_offset, $frame_terminatorpos - $frame_offset);
939
+
940
+ $frame_remainingdata = substr($frame_remainingdata, $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
941
+ if (($timestampindex == 0) && (ord($frame_remainingdata{0}) != 0)) {
942
+ // timestamp probably omitted for first data item
943
+ } else {
944
+ $parsedFrame['lyrics'][$timestampindex]['timestamp'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 0, 4));
945
+ $frame_remainingdata = substr($frame_remainingdata, 4);
946
+ }
947
+ $timestampindex++;
948
+ }
949
+ }
950
+ unset($parsedFrame['data']);
951
+
952
+
953
+ } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'COMM')) || // 4.10 COMM Comments
954
+ (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'COM'))) { // 4.11 COM Comments
955
+ // There may be more than one comment frame in each tag,
956
+ // but only one with the same language and content descriptor.
957
+ // <Header for 'Comment', ID: 'COMM'>
958
+ // Text encoding $xx
959
+ // Language $xx xx xx
960
+ // Short content descrip. <text string according to encoding> $00 (00)
961
+ // The actual text <full text string according to encoding>
962
+
963
+ if (strlen($parsedFrame['data']) < 5) {
964
+
965
+ $info['warning'][] = 'Invalid data (too short) for "'.$parsedFrame['frame_name'].'" frame at offset '.$parsedFrame['dataoffset'];
966
+
967
+ } else {
968
+
969
+ $frame_offset = 0;
970
+ $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
971
+ if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
972
+ $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
973
+ }
974
+ $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
975
+ $frame_offset += 3;
976
+ $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
977
+ if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
978
+ $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
979
+ }
980
+ $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
981
+ if (ord($frame_description) === 0) {
982
+ $frame_description = '';
983
+ }
984
+ $frame_text = (string) substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
985
+
986
+ $parsedFrame['encodingid'] = $frame_textencoding;
987
+ $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
988
+
989
+ $parsedFrame['language'] = $frame_language;
990
+ $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
991
+ $parsedFrame['description'] = $frame_description;
992
+ $parsedFrame['data'] = $frame_text;
993
+ if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
994
+ $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
995
+ }
996
+
997
+ }
998
+
999
+ } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'RVA2')) { // 4.11 RVA2 Relative volume adjustment (2) (ID3v2.4+ only)
1000
+ // There may be more than one 'RVA2' frame in each tag,
1001
+ // but only one with the same identification string
1002
+ // <Header for 'Relative volume adjustment (2)', ID: 'RVA2'>
1003
+ // Identification <text string> $00
1004
+ // The 'identification' string is used to identify the situation and/or
1005
+ // device where this adjustment should apply. The following is then
1006
+ // repeated for every channel:
1007
+ // Type of channel $xx
1008
+ // Volume adjustment $xx xx
1009
+ // Bits representing peak $xx
1010
+ // Peak volume $xx (xx ...)
1011
+
1012
+ $frame_terminatorpos = strpos($parsedFrame['data'], "\x00");
1013
+ $frame_idstring = substr($parsedFrame['data'], 0, $frame_terminatorpos);
1014
+ if (ord($frame_idstring) === 0) {
1015
+ $frame_idstring = '';
1016
+ }
1017
+ $frame_remainingdata = substr($parsedFrame['data'], $frame_terminatorpos + strlen("\x00"));
1018
+ $parsedFrame['description'] = $frame_idstring;
1019
+ $RVA2channelcounter = 0;
1020
+ while (strlen($frame_remainingdata) >= 5) {
1021
+ $frame_offset = 0;
1022
+ $frame_channeltypeid = ord(substr($frame_remainingdata, $frame_offset++, 1));
1023
+ $parsedFrame[$RVA2channelcounter]['channeltypeid'] = $frame_channeltypeid;
1024
+ $parsedFrame[$RVA2channelcounter]['channeltype'] = $this->RVA2ChannelTypeLookup($frame_channeltypeid);
1025
+ $parsedFrame[$RVA2channelcounter]['volumeadjust'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, 2), false, true); // 16-bit signed
1026
+ $frame_offset += 2;
1027
+ $parsedFrame[$RVA2channelcounter]['bitspeakvolume'] = ord(substr($frame_remainingdata, $frame_offset++, 1));
1028
+ if (($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] < 1) || ($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] > 4)) {
1029
+ $info['warning'][] = 'ID3v2::RVA2 frame['.$RVA2channelcounter.'] contains invalid '.$parsedFrame[$RVA2channelcounter]['bitspeakvolume'].'-byte bits-representing-peak value';
1030
+ break;
1031
+ }
1032
+ $frame_bytespeakvolume = ceil($parsedFrame[$RVA2channelcounter]['bitspeakvolume'] / 8);
1033
+ $parsedFrame[$RVA2channelcounter]['peakvolume'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, $frame_offset, $frame_bytespeakvolume));
1034
+ $frame_remainingdata = substr($frame_remainingdata, $frame_offset + $frame_bytespeakvolume);
1035
+ $RVA2channelcounter++;
1036
+ }
1037
+ unset($parsedFrame['data']);
1038
+
1039
+
1040
+ } elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'RVAD')) || // 4.12 RVAD Relative volume adjustment (ID3v2.3 only)
1041
+ (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'RVA'))) { // 4.12 RVA Relative volume adjustment (ID3v2.2 only)
1042
+ // There may only be one 'RVA' frame in each tag
1043
+ // <Header for 'Relative volume adjustment', ID: 'RVA'>
1044
+ // ID3v2.2 => Increment/decrement %000000ba
1045
+ // ID3v2.3 => Increment/decrement %00fedcba
1046
+ // Bits used for volume descr. $xx
1047
+ // Relative volume change, right $xx xx (xx ...) // a
1048
+ // Relative volume change, left $xx xx (xx ...) // b
1049
+ // Peak volume right $xx xx (xx ...)
1050
+ // Peak volume left $xx xx (xx ...)
1051
+ // ID3v2.3 only, optional (not present in ID3v2.2):
1052
+ // Relative volume change, right back $xx xx (xx ...) // c
1053
+ // Relative volume change, left back $xx xx (xx ...) // d
1054
+ // Peak volume right back $xx xx (xx ...)
1055
+ // Peak volume left back $xx xx (xx ...)
1056
+ // ID3v2.3 only, optional (not present in ID3v2.2):
1057
+ // Relative volume change, center $xx xx (xx ...) // e
1058
+ // Peak volume center $xx xx (xx ...)
1059
+ // ID3v2.3 only, optional (not present in ID3v2.2):
1060
+ // Relative volume change, bass $xx xx (xx ...) // f
1061
+ // Peak volume bass $xx xx (xx ...)
1062
+
1063
+ $frame_offset = 0;
1064
+ $frame_incrdecrflags = getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1));
1065
+ $parsedFrame['incdec']['right'] = (bool) substr($frame_incrdecrflags, 6, 1);
1066
+ $parsedFrame['incdec']['left'] = (bool) substr($frame_incrdecrflags, 7, 1);
1067
+ $parsedFrame['bitsvolume'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1068
+ $frame_bytesvolume = ceil($parsedFrame['bitsvolume'] / 8);
1069
+ $parsedFrame['volumechange']['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1070
+ if ($parsedFrame['incdec']['right'] === false) {
1071
+ $parsedFrame['volumechange']['right'] *= -1;
1072
+ }
1073
+ $frame_offset += $frame_bytesvolume;
1074
+ $parsedFrame['volumechange']['left'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1075
+ if ($parsedFrame['incdec']['left'] === false) {
1076
+ $parsedFrame['volumechange']['left'] *= -1;
1077
+ }
1078
+ $frame_offset += $frame_bytesvolume;
1079
+ $parsedFrame['peakvolume']['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1080
+ $frame_offset += $frame_bytesvolume;
1081
+ $parsedFrame['peakvolume']['left'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1082
+ $frame_offset += $frame_bytesvolume;
1083
+ if ($id3v2_majorversion == 3) {
1084
+ $parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
1085
+ if (strlen($parsedFrame['data']) > 0) {
1086
+ $parsedFrame['incdec']['rightrear'] = (bool) substr($frame_incrdecrflags, 4, 1);
1087
+ $parsedFrame['incdec']['leftrear'] = (bool) substr($frame_incrdecrflags, 5, 1);
1088
+ $parsedFrame['volumechange']['rightrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1089
+ if ($parsedFrame['incdec']['rightrear'] === false) {
1090
+ $parsedFrame['volumechange']['rightrear'] *= -1;
1091
+ }
1092
+ $frame_offset += $frame_bytesvolume;
1093
+ $parsedFrame['volumechange']['leftrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1094
+ if ($parsedFrame['incdec']['leftrear'] === false) {
1095
+ $parsedFrame['volumechange']['leftrear'] *= -1;
1096
+ }
1097
+ $frame_offset += $frame_bytesvolume;
1098
+ $parsedFrame['peakvolume']['rightrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1099
+ $frame_offset += $frame_bytesvolume;
1100
+ $parsedFrame['peakvolume']['leftrear'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1101
+ $frame_offset += $frame_bytesvolume;
1102
+ }
1103
+ $parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
1104
+ if (strlen($parsedFrame['data']) > 0) {
1105
+ $parsedFrame['incdec']['center'] = (bool) substr($frame_incrdecrflags, 3, 1);
1106
+ $parsedFrame['volumechange']['center'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1107
+ if ($parsedFrame['incdec']['center'] === false) {
1108
+ $parsedFrame['volumechange']['center'] *= -1;
1109
+ }
1110
+ $frame_offset += $frame_bytesvolume;
1111
+ $parsedFrame['peakvolume']['center'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1112
+ $frame_offset += $frame_bytesvolume;
1113
+ }
1114
+ $parsedFrame['data'] = substr($parsedFrame['data'], $frame_offset);
1115
+ if (strlen($parsedFrame['data']) > 0) {
1116
+ $parsedFrame['incdec']['bass'] = (bool) substr($frame_incrdecrflags, 2, 1);
1117
+ $parsedFrame['volumechange']['bass'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1118
+ if ($parsedFrame['incdec']['bass'] === false) {
1119
+ $parsedFrame['volumechange']['bass'] *= -1;
1120
+ }
1121
+ $frame_offset += $frame_bytesvolume;
1122
+ $parsedFrame['peakvolume']['bass'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesvolume));
1123
+ $frame_offset += $frame_bytesvolume;
1124
+ }
1125
+ }
1126
+ unset($parsedFrame['data']);
1127
+
1128
+
1129
+ } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'EQU2')) { // 4.12 EQU2 Equalisation (2) (ID3v2.4+ only)
1130
+ // There may be more than one 'EQU2' frame in each tag,
1131
+ // but only one with the same identification string
1132
+ // <Header of 'Equalisation (2)', ID: 'EQU2'>
1133
+ // Interpolation method $xx
1134
+ // $00 Band
1135
+ // $01 Linear
1136
+ // Identification <text string> $00
1137
+ // The following is then repeated for every adjustment point
1138
+ // Frequency $xx xx
1139
+ // Volume adjustment $xx xx
1140
+
1141
+ $frame_offset = 0;
1142
+ $frame_interpolationmethod = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1143
+ $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1144
+ $frame_idstring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1145
+ if (ord($frame_idstring) === 0) {
1146
+ $frame_idstring = '';
1147
+ }
1148
+ $parsedFrame['description'] = $frame_idstring;
1149
+ $frame_remainingdata = substr($parsedFrame['data'], $frame_terminatorpos + strlen("\x00"));
1150
+ while (strlen($frame_remainingdata)) {
1151
+ $frame_frequency = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 0, 2)) / 2;
1152
+ $parsedFrame['data'][$frame_frequency] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 2, 2), false, true);
1153
+ $frame_remainingdata = substr($frame_remainingdata, 4);
1154
+ }
1155
+ $parsedFrame['interpolationmethod'] = $frame_interpolationmethod;
1156
+ unset($parsedFrame['data']);
1157
+
1158
+
1159
+ } elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'EQUA')) || // 4.12 EQUA Equalisation (ID3v2.3 only)
1160
+ (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'EQU'))) { // 4.13 EQU Equalisation (ID3v2.2 only)
1161
+ // There may only be one 'EQUA' frame in each tag
1162
+ // <Header for 'Relative volume adjustment', ID: 'EQU'>
1163
+ // Adjustment bits $xx
1164
+ // This is followed by 2 bytes + ('adjustment bits' rounded up to the
1165
+ // nearest byte) for every equalisation band in the following format,
1166
+ // giving a frequency range of 0 - 32767Hz:
1167
+ // Increment/decrement %x (MSB of the Frequency)
1168
+ // Frequency (lower 15 bits)
1169
+ // Adjustment $xx (xx ...)
1170
+
1171
+ $frame_offset = 0;
1172
+ $parsedFrame['adjustmentbits'] = substr($parsedFrame['data'], $frame_offset++, 1);
1173
+ $frame_adjustmentbytes = ceil($parsedFrame['adjustmentbits'] / 8);
1174
+
1175
+ $frame_remainingdata = (string) substr($parsedFrame['data'], $frame_offset);
1176
+ while (strlen($frame_remainingdata) > 0) {
1177
+ $frame_frequencystr = getid3_lib::BigEndian2Bin(substr($frame_remainingdata, 0, 2));
1178
+ $frame_incdec = (bool) substr($frame_frequencystr, 0, 1);
1179
+ $frame_frequency = bindec(substr($frame_frequencystr, 1, 15));
1180
+ $parsedFrame[$frame_frequency]['incdec'] = $frame_incdec;
1181
+ $parsedFrame[$frame_frequency]['adjustment'] = getid3_lib::BigEndian2Int(substr($frame_remainingdata, 2, $frame_adjustmentbytes));
1182
+ if ($parsedFrame[$frame_frequency]['incdec'] === false) {
1183
+ $parsedFrame[$frame_frequency]['adjustment'] *= -1;
1184
+ }
1185
+ $frame_remainingdata = substr($frame_remainingdata, 2 + $frame_adjustmentbytes);
1186
+ }
1187
+ unset($parsedFrame['data']);
1188
+
1189
+
1190
+ } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RVRB')) || // 4.13 RVRB Reverb
1191
+ (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'REV'))) { // 4.14 REV Reverb
1192
+ // There may only be one 'RVRB' frame in each tag.
1193
+ // <Header for 'Reverb', ID: 'RVRB'>
1194
+ // Reverb left (ms) $xx xx
1195
+ // Reverb right (ms) $xx xx
1196
+ // Reverb bounces, left $xx
1197
+ // Reverb bounces, right $xx
1198
+ // Reverb feedback, left to left $xx
1199
+ // Reverb feedback, left to right $xx
1200
+ // Reverb feedback, right to right $xx
1201
+ // Reverb feedback, right to left $xx
1202
+ // Premix left to right $xx
1203
+ // Premix right to left $xx
1204
+
1205
+ $frame_offset = 0;
1206
+ $parsedFrame['left'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1207
+ $frame_offset += 2;
1208
+ $parsedFrame['right'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1209
+ $frame_offset += 2;
1210
+ $parsedFrame['bouncesL'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1211
+ $parsedFrame['bouncesR'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1212
+ $parsedFrame['feedbackLL'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1213
+ $parsedFrame['feedbackLR'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1214
+ $parsedFrame['feedbackRR'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1215
+ $parsedFrame['feedbackRL'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1216
+ $parsedFrame['premixLR'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1217
+ $parsedFrame['premixRL'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1218
+ unset($parsedFrame['data']);
1219
+
1220
+
1221
+ } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'APIC')) || // 4.14 APIC Attached picture
1222
+ (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'PIC'))) { // 4.15 PIC Attached picture
1223
+ // There may be several pictures attached to one file,
1224
+ // each in their individual 'APIC' frame, but only one
1225
+ // with the same content descriptor
1226
+ // <Header for 'Attached picture', ID: 'APIC'>
1227
+ // Text encoding $xx
1228
+ // ID3v2.3+ => MIME type <text string> $00
1229
+ // ID3v2.2 => Image format $xx xx xx
1230
+ // Picture type $xx
1231
+ // Description <text string according to encoding> $00 (00)
1232
+ // Picture data <binary data>
1233
+
1234
+ $frame_offset = 0;
1235
+ $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1236
+ if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1237
+ $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
1238
+ }
1239
+
1240
+ if ($id3v2_majorversion == 2 && strlen($parsedFrame['data']) > $frame_offset) {
1241
+ $frame_imagetype = substr($parsedFrame['data'], $frame_offset, 3);
1242
+ if (strtolower($frame_imagetype) == 'ima') {
1243
+ // complete hack for mp3Rage (www.chaoticsoftware.com) that puts ID3v2.3-formatted
1244
+ // MIME type instead of 3-char ID3v2.2-format image type (thanks xbhoff�pacbell*net)
1245
+ $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1246
+ $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1247
+ if (ord($frame_mimetype) === 0) {
1248
+ $frame_mimetype = '';
1249
+ }
1250
+ $frame_imagetype = strtoupper(str_replace('image/', '', strtolower($frame_mimetype)));
1251
+ if ($frame_imagetype == 'JPEG') {
1252
+ $frame_imagetype = 'JPG';
1253
+ }
1254
+ $frame_offset = $frame_terminatorpos + strlen("\x00");
1255
+ } else {
1256
+ $frame_offset += 3;
1257
+ }
1258
+ }
1259
+ if ($id3v2_majorversion > 2 && strlen($parsedFrame['data']) > $frame_offset) {
1260
+ $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1261
+ $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1262
+ if (ord($frame_mimetype) === 0) {
1263
+ $frame_mimetype = '';
1264
+ }
1265
+ $frame_offset = $frame_terminatorpos + strlen("\x00");
1266
+ }
1267
+
1268
+ $frame_picturetype = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1269
+
1270
+ if ($frame_offset >= $parsedFrame['datalength']) {
1271
+ $info['warning'][] = 'data portion of APIC frame is missing at offset '.($parsedFrame['dataoffset'] + 8 + $frame_offset);
1272
+ } else {
1273
+ $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
1274
+ if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
1275
+ $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1276
+ }
1277
+ $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1278
+ if (ord($frame_description) === 0) {
1279
+ $frame_description = '';
1280
+ }
1281
+ $parsedFrame['encodingid'] = $frame_textencoding;
1282
+ $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1283
+
1284
+ if ($id3v2_majorversion == 2) {
1285
+ $parsedFrame['imagetype'] = $frame_imagetype;
1286
+ } else {
1287
+ $parsedFrame['mime'] = $frame_mimetype;
1288
+ }
1289
+ $parsedFrame['picturetypeid'] = $frame_picturetype;
1290
+ $parsedFrame['picturetype'] = $this->APICPictureTypeLookup($frame_picturetype);
1291
+ $parsedFrame['description'] = $frame_description;
1292
+ $parsedFrame['data'] = substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)));
1293
+ $parsedFrame['datalength'] = strlen($parsedFrame['data']);
1294
+
1295
+ $parsedFrame['image_mime'] = '';
1296
+ $imageinfo = array();
1297
+ $imagechunkcheck = getid3_lib::GetDataImageSize($parsedFrame['data'], $imageinfo);
1298
+ if (($imagechunkcheck[2] >= 1) && ($imagechunkcheck[2] <= 3)) {
1299
+ $parsedFrame['image_mime'] = 'image/'.getid3_lib::ImageTypesLookup($imagechunkcheck[2]);
1300
+ if ($imagechunkcheck[0]) {
1301
+ $parsedFrame['image_width'] = $imagechunkcheck[0];
1302
+ }
1303
+ if ($imagechunkcheck[1]) {
1304
+ $parsedFrame['image_height'] = $imagechunkcheck[1];
1305
+ }
1306
+ }
1307
+
1308
+ do {
1309
+ if ($this->inline_attachments === false) {
1310
+ // skip entirely
1311
+ unset($parsedFrame['data']);
1312
+ break;
1313
+ }
1314
+ if ($this->inline_attachments === true) {
1315
+ // great
1316
+ } elseif (is_int($this->inline_attachments)) {
1317
+ if ($this->inline_attachments < $parsedFrame['data_length']) {
1318
+ // too big, skip
1319
+ $info['warning'][] = 'attachment at '.$frame_offset.' is too large to process inline ('.number_format($parsedFrame['data_length']).' bytes)';
1320
+ unset($parsedFrame['data']);
1321
+ break;
1322
+ }
1323
+ } elseif (is_string($this->inline_attachments)) {
1324
+ $this->inline_attachments = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->inline_attachments), DIRECTORY_SEPARATOR);
1325
+ if (!is_dir($this->inline_attachments) || !is_writable($this->inline_attachments)) {
1326
+ // cannot write, skip
1327
+ $info['warning'][] = 'attachment at '.$frame_offset.' cannot be saved to "'.$this->inline_attachments.'" (not writable)';
1328
+ unset($parsedFrame['data']);
1329
+ break;
1330
+ }
1331
+ }
1332
+ // if we get this far, must be OK
1333
+ if (is_string($this->inline_attachments)) {
1334
+ $destination_filename = $this->inline_attachments.DIRECTORY_SEPARATOR.md5($info['filenamepath']).'_'.$frame_offset;
1335
+ if (!file_exists($destination_filename) || is_writable($destination_filename)) {
1336
+ file_put_contents($destination_filename, $parsedFrame['data']);
1337
+ } else {
1338
+ $info['warning'][] = 'attachment at '.$frame_offset.' cannot be saved to "'.$destination_filename.'" (not writable)';
1339
+ }
1340
+ $parsedFrame['data_filename'] = $destination_filename;
1341
+ unset($parsedFrame['data']);
1342
+ } else {
1343
+ if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
1344
+ if (!isset($info['id3v2']['comments']['picture'])) {
1345
+ $info['id3v2']['comments']['picture'] = array();
1346
+ }
1347
+ $info['id3v2']['comments']['picture'][] = array('data'=>$parsedFrame['data'], 'image_mime'=>$parsedFrame['image_mime']);
1348
+ }
1349
+ }
1350
+ } while (false);
1351
+ }
1352
+
1353
+ } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'GEOB')) || // 4.15 GEOB General encapsulated object
1354
+ (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'GEO'))) { // 4.16 GEO General encapsulated object
1355
+ // There may be more than one 'GEOB' frame in each tag,
1356
+ // but only one with the same content descriptor
1357
+ // <Header for 'General encapsulated object', ID: 'GEOB'>
1358
+ // Text encoding $xx
1359
+ // MIME type <text string> $00
1360
+ // Filename <text string according to encoding> $00 (00)
1361
+ // Content description <text string according to encoding> $00 (00)
1362
+ // Encapsulated object <binary data>
1363
+
1364
+ $frame_offset = 0;
1365
+ $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1366
+ if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1367
+ $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
1368
+ }
1369
+ $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1370
+ $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1371
+ if (ord($frame_mimetype) === 0) {
1372
+ $frame_mimetype = '';
1373
+ }
1374
+ $frame_offset = $frame_terminatorpos + strlen("\x00");
1375
+
1376
+ $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
1377
+ if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
1378
+ $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1379
+ }
1380
+ $frame_filename = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1381
+ if (ord($frame_filename) === 0) {
1382
+ $frame_filename = '';
1383
+ }
1384
+ $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding));
1385
+
1386
+ $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
1387
+ if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
1388
+ $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1389
+ }
1390
+ $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1391
+ if (ord($frame_description) === 0) {
1392
+ $frame_description = '';
1393
+ }
1394
+ $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding));
1395
+
1396
+ $parsedFrame['objectdata'] = (string) substr($parsedFrame['data'], $frame_offset);
1397
+ $parsedFrame['encodingid'] = $frame_textencoding;
1398
+ $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1399
+
1400
+ $parsedFrame['mime'] = $frame_mimetype;
1401
+ $parsedFrame['filename'] = $frame_filename;
1402
+ $parsedFrame['description'] = $frame_description;
1403
+ unset($parsedFrame['data']);
1404
+
1405
+
1406
+ } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'PCNT')) || // 4.16 PCNT Play counter
1407
+ (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CNT'))) { // 4.17 CNT Play counter
1408
+ // There may only be one 'PCNT' frame in each tag.
1409
+ // When the counter reaches all one's, one byte is inserted in
1410
+ // front of the counter thus making the counter eight bits bigger
1411
+ // <Header for 'Play counter', ID: 'PCNT'>
1412
+ // Counter $xx xx xx xx (xx ...)
1413
+
1414
+ $parsedFrame['data'] = getid3_lib::BigEndian2Int($parsedFrame['data']);
1415
+
1416
+
1417
+ } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'POPM')) || // 4.17 POPM Popularimeter
1418
+ (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'POP'))) { // 4.18 POP Popularimeter
1419
+ // There may be more than one 'POPM' frame in each tag,
1420
+ // but only one with the same email address
1421
+ // <Header for 'Popularimeter', ID: 'POPM'>
1422
+ // Email to user <text string> $00
1423
+ // Rating $xx
1424
+ // Counter $xx xx xx xx (xx ...)
1425
+
1426
+ $frame_offset = 0;
1427
+ $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1428
+ $frame_emailaddress = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1429
+ if (ord($frame_emailaddress) === 0) {
1430
+ $frame_emailaddress = '';
1431
+ }
1432
+ $frame_offset = $frame_terminatorpos + strlen("\x00");
1433
+ $frame_rating = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1434
+ $parsedFrame['counter'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset));
1435
+ $parsedFrame['email'] = $frame_emailaddress;
1436
+ $parsedFrame['rating'] = $frame_rating;
1437
+ unset($parsedFrame['data']);
1438
+
1439
+
1440
+ } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RBUF')) || // 4.18 RBUF Recommended buffer size
1441
+ (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'BUF'))) { // 4.19 BUF Recommended buffer size
1442
+ // There may only be one 'RBUF' frame in each tag
1443
+ // <Header for 'Recommended buffer size', ID: 'RBUF'>
1444
+ // Buffer size $xx xx xx
1445
+ // Embedded info flag %0000000x
1446
+ // Offset to next tag $xx xx xx xx
1447
+
1448
+ $frame_offset = 0;
1449
+ $parsedFrame['buffersize'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 3));
1450
+ $frame_offset += 3;
1451
+
1452
+ $frame_embeddedinfoflags = getid3_lib::BigEndian2Bin(substr($parsedFrame['data'], $frame_offset++, 1));
1453
+ $parsedFrame['flags']['embededinfo'] = (bool) substr($frame_embeddedinfoflags, 7, 1);
1454
+ $parsedFrame['nexttagoffset'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1455
+ unset($parsedFrame['data']);
1456
+
1457
+
1458
+ } elseif (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CRM')) { // 4.20 Encrypted meta frame (ID3v2.2 only)
1459
+ // There may be more than one 'CRM' frame in a tag,
1460
+ // but only one with the same 'owner identifier'
1461
+ // <Header for 'Encrypted meta frame', ID: 'CRM'>
1462
+ // Owner identifier <textstring> $00 (00)
1463
+ // Content/explanation <textstring> $00 (00)
1464
+ // Encrypted datablock <binary data>
1465
+
1466
+ $frame_offset = 0;
1467
+ $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1468
+ $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1469
+ $frame_offset = $frame_terminatorpos + strlen("\x00");
1470
+
1471
+ $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1472
+ $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1473
+ if (ord($frame_description) === 0) {
1474
+ $frame_description = '';
1475
+ }
1476
+ $frame_offset = $frame_terminatorpos + strlen("\x00");
1477
+
1478
+ $parsedFrame['ownerid'] = $frame_ownerid;
1479
+ $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
1480
+ $parsedFrame['description'] = $frame_description;
1481
+ unset($parsedFrame['data']);
1482
+
1483
+
1484
+ } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'AENC')) || // 4.19 AENC Audio encryption
1485
+ (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'CRA'))) { // 4.21 CRA Audio encryption
1486
+ // There may be more than one 'AENC' frames in a tag,
1487
+ // but only one with the same 'Owner identifier'
1488
+ // <Header for 'Audio encryption', ID: 'AENC'>
1489
+ // Owner identifier <text string> $00
1490
+ // Preview start $xx xx
1491
+ // Preview length $xx xx
1492
+ // Encryption info <binary data>
1493
+
1494
+ $frame_offset = 0;
1495
+ $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1496
+ $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1497
+ if (ord($frame_ownerid) === 0) {
1498
+ $frame_ownerid == '';
1499
+ }
1500
+ $frame_offset = $frame_terminatorpos + strlen("\x00");
1501
+ $parsedFrame['ownerid'] = $frame_ownerid;
1502
+ $parsedFrame['previewstart'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1503
+ $frame_offset += 2;
1504
+ $parsedFrame['previewlength'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1505
+ $frame_offset += 2;
1506
+ $parsedFrame['encryptioninfo'] = (string) substr($parsedFrame['data'], $frame_offset);
1507
+ unset($parsedFrame['data']);
1508
+
1509
+
1510
+ } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'LINK')) || // 4.20 LINK Linked information
1511
+ (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'LNK'))) { // 4.22 LNK Linked information
1512
+ // There may be more than one 'LINK' frame in a tag,
1513
+ // but only one with the same contents
1514
+ // <Header for 'Linked information', ID: 'LINK'>
1515
+ // ID3v2.3+ => Frame identifier $xx xx xx xx
1516
+ // ID3v2.2 => Frame identifier $xx xx xx
1517
+ // URL <text string> $00
1518
+ // ID and additional data <text string(s)>
1519
+
1520
+ $frame_offset = 0;
1521
+ if ($id3v2_majorversion == 2) {
1522
+ $parsedFrame['frameid'] = substr($parsedFrame['data'], $frame_offset, 3);
1523
+ $frame_offset += 3;
1524
+ } else {
1525
+ $parsedFrame['frameid'] = substr($parsedFrame['data'], $frame_offset, 4);
1526
+ $frame_offset += 4;
1527
+ }
1528
+
1529
+ $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1530
+ $frame_url = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1531
+ if (ord($frame_url) === 0) {
1532
+ $frame_url = '';
1533
+ }
1534
+ $frame_offset = $frame_terminatorpos + strlen("\x00");
1535
+ $parsedFrame['url'] = $frame_url;
1536
+
1537
+ $parsedFrame['additionaldata'] = (string) substr($parsedFrame['data'], $frame_offset);
1538
+ if (!empty($parsedFrame['framenameshort']) && $parsedFrame['url']) {
1539
+ $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = utf8_encode($parsedFrame['url']);
1540
+ }
1541
+ unset($parsedFrame['data']);
1542
+
1543
+
1544
+ } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'POSS')) { // 4.21 POSS Position synchronisation frame (ID3v2.3+ only)
1545
+ // There may only be one 'POSS' frame in each tag
1546
+ // <Head for 'Position synchronisation', ID: 'POSS'>
1547
+ // Time stamp format $xx
1548
+ // Position $xx (xx ...)
1549
+
1550
+ $frame_offset = 0;
1551
+ $parsedFrame['timestampformat'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1552
+ $parsedFrame['position'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset));
1553
+ unset($parsedFrame['data']);
1554
+
1555
+
1556
+ } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'USER')) { // 4.22 USER Terms of use (ID3v2.3+ only)
1557
+ // There may be more than one 'Terms of use' frame in a tag,
1558
+ // but only one with the same 'Language'
1559
+ // <Header for 'Terms of use frame', ID: 'USER'>
1560
+ // Text encoding $xx
1561
+ // Language $xx xx xx
1562
+ // The actual text <text string according to encoding>
1563
+
1564
+ $frame_offset = 0;
1565
+ $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1566
+ if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1567
+ $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
1568
+ }
1569
+ $frame_language = substr($parsedFrame['data'], $frame_offset, 3);
1570
+ $frame_offset += 3;
1571
+ $parsedFrame['language'] = $frame_language;
1572
+ $parsedFrame['languagename'] = $this->LanguageLookup($frame_language, false);
1573
+ $parsedFrame['encodingid'] = $frame_textencoding;
1574
+ $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1575
+
1576
+ $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
1577
+ if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
1578
+ $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $parsedFrame['data']);
1579
+ }
1580
+ unset($parsedFrame['data']);
1581
+
1582
+
1583
+ } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'OWNE')) { // 4.23 OWNE Ownership frame (ID3v2.3+ only)
1584
+ // There may only be one 'OWNE' frame in a tag
1585
+ // <Header for 'Ownership frame', ID: 'OWNE'>
1586
+ // Text encoding $xx
1587
+ // Price paid <text string> $00
1588
+ // Date of purch. <text string>
1589
+ // Seller <text string according to encoding>
1590
+
1591
+ $frame_offset = 0;
1592
+ $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1593
+ if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1594
+ $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
1595
+ }
1596
+ $parsedFrame['encodingid'] = $frame_textencoding;
1597
+ $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1598
+
1599
+ $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1600
+ $frame_pricepaid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1601
+ $frame_offset = $frame_terminatorpos + strlen("\x00");
1602
+
1603
+ $parsedFrame['pricepaid']['currencyid'] = substr($frame_pricepaid, 0, 3);
1604
+ $parsedFrame['pricepaid']['currency'] = $this->LookupCurrencyUnits($parsedFrame['pricepaid']['currencyid']);
1605
+ $parsedFrame['pricepaid']['value'] = substr($frame_pricepaid, 3);
1606
+
1607
+ $parsedFrame['purchasedate'] = substr($parsedFrame['data'], $frame_offset, 8);
1608
+ if (!$this->IsValidDateStampString($parsedFrame['purchasedate'])) {
1609
+ $parsedFrame['purchasedateunix'] = mktime (0, 0, 0, substr($parsedFrame['purchasedate'], 4, 2), substr($parsedFrame['purchasedate'], 6, 2), substr($parsedFrame['purchasedate'], 0, 4));
1610
+ }
1611
+ $frame_offset += 8;
1612
+
1613
+ $parsedFrame['seller'] = (string) substr($parsedFrame['data'], $frame_offset);
1614
+ unset($parsedFrame['data']);
1615
+
1616
+
1617
+ } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'COMR')) { // 4.24 COMR Commercial frame (ID3v2.3+ only)
1618
+ // There may be more than one 'commercial frame' in a tag,
1619
+ // but no two may be identical
1620
+ // <Header for 'Commercial frame', ID: 'COMR'>
1621
+ // Text encoding $xx
1622
+ // Price string <text string> $00
1623
+ // Valid until <text string>
1624
+ // Contact URL <text string> $00
1625
+ // Received as $xx
1626
+ // Name of seller <text string according to encoding> $00 (00)
1627
+ // Description <text string according to encoding> $00 (00)
1628
+ // Picture MIME type <string> $00
1629
+ // Seller logo <binary data>
1630
+
1631
+ $frame_offset = 0;
1632
+ $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1633
+ if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
1634
+ $info['warning'][] = 'Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding';
1635
+ }
1636
+
1637
+ $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1638
+ $frame_pricestring = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1639
+ $frame_offset = $frame_terminatorpos + strlen("\x00");
1640
+ $frame_rawpricearray = explode('/', $frame_pricestring);
1641
+ foreach ($frame_rawpricearray as $key => $val) {
1642
+ $frame_currencyid = substr($val, 0, 3);
1643
+ $parsedFrame['price'][$frame_currencyid]['currency'] = $this->LookupCurrencyUnits($frame_currencyid);
1644
+ $parsedFrame['price'][$frame_currencyid]['value'] = substr($val, 3);
1645
+ }
1646
+
1647
+ $frame_datestring = substr($parsedFrame['data'], $frame_offset, 8);
1648
+ $frame_offset += 8;
1649
+
1650
+ $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1651
+ $frame_contacturl = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1652
+ $frame_offset = $frame_terminatorpos + strlen("\x00");
1653
+
1654
+ $frame_receivedasid = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1655
+
1656
+ $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
1657
+ if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
1658
+ $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1659
+ }
1660
+ $frame_sellername = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1661
+ if (ord($frame_sellername) === 0) {
1662
+ $frame_sellername = '';
1663
+ }
1664
+ $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding));
1665
+
1666
+ $frame_terminatorpos = strpos($parsedFrame['data'], $this->TextEncodingTerminatorLookup($frame_textencoding), $frame_offset);
1667
+ if (ord(substr($parsedFrame['data'], $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding)), 1)) === 0) {
1668
+ $frame_terminatorpos++; // strpos() fooled because 2nd byte of Unicode chars are often 0x00
1669
+ }
1670
+ $frame_description = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1671
+ if (ord($frame_description) === 0) {
1672
+ $frame_description = '';
1673
+ }
1674
+ $frame_offset = $frame_terminatorpos + strlen($this->TextEncodingTerminatorLookup($frame_textencoding));
1675
+
1676
+ $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1677
+ $frame_mimetype = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1678
+ $frame_offset = $frame_terminatorpos + strlen("\x00");
1679
+
1680
+ $frame_sellerlogo = substr($parsedFrame['data'], $frame_offset);
1681
+
1682
+ $parsedFrame['encodingid'] = $frame_textencoding;
1683
+ $parsedFrame['encoding'] = $this->TextEncodingNameLookup($frame_textencoding);
1684
+
1685
+ $parsedFrame['pricevaliduntil'] = $frame_datestring;
1686
+ $parsedFrame['contacturl'] = $frame_contacturl;
1687
+ $parsedFrame['receivedasid'] = $frame_receivedasid;
1688
+ $parsedFrame['receivedas'] = $this->COMRReceivedAsLookup($frame_receivedasid);
1689
+ $parsedFrame['sellername'] = $frame_sellername;
1690
+ $parsedFrame['description'] = $frame_description;
1691
+ $parsedFrame['mime'] = $frame_mimetype;
1692
+ $parsedFrame['logo'] = $frame_sellerlogo;
1693
+ unset($parsedFrame['data']);
1694
+
1695
+
1696
+ } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'ENCR')) { // 4.25 ENCR Encryption method registration (ID3v2.3+ only)
1697
+ // There may be several 'ENCR' frames in a tag,
1698
+ // but only one containing the same symbol
1699
+ // and only one containing the same owner identifier
1700
+ // <Header for 'Encryption method registration', ID: 'ENCR'>
1701
+ // Owner identifier <text string> $00
1702
+ // Method symbol $xx
1703
+ // Encryption data <binary data>
1704
+
1705
+ $frame_offset = 0;
1706
+ $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1707
+ $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1708
+ if (ord($frame_ownerid) === 0) {
1709
+ $frame_ownerid = '';
1710
+ }
1711
+ $frame_offset = $frame_terminatorpos + strlen("\x00");
1712
+
1713
+ $parsedFrame['ownerid'] = $frame_ownerid;
1714
+ $parsedFrame['methodsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1715
+ $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
1716
+
1717
+
1718
+ } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'GRID')) { // 4.26 GRID Group identification registration (ID3v2.3+ only)
1719
+
1720
+ // There may be several 'GRID' frames in a tag,
1721
+ // but only one containing the same symbol
1722
+ // and only one containing the same owner identifier
1723
+ // <Header for 'Group ID registration', ID: 'GRID'>
1724
+ // Owner identifier <text string> $00
1725
+ // Group symbol $xx
1726
+ // Group dependent data <binary data>
1727
+
1728
+ $frame_offset = 0;
1729
+ $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1730
+ $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1731
+ if (ord($frame_ownerid) === 0) {
1732
+ $frame_ownerid = '';
1733
+ }
1734
+ $frame_offset = $frame_terminatorpos + strlen("\x00");
1735
+
1736
+ $parsedFrame['ownerid'] = $frame_ownerid;
1737
+ $parsedFrame['groupsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1738
+ $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
1739
+
1740
+
1741
+ } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'PRIV')) { // 4.27 PRIV Private frame (ID3v2.3+ only)
1742
+ // The tag may contain more than one 'PRIV' frame
1743
+ // but only with different contents
1744
+ // <Header for 'Private frame', ID: 'PRIV'>
1745
+ // Owner identifier <text string> $00
1746
+ // The private data <binary data>
1747
+
1748
+ $frame_offset = 0;
1749
+ $frame_terminatorpos = strpos($parsedFrame['data'], "\x00", $frame_offset);
1750
+ $frame_ownerid = substr($parsedFrame['data'], $frame_offset, $frame_terminatorpos - $frame_offset);
1751
+ if (ord($frame_ownerid) === 0) {
1752
+ $frame_ownerid = '';
1753
+ }
1754
+ $frame_offset = $frame_terminatorpos + strlen("\x00");
1755
+
1756
+ $parsedFrame['ownerid'] = $frame_ownerid;
1757
+ $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
1758
+
1759
+
1760
+ } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'SIGN')) { // 4.28 SIGN Signature frame (ID3v2.4+ only)
1761
+ // There may be more than one 'signature frame' in a tag,
1762
+ // but no two may be identical
1763
+ // <Header for 'Signature frame', ID: 'SIGN'>
1764
+ // Group symbol $xx
1765
+ // Signature <binary data>
1766
+
1767
+ $frame_offset = 0;
1768
+ $parsedFrame['groupsymbol'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1769
+ $parsedFrame['data'] = (string) substr($parsedFrame['data'], $frame_offset);
1770
+
1771
+
1772
+ } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'SEEK')) { // 4.29 SEEK Seek frame (ID3v2.4+ only)
1773
+ // There may only be one 'seek frame' in a tag
1774
+ // <Header for 'Seek frame', ID: 'SEEK'>
1775
+ // Minimum offset to next tag $xx xx xx xx
1776
+
1777
+ $frame_offset = 0;
1778
+ $parsedFrame['data'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1779
+
1780
+
1781
+ } elseif (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'ASPI')) { // 4.30 ASPI Audio seek point index (ID3v2.4+ only)
1782
+ // There may only be one 'audio seek point index' frame in a tag
1783
+ // <Header for 'Seek Point Index', ID: 'ASPI'>
1784
+ // Indexed data start (S) $xx xx xx xx
1785
+ // Indexed data length (L) $xx xx xx xx
1786
+ // Number of index points (N) $xx xx
1787
+ // Bits per index point (b) $xx
1788
+ // Then for every index point the following data is included:
1789
+ // Fraction at index (Fi) $xx (xx)
1790
+
1791
+ $frame_offset = 0;
1792
+ $parsedFrame['datastart'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1793
+ $frame_offset += 4;
1794
+ $parsedFrame['indexeddatalength'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
1795
+ $frame_offset += 4;
1796
+ $parsedFrame['indexpoints'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 2));
1797
+ $frame_offset += 2;
1798
+ $parsedFrame['bitsperpoint'] = ord(substr($parsedFrame['data'], $frame_offset++, 1));
1799
+ $frame_bytesperpoint = ceil($parsedFrame['bitsperpoint'] / 8);
1800
+ for ($i = 0; $i < $frame_indexpoints; $i++) {
1801
+ $parsedFrame['indexes'][$i] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, $frame_bytesperpoint));
1802
+ $frame_offset += $frame_bytesperpoint;
1803
+ }
1804
+ unset($parsedFrame['data']);
1805
+
1806
+ } elseif (($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'RGAD')) { // Replay Gain Adjustment
1807
+ // http://privatewww.essex.ac.uk/~djmrob/replaygain/file_format_id3v2.html
1808
+ // There may only be one 'RGAD' frame in a tag
1809
+ // <Header for 'Replay Gain Adjustment', ID: 'RGAD'>
1810
+ // Peak Amplitude $xx $xx $xx $xx
1811
+ // Radio Replay Gain Adjustment %aaabbbcd %dddddddd
1812
+ // Audiophile Replay Gain Adjustment %aaabbbcd %dddddddd
1813
+ // a - name code
1814
+ // b - originator code
1815
+ // c - sign bit
1816
+ // d - replay gain adjustment
1817
+
1818
+ $frame_offset = 0;
1819
+ $parsedFrame['peakamplitude'] = getid3_lib::BigEndian2Float(substr($parsedFrame['data'], $frame_offset, 4));
1820
+ $frame_offset += 4;
1821
+ $rg_track_adjustment = getid3_lib::Dec2Bin(substr($parsedFrame['data'], $frame_offset, 2));
1822
+ $frame_offset += 2;
1823
+ $rg_album_adjustment = getid3_lib::Dec2Bin(substr($parsedFrame['data'], $frame_offset, 2));
1824
+ $frame_offset += 2;
1825
+ $parsedFrame['raw']['track']['name'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 0, 3));
1826
+ $parsedFrame['raw']['track']['originator'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 3, 3));
1827
+ $parsedFrame['raw']['track']['signbit'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 6, 1));
1828
+ $parsedFrame['raw']['track']['adjustment'] = getid3_lib::Bin2Dec(substr($rg_track_adjustment, 7, 9));
1829
+ $parsedFrame['raw']['album']['name'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 0, 3));
1830
+ $parsedFrame['raw']['album']['originator'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 3, 3));
1831
+ $parsedFrame['raw']['album']['signbit'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 6, 1));
1832
+ $parsedFrame['raw']['album']['adjustment'] = getid3_lib::Bin2Dec(substr($rg_album_adjustment, 7, 9));
1833
+ $parsedFrame['track']['name'] = getid3_lib::RGADnameLookup($parsedFrame['raw']['track']['name']);
1834
+ $parsedFrame['track']['originator'] = getid3_lib::RGADoriginatorLookup($parsedFrame['raw']['track']['originator']);
1835
+ $parsedFrame['track']['adjustment'] = getid3_lib::RGADadjustmentLookup($parsedFrame['raw']['track']['adjustment'], $parsedFrame['raw']['track']['signbit']);
1836
+ $parsedFrame['album']['name'] = getid3_lib::RGADnameLookup($parsedFrame['raw']['album']['name']);
1837
+ $parsedFrame['album']['originator'] = getid3_lib::RGADoriginatorLookup($parsedFrame['raw']['album']['originator']);
1838
+ $parsedFrame['album']['adjustment'] = getid3_lib::RGADadjustmentLookup($parsedFrame['raw']['album']['adjustment'], $parsedFrame['raw']['album']['signbit']);
1839
+
1840
+ $info['replay_gain']['track']['peak'] = $parsedFrame['peakamplitude'];
1841
+ $info['replay_gain']['track']['originator'] = $parsedFrame['track']['originator'];
1842
+ $info['replay_gain']['track']['adjustment'] = $parsedFrame['track']['adjustment'];
1843
+ $info['replay_gain']['album']['originator'] = $parsedFrame['album']['originator'];
1844
+ $info['replay_gain']['album']['adjustment'] = $parsedFrame['album']['adjustment'];
1845
+
1846
+ unset($parsedFrame['data']);
1847
+
1848
+ }
1849
+
1850
+ return true;
1851
+ }
1852
+
1853
+
1854
+ function DeUnsynchronise($data) {
1855
+ return str_replace("\xFF\x00", "\xFF", $data);
1856
+ }
1857
+
1858
+ function LookupExtendedHeaderRestrictionsTagSizeLimits($index) {
1859
+ static $LookupExtendedHeaderRestrictionsTagSizeLimits = array(
1860
+ 0x00 => 'No more than 128 frames and 1 MB total tag size',
1861
+ 0x01 => 'No more than 64 frames and 128 KB total tag size',
1862
+ 0x02 => 'No more than 32 frames and 40 KB total tag size',
1863
+ 0x03 => 'No more than 32 frames and 4 KB total tag size',
1864
+ );
1865
+ return (isset($LookupExtendedHeaderRestrictionsTagSizeLimits[$index]) ? $LookupExtendedHeaderRestrictionsTagSizeLimits[$index] : '');
1866
+ }
1867
+
1868
+ function LookupExtendedHeaderRestrictionsTextEncodings($index) {
1869
+ static $LookupExtendedHeaderRestrictionsTextEncodings = array(
1870
+ 0x00 => 'No restrictions',
1871
+ 0x01 => 'Strings are only encoded with ISO-8859-1 or UTF-8',
1872
+ );
1873
+ return (isset($LookupExtendedHeaderRestrictionsTextEncodings[$index]) ? $LookupExtendedHeaderRestrictionsTextEncodings[$index] : '');
1874
+ }
1875
+
1876
+ function LookupExtendedHeaderRestrictionsTextFieldSize($index) {
1877
+ static $LookupExtendedHeaderRestrictionsTextFieldSize = array(
1878
+ 0x00 => 'No restrictions',
1879
+ 0x01 => 'No string is longer than 1024 characters',
1880
+ 0x02 => 'No string is longer than 128 characters',
1881
+ 0x03 => 'No string is longer than 30 characters',
1882
+ );
1883
+ return (isset($LookupExtendedHeaderRestrictionsTextFieldSize[$index]) ? $LookupExtendedHeaderRestrictionsTextFieldSize[$index] : '');
1884
+ }
1885
+
1886
+ function LookupExtendedHeaderRestrictionsImageEncoding($index) {
1887
+ static $LookupExtendedHeaderRestrictionsImageEncoding = array(
1888
+ 0x00 => 'No restrictions',
1889
+ 0x01 => 'Images are encoded only with PNG or JPEG',
1890
+ );
1891
+ return (isset($LookupExtendedHeaderRestrictionsImageEncoding[$index]) ? $LookupExtendedHeaderRestrictionsImageEncoding[$index] : '');
1892
+ }
1893
+
1894
+ function LookupExtendedHeaderRestrictionsImageSizeSize($index) {
1895
+ static $LookupExtendedHeaderRestrictionsImageSizeSize = array(
1896
+ 0x00 => 'No restrictions',
1897
+ 0x01 => 'All images are 256x256 pixels or smaller',
1898
+ 0x02 => 'All images are 64x64 pixels or smaller',
1899
+ 0x03 => 'All images are exactly 64x64 pixels, unless required otherwise',
1900
+ );
1901
+ return (isset($LookupExtendedHeaderRestrictionsImageSizeSize[$index]) ? $LookupExtendedHeaderRestrictionsImageSizeSize[$index] : '');
1902
+ }
1903
+
1904
+ function LookupCurrencyUnits($currencyid) {
1905
+
1906
+ $begin = __LINE__;
1907
+
1908
+ /** This is not a comment!
1909
+
1910
+
1911
+ AED Dirhams
1912
+ AFA Afghanis
1913
+ ALL Leke
1914
+ AMD Drams
1915
+ ANG Guilders
1916
+ AOA Kwanza
1917
+ ARS Pesos
1918
+ ATS Schillings
1919
+ AUD Dollars
1920
+ AWG Guilders
1921
+ AZM Manats
1922
+ BAM Convertible Marka
1923
+ BBD Dollars
1924
+ BDT Taka
1925
+ BEF Francs
1926
+ BGL Leva
1927
+ BHD Dinars
1928
+ BIF Francs
1929
+ BMD Dollars
1930
+ BND Dollars
1931
+ BOB Bolivianos
1932
+ BRL Brazil Real
1933
+ BSD Dollars
1934
+ BTN Ngultrum
1935
+ BWP Pulas
1936
+ BYR Rubles
1937
+ BZD Dollars
1938
+ CAD Dollars
1939
+ CDF Congolese Francs
1940
+ CHF Francs
1941
+ CLP Pesos
1942
+ CNY Yuan Renminbi
1943
+ COP Pesos
1944
+ CRC Colones
1945
+ CUP Pesos
1946
+ CVE Escudos
1947
+ CYP Pounds
1948
+ CZK Koruny
1949
+ DEM Deutsche Marks
1950
+ DJF Francs
1951
+ DKK Kroner
1952
+ DOP Pesos
1953
+ DZD Algeria Dinars
1954
+ EEK Krooni
1955
+ EGP Pounds
1956
+ ERN Nakfa
1957
+ ESP Pesetas
1958
+ ETB Birr
1959
+ EUR Euro
1960
+ FIM Markkaa
1961
+ FJD Dollars
1962
+ FKP Pounds
1963
+ FRF Francs
1964
+ GBP Pounds
1965
+ GEL Lari
1966
+ GGP Pounds
1967
+ GHC Cedis
1968
+ GIP Pounds
1969
+ GMD Dalasi
1970
+ GNF Francs
1971
+ GRD Drachmae
1972
+ GTQ Quetzales
1973
+ GYD Dollars
1974
+ HKD Dollars
1975
+ HNL Lempiras
1976
+ HRK Kuna
1977
+ HTG Gourdes
1978
+ HUF Forints
1979
+ IDR Rupiahs
1980
+ IEP Pounds
1981
+ ILS New Shekels
1982
+ IMP Pounds
1983
+ INR Rupees
1984
+ IQD Dinars
1985
+ IRR Rials
1986
+ ISK Kronur
1987
+ ITL Lire
1988
+ JEP Pounds
1989
+ JMD Dollars
1990
+ JOD Dinars
1991
+ JPY Yen
1992
+ KES Shillings
1993
+ KGS Soms
1994
+ KHR Riels
1995
+ KMF Francs
1996
+ KPW Won
1997
+ KWD Dinars
1998
+ KYD Dollars
1999
+ KZT Tenge
2000
+ LAK Kips
2001
+ LBP Pounds
2002
+ LKR Rupees
2003
+ LRD Dollars
2004
+ LSL Maloti
2005
+ LTL Litai
2006
+ LUF Francs
2007
+ LVL Lati
2008
+ LYD Dinars
2009
+ MAD Dirhams
2010
+ MDL Lei
2011
+ MGF Malagasy Francs
2012
+ MKD Denars
2013
+ MMK Kyats
2014
+ MNT Tugriks
2015
+ MOP Patacas
2016
+ MRO Ouguiyas
2017
+ MTL Liri
2018
+ MUR Rupees
2019
+ MVR Rufiyaa
2020
+ MWK Kwachas
2021
+ MXN Pesos
2022
+ MYR Ringgits
2023
+ MZM Meticais
2024
+ NAD Dollars
2025
+ NGN Nairas
2026
+ NIO Gold Cordobas
2027
+ NLG Guilders
2028
+ NOK Krone
2029
+ NPR Nepal Rupees
2030
+ NZD Dollars
2031
+ OMR Rials
2032
+ PAB Balboa
2033
+ PEN Nuevos Soles
2034
+ PGK Kina
2035
+ PHP Pesos
2036
+ PKR Rupees
2037
+ PLN Zlotych
2038
+ PTE Escudos
2039
+ PYG Guarani
2040
+ QAR Rials
2041
+ ROL Lei
2042
+ RUR Rubles
2043
+ RWF Rwanda Francs
2044
+ SAR Riyals
2045
+ SBD Dollars
2046
+ SCR Rupees
2047
+ SDD Dinars
2048
+ SEK Kronor
2049
+ SGD Dollars
2050
+ SHP Pounds
2051
+ SIT Tolars
2052
+ SKK Koruny
2053
+ SLL Leones
2054
+ SOS Shillings
2055
+ SPL Luigini
2056
+ SRG Guilders
2057
+ STD Dobras
2058
+ SVC Colones
2059
+ SYP Pounds
2060
+ SZL Emalangeni
2061
+ THB Baht
2062
+ TJR Rubles
2063
+ TMM Manats
2064
+ TND Dinars
2065
+ TOP Pa'anga
2066
+ TRL Liras
2067
+ TTD Dollars
2068
+ TVD Tuvalu Dollars
2069
+ TWD New Dollars
2070
+ TZS Shillings
2071
+ UAH Hryvnia
2072
+ UGX Shillings
2073
+ USD Dollars
2074
+ UYU Pesos
2075
+ UZS Sums
2076
+ VAL Lire
2077
+ VEB Bolivares
2078
+ VND Dong
2079
+ VUV Vatu
2080
+ WST Tala
2081
+ XAF Francs
2082
+ XAG Ounces
2083
+ XAU Ounces
2084
+ XCD Dollars
2085
+ XDR Special Drawing Rights
2086
+ XPD Ounces
2087
+ XPF Francs
2088
+ XPT Ounces
2089
+ YER Rials
2090
+ YUM New Dinars
2091
+ ZAR Rand
2092
+ ZMK Kwacha
2093
+ ZWD Zimbabwe Dollars
2094
+
2095
+ */
2096
+
2097
+ return getid3_lib::EmbeddedLookup($currencyid, $begin, __LINE__, __FILE__, 'id3v2-currency-units');
2098
+ }
2099
+
2100
+
2101
+ function LookupCurrencyCountry($currencyid) {
2102
+
2103
+ $begin = __LINE__;
2104
+
2105
+ /** This is not a comment!
2106
+
2107
+ AED United Arab Emirates
2108
+ AFA Afghanistan
2109
+ ALL Albania
2110
+ AMD Armenia
2111
+ ANG Netherlands Antilles
2112
+ AOA Angola
2113
+ ARS Argentina
2114
+ ATS Austria
2115
+ AUD Australia
2116
+ AWG Aruba
2117
+ AZM Azerbaijan
2118
+ BAM Bosnia and Herzegovina
2119
+ BBD Barbados
2120
+ BDT Bangladesh
2121
+ BEF Belgium
2122
+ BGL Bulgaria
2123
+ BHD Bahrain
2124
+ BIF Burundi
2125
+ BMD Bermuda
2126
+ BND Brunei Darussalam
2127
+ BOB Bolivia
2128
+ BRL Brazil
2129
+ BSD Bahamas
2130
+ BTN Bhutan
2131
+ BWP Botswana
2132
+ BYR Belarus
2133
+ BZD Belize
2134
+ CAD Canada
2135
+ CDF Congo/Kinshasa
2136
+ CHF Switzerland
2137
+ CLP Chile
2138
+ CNY China
2139
+ COP Colombia
2140
+ CRC Costa Rica
2141
+ CUP Cuba
2142
+ CVE Cape Verde
2143
+ CYP Cyprus
2144
+ CZK Czech Republic
2145
+ DEM Germany
2146
+ DJF Djibouti
2147
+ DKK Denmark
2148
+ DOP Dominican Republic
2149
+ DZD Algeria
2150
+ EEK Estonia
2151
+ EGP Egypt
2152
+ ERN Eritrea
2153
+ ESP Spain
2154
+ ETB Ethiopia
2155
+ EUR Euro Member Countries
2156
+ FIM Finland
2157
+ FJD Fiji
2158
+ FKP Falkland Islands (Malvinas)
2159
+ FRF France
2160
+ GBP United Kingdom
2161
+ GEL Georgia
2162
+ GGP Guernsey
2163
+ GHC Ghana
2164
+ GIP Gibraltar
2165
+ GMD Gambia
2166
+ GNF Guinea
2167
+ GRD Greece
2168
+ GTQ Guatemala
2169
+ GYD Guyana
2170
+ HKD Hong Kong
2171
+ HNL Honduras
2172
+ HRK Croatia
2173
+ HTG Haiti
2174
+ HUF Hungary
2175
+ IDR Indonesia
2176
+ IEP Ireland (Eire)
2177
+ ILS Israel
2178
+ IMP Isle of Man
2179
+ INR India
2180
+ IQD Iraq
2181
+ IRR Iran
2182
+ ISK Iceland
2183
+ ITL Italy
2184
+ JEP Jersey
2185
+ JMD Jamaica
2186
+ JOD Jordan
2187
+ JPY Japan
2188
+ KES Kenya
2189
+ KGS Kyrgyzstan
2190
+ KHR Cambodia
2191
+ KMF Comoros
2192
+ KPW Korea
2193
+ KWD Kuwait
2194
+ KYD Cayman Islands
2195
+ KZT Kazakstan
2196
+ LAK Laos
2197
+ LBP Lebanon
2198
+ LKR Sri Lanka
2199
+ LRD Liberia
2200
+ LSL Lesotho
2201
+ LTL Lithuania
2202
+ LUF Luxembourg
2203
+ LVL Latvia
2204
+ LYD Libya
2205
+ MAD Morocco
2206
+ MDL Moldova
2207
+ MGF Madagascar
2208
+ MKD Macedonia
2209
+ MMK Myanmar (Burma)
2210
+ MNT Mongolia
2211
+ MOP Macau
2212
+ MRO Mauritania
2213
+ MTL Malta
2214
+ MUR Mauritius
2215
+ MVR Maldives (Maldive Islands)
2216
+ MWK Malawi
2217
+ MXN Mexico
2218
+ MYR Malaysia
2219
+ MZM Mozambique
2220
+ NAD Namibia
2221
+ NGN Nigeria
2222
+ NIO Nicaragua
2223
+ NLG Netherlands (Holland)
2224
+ NOK Norway
2225
+ NPR Nepal
2226
+ NZD New Zealand
2227
+ OMR Oman
2228
+ PAB Panama
2229
+ PEN Peru
2230
+ PGK Papua New Guinea
2231
+ PHP Philippines
2232
+ PKR Pakistan
2233
+ PLN Poland
2234
+ PTE Portugal
2235
+ PYG Paraguay
2236
+ QAR Qatar
2237
+ ROL Romania
2238
+ RUR Russia
2239
+ RWF Rwanda
2240
+ SAR Saudi Arabia
2241
+ SBD Solomon Islands
2242
+ SCR Seychelles
2243
+ SDD Sudan
2244
+ SEK Sweden
2245
+ SGD Singapore
2246
+ SHP Saint Helena
2247
+ SIT Slovenia
2248
+ SKK Slovakia
2249
+ SLL Sierra Leone
2250
+ SOS Somalia
2251
+ SPL Seborga
2252
+ SRG Suriname
2253
+ STD S�o Tome and Principe
2254
+ SVC El Salvador
2255
+ SYP Syria
2256
+ SZL Swaziland
2257
+ THB Thailand
2258
+ TJR Tajikistan
2259
+ TMM Turkmenistan
2260
+ TND Tunisia
2261
+ TOP Tonga
2262
+ TRL Turkey
2263
+ TTD Trinidad and Tobago
2264
+ TVD Tuvalu
2265
+ TWD Taiwan
2266
+ TZS Tanzania
2267
+ UAH Ukraine
2268
+ UGX Uganda
2269
+ USD United States of America
2270
+ UYU Uruguay
2271
+ UZS Uzbekistan
2272
+ VAL Vatican City
2273
+ VEB Venezuela
2274
+ VND Viet Nam
2275
+ VUV Vanuatu
2276
+ WST Samoa
2277
+ XAF Communaut� Financi�re Africaine
2278
+ XAG Silver
2279
+ XAU Gold
2280
+ XCD East Caribbean
2281
+ XDR International Monetary Fund
2282
+ XPD Palladium
2283
+ XPF Comptoirs Fran�ais du Pacifique
2284
+ XPT Platinum
2285
+ YER Yemen
2286
+ YUM Yugoslavia
2287
+ ZAR South Africa
2288
+ ZMK Zambia
2289
+ ZWD Zimbabwe
2290
+
2291
+ */
2292
+
2293
+ return getid3_lib::EmbeddedLookup($currencyid, $begin, __LINE__, __FILE__, 'id3v2-currency-country');
2294
+ }
2295
+
2296
+
2297
+
2298
+ static function LanguageLookup($languagecode, $casesensitive=false) {
2299
+
2300
+ if (!$casesensitive) {
2301
+ $languagecode = strtolower($languagecode);
2302
+ }
2303
+
2304
+ // http://www.id3.org/id3v2.4.0-structure.txt
2305
+ // [4. ID3v2 frame overview]
2306
+ // The three byte language field, present in several frames, is used to
2307
+ // describe the language of the frame's content, according to ISO-639-2
2308
+ // [ISO-639-2]. The language should be represented in lower case. If the
2309
+ // language is not known the string "XXX" should be used.
2310
+
2311
+
2312
+ // ISO 639-2 - http://www.id3.org/iso639-2.html
2313
+
2314
+ $begin = __LINE__;
2315
+
2316
+ /** This is not a comment!
2317
+
2318
+ XXX unknown
2319
+ xxx unknown
2320
+ aar Afar
2321
+ abk Abkhazian
2322
+ ace Achinese
2323
+ ach Acoli
2324
+ ada Adangme
2325
+ afa Afro-Asiatic (Other)
2326
+ afh Afrihili
2327
+ afr Afrikaans
2328
+ aka Akan
2329
+ akk Akkadian
2330
+ alb Albanian
2331
+ ale Aleut
2332
+ alg Algonquian Languages
2333
+ amh Amharic
2334
+ ang English, Old (ca. 450-1100)
2335
+ apa Apache Languages
2336
+ ara Arabic
2337
+ arc Aramaic
2338
+ arm Armenian
2339
+ arn Araucanian
2340
+ arp Arapaho
2341
+ art Artificial (Other)
2342
+ arw Arawak
2343
+ asm Assamese
2344
+ ath Athapascan Languages
2345
+ ava Avaric
2346
+ ave Avestan
2347
+ awa Awadhi
2348
+ aym Aymara
2349
+ aze Azerbaijani
2350
+ bad Banda
2351
+ bai Bamileke Languages
2352
+ bak Bashkir
2353
+ bal Baluchi
2354
+ bam Bambara
2355
+ ban Balinese
2356
+ baq Basque
2357
+ bas Basa
2358
+ bat Baltic (Other)
2359
+ bej Beja
2360
+ bel Byelorussian
2361
+ bem Bemba
2362
+ ben Bengali
2363
+ ber Berber (Other)
2364
+ bho Bhojpuri
2365
+ bih Bihari
2366
+ bik Bikol
2367
+ bin Bini
2368
+ bis Bislama
2369
+ bla Siksika
2370
+ bnt Bantu (Other)
2371
+ bod Tibetan
2372
+ bra Braj
2373
+ bre Breton
2374
+ bua Buriat
2375
+ bug Buginese
2376
+ bul Bulgarian
2377
+ bur Burmese
2378
+ cad Caddo
2379
+ cai Central American Indian (Other)
2380
+ car Carib
2381
+ cat Catalan
2382
+ cau Caucasian (Other)
2383
+ ceb Cebuano
2384
+ cel Celtic (Other)
2385
+ ces Czech
2386
+ cha Chamorro
2387
+ chb Chibcha
2388
+ che Chechen
2389
+ chg Chagatai
2390
+ chi Chinese
2391
+ chm Mari
2392
+ chn Chinook jargon
2393
+ cho Choctaw
2394
+ chr Cherokee
2395
+ chu Church Slavic
2396
+ chv Chuvash
2397
+ chy Cheyenne
2398
+ cop Coptic
2399
+ cor Cornish
2400
+ cos Corsican
2401
+ cpe Creoles and Pidgins, English-based (Other)
2402
+ cpf Creoles and Pidgins, French-based (Other)
2403
+ cpp Creoles and Pidgins, Portuguese-based (Other)
2404
+ cre Cree
2405
+ crp Creoles and Pidgins (Other)
2406
+ cus Cushitic (Other)
2407
+ cym Welsh
2408
+ cze Czech
2409
+ dak Dakota
2410
+ dan Danish
2411
+ del Delaware
2412
+ deu German
2413
+ din Dinka
2414
+ div Divehi
2415
+ doi Dogri
2416
+ dra Dravidian (Other)
2417
+ dua Duala
2418
+ dum Dutch, Middle (ca. 1050-1350)
2419
+ dut Dutch
2420
+ dyu Dyula
2421
+ dzo Dzongkha
2422
+ efi Efik
2423
+ egy Egyptian (Ancient)
2424
+ eka Ekajuk
2425
+ ell Greek, Modern (1453-)
2426
+ elx Elamite
2427
+ eng English
2428
+ enm English, Middle (ca. 1100-1500)
2429
+ epo Esperanto
2430
+ esk Eskimo (Other)
2431
+ esl Spanish
2432
+ est Estonian
2433
+ eus Basque
2434
+ ewe Ewe
2435
+ ewo Ewondo
2436
+ fan Fang
2437
+ fao Faroese
2438
+ fas Persian
2439
+ fat Fanti
2440
+ fij Fijian
2441
+ fin Finnish
2442
+ fiu Finno-Ugrian (Other)
2443
+ fon Fon
2444
+ fra French
2445
+ fre French
2446
+ frm French, Middle (ca. 1400-1600)
2447
+ fro French, Old (842- ca. 1400)
2448
+ fry Frisian
2449
+ ful Fulah
2450
+ gaa Ga
2451
+ gae Gaelic (Scots)
2452
+ gai Irish
2453
+ gay Gayo
2454
+ gdh Gaelic (Scots)
2455
+ gem Germanic (Other)
2456
+ geo Georgian
2457
+ ger German
2458
+ gez Geez
2459
+ gil Gilbertese
2460
+ glg Gallegan
2461
+ gmh German, Middle High (ca. 1050-1500)
2462
+ goh German, Old High (ca. 750-1050)
2463
+ gon Gondi
2464
+ got Gothic
2465
+ grb Grebo
2466
+ grc Greek, Ancient (to 1453)
2467
+ gre Greek, Modern (1453-)
2468
+ grn Guarani
2469
+ guj Gujarati
2470
+ hai Haida
2471
+ hau Hausa
2472
+ haw Hawaiian
2473
+ heb Hebrew
2474
+ her Herero
2475
+ hil Hiligaynon
2476
+ him Himachali
2477
+ hin Hindi
2478
+ hmo Hiri Motu
2479
+ hun Hungarian
2480
+ hup Hupa
2481
+ hye Armenian
2482
+ iba Iban
2483
+ ibo Igbo
2484
+ ice Icelandic
2485
+ ijo Ijo
2486
+ iku Inuktitut
2487
+ ilo Iloko
2488
+ ina Interlingua (International Auxiliary language Association)
2489
+ inc Indic (Other)
2490
+ ind Indonesian
2491
+ ine Indo-European (Other)
2492
+ ine Interlingue
2493
+ ipk Inupiak
2494
+ ira Iranian (Other)
2495
+ iri Irish
2496
+ iro Iroquoian uages
2497
+ isl Icelandic
2498
+ ita Italian
2499
+ jav Javanese
2500
+ jaw Javanese
2501
+ jpn Japanese
2502
+ jpr Judeo-Persian
2503
+ jrb Judeo-Arabic
2504
+ kaa Kara-Kalpak
2505
+ kab Kabyle
2506
+ kac Kachin
2507
+ kal Greenlandic
2508
+ kam Kamba
2509
+ kan Kannada
2510
+ kar Karen
2511
+ kas Kashmiri
2512
+ kat Georgian
2513
+ kau Kanuri
2514
+ kaw Kawi
2515
+ kaz Kazakh
2516
+ kha Khasi
2517
+ khi Khoisan (Other)
2518
+ khm Khmer
2519
+ kho Khotanese
2520
+ kik Kikuyu
2521
+ kin Kinyarwanda
2522
+ kir Kirghiz
2523
+ kok Konkani
2524
+ kom Komi
2525
+ kon Kongo
2526
+ kor Korean
2527
+ kpe Kpelle
2528
+ kro Kru
2529
+ kru Kurukh
2530
+ kua Kuanyama
2531
+ kum Kumyk
2532
+ kur Kurdish
2533
+ kus Kusaie
2534
+ kut Kutenai
2535
+ lad Ladino
2536
+ lah Lahnda
2537
+ lam Lamba
2538
+ lao Lao
2539
+ lat Latin
2540
+ lav Latvian
2541
+ lez Lezghian
2542
+ lin Lingala
2543
+ lit Lithuanian
2544
+ lol Mongo
2545
+ loz Lozi
2546
+ ltz Letzeburgesch
2547
+ lub Luba-Katanga
2548
+ lug Ganda
2549
+ lui Luiseno
2550
+ lun Lunda
2551
+ luo Luo (Kenya and Tanzania)
2552
+ mac Macedonian
2553
+ mad Madurese
2554
+ mag Magahi
2555
+ mah Marshall
2556
+ mai Maithili
2557
+ mak Macedonian
2558
+ mak Makasar
2559
+ mal Malayalam
2560
+ man Mandingo
2561
+ mao Maori
2562
+ map Austronesian (Other)
2563
+ mar Marathi
2564
+ mas Masai
2565
+ max Manx
2566
+ may Malay
2567
+ men Mende
2568
+ mga Irish, Middle (900 - 1200)
2569
+ mic Micmac
2570
+ min Minangkabau
2571
+ mis Miscellaneous (Other)
2572
+ mkh Mon-Kmer (Other)
2573
+ mlg Malagasy
2574
+ mlt Maltese
2575
+ mni Manipuri
2576
+ mno Manobo Languages
2577
+ moh Mohawk
2578
+ mol Moldavian
2579
+ mon Mongolian
2580
+ mos Mossi
2581
+ mri Maori
2582
+ msa Malay
2583
+ mul Multiple Languages
2584
+ mun Munda Languages
2585
+ mus Creek
2586
+ mwr Marwari
2587
+ mya Burmese
2588
+ myn Mayan Languages
2589
+ nah Aztec
2590
+ nai North American Indian (Other)
2591
+ nau Nauru
2592
+ nav Navajo
2593
+ nbl Ndebele, South
2594
+ nde Ndebele, North
2595
+ ndo Ndongo
2596
+ nep Nepali
2597
+ new Newari
2598
+ nic Niger-Kordofanian (Other)
2599
+ niu Niuean
2600
+ nla Dutch
2601
+ nno Norwegian (Nynorsk)
2602
+ non Norse, Old
2603
+ nor Norwegian
2604
+ nso Sotho, Northern
2605
+ nub Nubian Languages
2606
+ nya Nyanja
2607
+ nym Nyamwezi
2608
+ nyn Nyankole
2609
+ nyo Nyoro
2610
+ nzi Nzima
2611
+ oci Langue d'Oc (post 1500)
2612
+ oji Ojibwa
2613
+ ori Oriya
2614
+ orm Oromo
2615
+ osa Osage
2616
+ oss Ossetic
2617
+ ota Turkish, Ottoman (1500 - 1928)
2618
+ oto Otomian Languages
2619
+ paa Papuan-Australian (Other)
2620
+ pag Pangasinan
2621
+ pal Pahlavi
2622
+ pam Pampanga
2623
+ pan Panjabi
2624
+ pap Papiamento
2625
+ pau Palauan
2626
+ peo Persian, Old (ca 600 - 400 B.C.)
2627
+ per Persian
2628
+ phn Phoenician
2629
+ pli Pali
2630
+ pol Polish
2631
+ pon Ponape
2632
+ por Portuguese
2633
+ pra Prakrit uages
2634
+ pro Provencal, Old (to 1500)
2635
+ pus Pushto
2636
+ que Quechua
2637
+ raj Rajasthani
2638
+ rar Rarotongan
2639
+ roa Romance (Other)
2640
+ roh Rhaeto-Romance
2641
+ rom Romany
2642
+ ron Romanian
2643
+ rum Romanian
2644
+ run Rundi
2645
+ rus Russian
2646
+ sad Sandawe
2647
+ sag Sango
2648
+ sah Yakut
2649
+ sai South American Indian (Other)
2650
+ sal Salishan Languages
2651
+ sam Samaritan Aramaic
2652
+ san Sanskrit
2653
+ sco Scots
2654
+ scr Serbo-Croatian
2655
+ sel Selkup
2656
+ sem Semitic (Other)
2657
+ sga Irish, Old (to 900)
2658
+ shn Shan
2659
+ sid Sidamo
2660
+ sin Singhalese
2661
+ sio Siouan Languages
2662
+ sit Sino-Tibetan (Other)
2663
+ sla Slavic (Other)
2664
+ slk Slovak
2665
+ slo Slovak
2666
+ slv Slovenian
2667
+ smi Sami Languages
2668
+ smo Samoan
2669
+ sna Shona
2670
+ snd Sindhi
2671
+ sog Sogdian
2672
+ som Somali
2673
+ son Songhai
2674
+ sot Sotho, Southern
2675
+ spa Spanish
2676
+ sqi Albanian
2677
+ srd Sardinian
2678
+ srr Serer
2679
+ ssa Nilo-Saharan (Other)
2680
+ ssw Siswant
2681
+ ssw Swazi
2682
+ suk Sukuma
2683
+ sun Sudanese
2684
+ sus Susu
2685
+ sux Sumerian
2686
+ sve Swedish
2687
+ swa Swahili
2688
+ swe Swedish
2689
+ syr Syriac
2690
+ tah Tahitian
2691
+ tam Tamil
2692
+ tat Tatar
2693
+ tel Telugu
2694
+ tem Timne
2695
+ ter Tereno
2696
+ tgk Tajik
2697
+ tgl Tagalog
2698
+ tha Thai
2699
+ tib Tibetan
2700
+ tig Tigre
2701
+ tir Tigrinya
2702
+ tiv Tivi
2703
+ tli Tlingit
2704
+ tmh Tamashek
2705
+ tog Tonga (Nyasa)
2706
+ ton Tonga (Tonga Islands)
2707
+ tru Truk
2708
+ tsi Tsimshian
2709
+ tsn Tswana
2710
+ tso Tsonga
2711
+ tuk Turkmen
2712
+ tum Tumbuka
2713
+ tur Turkish
2714
+ tut Altaic (Other)
2715
+ twi Twi
2716
+ tyv Tuvinian
2717
+ uga Ugaritic
2718
+ uig Uighur
2719
+ ukr Ukrainian
2720
+ umb Umbundu
2721
+ und Undetermined
2722
+ urd Urdu
2723
+ uzb Uzbek
2724
+ vai Vai
2725
+ ven Venda
2726
+ vie Vietnamese
2727
+ vol Volap�k
2728
+ vot Votic
2729
+ wak Wakashan Languages
2730
+ wal Walamo
2731
+ war Waray
2732
+ was Washo
2733
+ wel Welsh
2734
+ wen Sorbian Languages
2735
+ wol Wolof
2736
+ xho Xhosa
2737
+ yao Yao
2738
+ yap Yap
2739
+ yid Yiddish
2740
+ yor Yoruba
2741
+ zap Zapotec
2742
+ zen Zenaga
2743
+ zha Zhuang
2744
+ zho Chinese
2745
+ zul Zulu
2746
+ zun Zuni
2747
+
2748
+ */
2749
+
2750
+ return getid3_lib::EmbeddedLookup($languagecode, $begin, __LINE__, __FILE__, 'id3v2-languagecode');
2751
+ }
2752
+
2753
+
2754
+ static function ETCOEventLookup($index) {
2755
+ if (($index >= 0x17) && ($index <= 0xDF)) {
2756
+ return 'reserved for future use';
2757
+ }
2758
+ if (($index >= 0xE0) && ($index <= 0xEF)) {
2759
+ return 'not predefined synch 0-F';
2760
+ }
2761
+ if (($index >= 0xF0) && ($index <= 0xFC)) {
2762
+ return 'reserved for future use';
2763
+ }
2764
+
2765
+ static $EventLookup = array(
2766
+ 0x00 => 'padding (has no meaning)',
2767
+ 0x01 => 'end of initial silence',
2768
+ 0x02 => 'intro start',
2769
+ 0x03 => 'main part start',
2770
+ 0x04 => 'outro start',
2771
+ 0x05 => 'outro end',
2772
+ 0x06 => 'verse start',
2773
+ 0x07 => 'refrain start',
2774
+ 0x08 => 'interlude start',
2775
+ 0x09 => 'theme start',
2776
+ 0x0A => 'variation start',
2777
+ 0x0B => 'key change',
2778
+ 0x0C => 'time change',
2779
+ 0x0D => 'momentary unwanted noise (Snap, Crackle & Pop)',
2780
+ 0x0E => 'sustained noise',
2781
+ 0x0F => 'sustained noise end',
2782
+ 0x10 => 'intro end',
2783
+ 0x11 => 'main part end',
2784
+ 0x12 => 'verse end',
2785
+ 0x13 => 'refrain end',
2786
+ 0x14 => 'theme end',
2787
+ 0x15 => 'profanity',
2788
+ 0x16 => 'profanity end',
2789
+ 0xFD => 'audio end (start of silence)',
2790
+ 0xFE => 'audio file ends',
2791
+ 0xFF => 'one more byte of events follows'
2792
+ );
2793
+
2794
+ return (isset($EventLookup[$index]) ? $EventLookup[$index] : '');
2795
+ }
2796
+
2797
+ static function SYTLContentTypeLookup($index) {
2798
+ static $SYTLContentTypeLookup = array(
2799
+ 0x00 => 'other',
2800
+ 0x01 => 'lyrics',
2801
+ 0x02 => 'text transcription',
2802
+ 0x03 => 'movement/part name', // (e.g. 'Adagio')
2803
+ 0x04 => 'events', // (e.g. 'Don Quijote enters the stage')
2804
+ 0x05 => 'chord', // (e.g. 'Bb F Fsus')
2805
+ 0x06 => 'trivia/\'pop up\' information',
2806
+ 0x07 => 'URLs to webpages',
2807
+ 0x08 => 'URLs to images'
2808
+ );
2809
+
2810
+ return (isset($SYTLContentTypeLookup[$index]) ? $SYTLContentTypeLookup[$index] : '');
2811
+ }
2812
+
2813
+ static function APICPictureTypeLookup($index, $returnarray=false) {
2814
+ static $APICPictureTypeLookup = array(
2815
+ 0x00 => 'Other',
2816
+ 0x01 => '32x32 pixels \'file icon\' (PNG only)',
2817
+ 0x02 => 'Other file icon',
2818
+ 0x03 => 'Cover (front)',
2819
+ 0x04 => 'Cover (back)',
2820
+ 0x05 => 'Leaflet page',
2821
+ 0x06 => 'Media (e.g. label side of CD)',
2822
+ 0x07 => 'Lead artist/lead performer/soloist',
2823
+ 0x08 => 'Artist/performer',
2824
+ 0x09 => 'Conductor',
2825
+ 0x0A => 'Band/Orchestra',
2826
+ 0x0B => 'Composer',
2827
+ 0x0C => 'Lyricist/text writer',
2828
+ 0x0D => 'Recording Location',
2829
+ 0x0E => 'During recording',
2830
+ 0x0F => 'During performance',
2831
+ 0x10 => 'Movie/video screen capture',
2832
+ 0x11 => 'A bright coloured fish',
2833
+ 0x12 => 'Illustration',
2834
+ 0x13 => 'Band/artist logotype',
2835
+ 0x14 => 'Publisher/Studio logotype'
2836
+ );
2837
+ if ($returnarray) {
2838
+ return $APICPictureTypeLookup;
2839
+ }
2840
+ return (isset($APICPictureTypeLookup[$index]) ? $APICPictureTypeLookup[$index] : '');
2841
+ }
2842
+
2843
+ static function COMRReceivedAsLookup($index) {
2844
+ static $COMRReceivedAsLookup = array(
2845
+ 0x00 => 'Other',
2846
+ 0x01 => 'Standard CD album with other songs',
2847
+ 0x02 => 'Compressed audio on CD',
2848
+ 0x03 => 'File over the Internet',
2849
+ 0x04 => 'Stream over the Internet',
2850
+ 0x05 => 'As note sheets',
2851
+ 0x06 => 'As note sheets in a book with other sheets',
2852
+ 0x07 => 'Music on other media',
2853
+ 0x08 => 'Non-musical merchandise'
2854
+ );
2855
+
2856
+ return (isset($COMRReceivedAsLookup[$index]) ? $COMRReceivedAsLookup[$index] : '');
2857
+ }
2858
+
2859
+ static function RVA2ChannelTypeLookup($index) {
2860
+ static $RVA2ChannelTypeLookup = array(
2861
+ 0x00 => 'Other',
2862
+ 0x01 => 'Master volume',
2863
+ 0x02 => 'Front right',
2864
+ 0x03 => 'Front left',
2865
+ 0x04 => 'Back right',
2866
+ 0x05 => 'Back left',
2867
+ 0x06 => 'Front centre',
2868
+ 0x07 => 'Back centre',
2869
+ 0x08 => 'Subwoofer'
2870
+ );
2871
+
2872
+ return (isset($RVA2ChannelTypeLookup[$index]) ? $RVA2ChannelTypeLookup[$index] : '');
2873
+ }
2874
+
2875
+ static function FrameNameLongLookup($framename) {
2876
+
2877
+ $begin = __LINE__;
2878
+
2879
+ /** This is not a comment!
2880
+
2881
+ AENC Audio encryption
2882
+ APIC Attached picture
2883
+ ASPI Audio seek point index
2884
+ BUF Recommended buffer size
2885
+ CNT Play counter
2886
+ COM Comments
2887
+ COMM Comments
2888
+ COMR Commercial frame
2889
+ CRA Audio encryption
2890
+ CRM Encrypted meta frame
2891
+ ENCR Encryption method registration
2892
+ EQU Equalisation
2893
+ EQU2 Equalisation (2)
2894
+ EQUA Equalisation
2895
+ ETC Event timing codes
2896
+ ETCO Event timing codes
2897
+ GEO General encapsulated object
2898
+ GEOB General encapsulated object
2899
+ GRID Group identification registration
2900
+ IPL Involved people list
2901
+ IPLS Involved people list
2902
+ LINK Linked information
2903
+ LNK Linked information
2904
+ MCDI Music CD identifier
2905
+ MCI Music CD Identifier
2906
+ MLL MPEG location lookup table
2907
+ MLLT MPEG location lookup table
2908
+ OWNE Ownership frame
2909
+ PCNT Play counter
2910
+ PIC Attached picture
2911
+ POP Popularimeter
2912
+ POPM Popularimeter
2913
+ POSS Position synchronisation frame
2914
+ PRIV Private frame
2915
+ RBUF Recommended buffer size
2916
+ REV Reverb
2917
+ RVA Relative volume adjustment
2918
+ RVA2 Relative volume adjustment (2)
2919
+ RVAD Relative volume adjustment
2920
+ RVRB Reverb
2921
+ SEEK Seek frame
2922
+ SIGN Signature frame
2923
+ SLT Synchronised lyric/text
2924
+ STC Synced tempo codes
2925
+ SYLT Synchronised lyric/text
2926
+ SYTC Synchronised tempo codes
2927
+ TAL Album/Movie/Show title
2928
+ TALB Album/Movie/Show title
2929
+ TBP BPM (Beats Per Minute)
2930
+ TBPM BPM (beats per minute)
2931
+ TCM Composer
2932
+ TCMP Part of a compilation
2933
+ TCO Content type
2934
+ TCOM Composer
2935
+ TCON Content type
2936
+ TCOP Copyright message
2937
+ TCP Part of a compilation
2938
+ TCR Copyright message
2939
+ TDA Date
2940
+ TDAT Date
2941
+ TDEN Encoding time
2942
+ TDLY Playlist delay
2943
+ TDOR Original release time
2944
+ TDRC Recording time
2945
+ TDRL Release time
2946
+ TDTG Tagging time
2947
+ TDY Playlist delay
2948
+ TEN Encoded by
2949
+ TENC Encoded by
2950
+ TEXT Lyricist/Text writer
2951
+ TFLT File type
2952
+ TFT File type
2953
+ TIM Time
2954
+ TIME Time
2955
+ TIPL Involved people list
2956
+ TIT1 Content group description
2957
+ TIT2 Title/songname/content description
2958
+ TIT3 Subtitle/Description refinement
2959
+ TKE Initial key
2960
+ TKEY Initial key
2961
+ TLA Language(s)
2962
+ TLAN Language(s)
2963
+ TLE Length
2964
+ TLEN Length
2965
+ TMCL Musician credits list
2966
+ TMED Media type
2967
+ TMOO Mood
2968
+ TMT Media type
2969
+ TOA Original artist(s)/performer(s)
2970
+ TOAL Original album/movie/show title
2971
+ TOF Original filename
2972
+ TOFN Original filename
2973
+ TOL Original Lyricist(s)/text writer(s)
2974
+ TOLY Original lyricist(s)/text writer(s)
2975
+ TOPE Original artist(s)/performer(s)
2976
+ TOR Original release year
2977
+ TORY Original release year
2978
+ TOT Original album/Movie/Show title
2979
+ TOWN File owner/licensee
2980
+ TP1 Lead artist(s)/Lead performer(s)/Soloist(s)/Performing group
2981
+ TP2 Band/Orchestra/Accompaniment
2982
+ TP3 Conductor/Performer refinement
2983
+ TP4 Interpreted, remixed, or otherwise modified by
2984
+ TPA Part of a set
2985
+ TPB Publisher
2986
+ TPE1 Lead performer(s)/Soloist(s)
2987
+ TPE2 Band/orchestra/accompaniment
2988
+ TPE3 Conductor/performer refinement
2989
+ TPE4 Interpreted, remixed, or otherwise modified by
2990
+ TPOS Part of a set
2991
+ TPRO Produced notice
2992
+ TPUB Publisher
2993
+ TRC ISRC (International Standard Recording Code)
2994
+ TRCK Track number/Position in set
2995
+ TRD Recording dates
2996
+ TRDA Recording dates
2997
+ TRK Track number/Position in set
2998
+ TRSN Internet radio station name
2999
+ TRSO Internet radio station owner
3000
+ TS2 Album-Artist sort order
3001
+ TSA Album sort order
3002
+ TSC Composer sort order
3003
+ TSI Size
3004
+ TSIZ Size
3005
+ TSO2 Album-Artist sort order
3006
+ TSOA Album sort order
3007
+ TSOC Composer sort order
3008
+ TSOP Performer sort order
3009
+ TSOT Title sort order
3010
+ TSP Performer sort order
3011
+ TSRC ISRC (international standard recording code)
3012
+ TSS Software/hardware and settings used for encoding
3013
+ TSSE Software/Hardware and settings used for encoding
3014
+ TSST Set subtitle
3015
+ TST Title sort order
3016
+ TT1 Content group description
3017
+ TT2 Title/Songname/Content description
3018
+ TT3 Subtitle/Description refinement
3019
+ TXT Lyricist/text writer
3020
+ TXX User defined text information frame
3021
+ TXXX User defined text information frame
3022
+ TYE Year
3023
+ TYER Year
3024
+ UFI Unique file identifier
3025
+ UFID Unique file identifier
3026
+ ULT Unsychronised lyric/text transcription
3027
+ USER Terms of use
3028
+ USLT Unsynchronised lyric/text transcription
3029
+ WAF Official audio file webpage
3030
+ WAR Official artist/performer webpage
3031
+ WAS Official audio source webpage
3032
+ WCM Commercial information
3033
+ WCOM Commercial information
3034
+ WCOP Copyright/Legal information
3035
+ WCP Copyright/Legal information
3036
+ WOAF Official audio file webpage
3037
+ WOAR Official artist/performer webpage
3038
+ WOAS Official audio source webpage
3039
+ WORS Official Internet radio station homepage
3040
+ WPAY Payment
3041
+ WPB Publishers official webpage
3042
+ WPUB Publishers official webpage
3043
+ WXX User defined URL link frame
3044
+ WXXX User defined URL link frame
3045
+ TFEA Featured Artist
3046
+ TSTU Recording Studio
3047
+ rgad Replay Gain Adjustment
3048
+
3049
+ */
3050
+
3051
+ return getid3_lib::EmbeddedLookup($framename, $begin, __LINE__, __FILE__, 'id3v2-framename_long');
3052
+
3053
+ // Last three:
3054
+ // from Helium2 [www.helium2.com]
3055
+ // from http://privatewww.essex.ac.uk/~djmrob/replaygain/file_format_id3v2.html
3056
+ }
3057
+
3058
+
3059
+ static function FrameNameShortLookup($framename) {
3060
+
3061
+ $begin = __LINE__;
3062
+
3063
+ /** This is not a comment!
3064
+
3065
+ AENC audio_encryption
3066
+ APIC attached_picture
3067
+ ASPI audio_seek_point_index
3068
+ BUF recommended_buffer_size
3069
+ CNT play_counter
3070
+ COM comment
3071
+ COMM comment
3072
+ COMR commercial_frame
3073
+ CRA audio_encryption
3074
+ CRM encrypted_meta_frame
3075
+ ENCR encryption_method_registration
3076
+ EQU equalisation
3077
+ EQU2 equalisation
3078
+ EQUA equalisation
3079
+ ETC event_timing_codes
3080
+ ETCO event_timing_codes
3081
+ GEO general_encapsulated_object
3082
+ GEOB general_encapsulated_object
3083
+ GRID group_identification_registration
3084
+ IPL involved_people_list
3085
+ IPLS involved_people_list
3086
+ LINK linked_information
3087
+ LNK linked_information
3088
+ MCDI music_cd_identifier
3089
+ MCI music_cd_identifier
3090
+ MLL mpeg_location_lookup_table
3091
+ MLLT mpeg_location_lookup_table
3092
+ OWNE ownership_frame
3093
+ PCNT play_counter
3094
+ PIC attached_picture
3095
+ POP popularimeter
3096
+ POPM popularimeter
3097
+ POSS position_synchronisation_frame
3098
+ PRIV private_frame
3099
+ RBUF recommended_buffer_size
3100
+ REV reverb
3101
+ RVA relative_volume_adjustment
3102
+ RVA2 relative_volume_adjustment
3103
+ RVAD relative_volume_adjustment
3104
+ RVRB reverb
3105
+ SEEK seek_frame
3106
+ SIGN signature_frame
3107
+ SLT synchronised_lyric
3108
+ STC synced_tempo_codes
3109
+ SYLT synchronised_lyric
3110
+ SYTC synchronised_tempo_codes
3111
+ TAL album
3112
+ TALB album
3113
+ TBP bpm
3114
+ TBPM bpm
3115
+ TCM composer
3116
+ TCMP part_of_a_compilation
3117
+ TCO genre
3118
+ TCOM composer
3119
+ TCON genre
3120
+ TCOP copyright_message
3121
+ TCP part_of_a_compilation
3122
+ TCR copyright_message
3123
+ TDA date
3124
+ TDAT date
3125
+ TDEN encoding_time
3126
+ TDLY playlist_delay
3127
+ TDOR original_release_time
3128
+ TDRC recording_time
3129
+ TDRL release_time
3130
+ TDTG tagging_time
3131
+ TDY playlist_delay
3132
+ TEN encoded_by
3133
+ TENC encoded_by
3134
+ TEXT lyricist
3135
+ TFLT file_type
3136
+ TFT file_type
3137
+ TIM time
3138
+ TIME time
3139
+ TIPL involved_people_list
3140
+ TIT1 content_group_description
3141
+ TIT2 title
3142
+ TIT3 subtitle
3143
+ TKE initial_key
3144
+ TKEY initial_key
3145
+ TLA language
3146
+ TLAN language
3147
+ TLE length
3148
+ TLEN length
3149
+ TMCL musician_credits_list
3150
+ TMED media_type
3151
+ TMOO mood
3152
+ TMT media_type
3153
+ TOA original_artist
3154
+ TOAL original_album
3155
+ TOF original_filename
3156
+ TOFN original_filename
3157
+ TOL original_lyricist
3158
+ TOLY original_lyricist
3159
+ TOPE original_artist
3160
+ TOR original_year
3161
+ TORY original_year
3162
+ TOT original_album
3163
+ TOWN file_owner
3164
+ TP1 artist
3165
+ TP2 band
3166
+ TP3 conductor
3167
+ TP4 remixer
3168
+ TPA part_of_a_set
3169
+ TPB publisher
3170
+ TPE1 artist
3171
+ TPE2 band
3172
+ TPE3 conductor
3173
+ TPE4 remixer
3174
+ TPOS part_of_a_set
3175
+ TPRO produced_notice
3176
+ TPUB publisher
3177
+ TRC isrc
3178
+ TRCK track_number
3179
+ TRD recording_dates
3180
+ TRDA recording_dates
3181
+ TRK track_number
3182
+ TRSN internet_radio_station_name
3183
+ TRSO internet_radio_station_owner
3184
+ TS2 album_artist_sort_order
3185
+ TSA album_sort_order
3186
+ TSC composer_sort_order
3187
+ TSI size
3188
+ TSIZ size
3189
+ TSO2 album_artist_sort_order
3190
+ TSOA album_sort_order
3191
+ TSOC composer_sort_order
3192
+ TSOP performer_sort_order
3193
+ TSOT title_sort_order
3194
+ TSP performer_sort_order
3195
+ TSRC isrc
3196
+ TSS encoder_settings
3197
+ TSSE encoder_settings
3198
+ TSST set_subtitle
3199
+ TST title_sort_order
3200
+ TT1 description
3201
+ TT2 title
3202
+ TT3 subtitle
3203
+ TXT lyricist
3204
+ TXX text
3205
+ TXXX text
3206
+ TYE year
3207
+ TYER year
3208
+ UFI unique_file_identifier
3209
+ UFID unique_file_identifier
3210
+ ULT unsychronised_lyric
3211
+ USER terms_of_use
3212
+ USLT unsynchronised_lyric
3213
+ WAF url_file
3214
+ WAR url_artist
3215
+ WAS url_source
3216
+ WCM commercial_information
3217
+ WCOM commercial_information
3218
+ WCOP copyright
3219
+ WCP copyright
3220
+ WOAF url_file
3221
+ WOAR url_artist
3222
+ WOAS url_source
3223
+ WORS url_station
3224
+ WPAY url_payment
3225
+ WPB url_publisher
3226
+ WPUB url_publisher
3227
+ WXX url_user
3228
+ WXXX url_user
3229
+ TFEA featured_artist
3230
+ TSTU recording_studio
3231
+ rgad replay_gain_adjustment
3232
+
3233
+ */
3234
+
3235
+ return getid3_lib::EmbeddedLookup($framename, $begin, __LINE__, __FILE__, 'id3v2-framename_short');
3236
+ }
3237
+
3238
+ static function TextEncodingTerminatorLookup($encoding) {
3239
+ // http://www.id3.org/id3v2.4.0-structure.txt
3240
+ // Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings:
3241
+ static $TextEncodingTerminatorLookup = array(
3242
+ 0 => "\x00", // $00 ISO-8859-1. Terminated with $00.
3243
+ 1 => "\x00\x00", // $01 UTF-16 encoded Unicode with BOM. All strings in the same frame SHALL have the same byteorder. Terminated with $00 00.
3244
+ 2 => "\x00\x00", // $02 UTF-16BE encoded Unicode without BOM. Terminated with $00 00.
3245
+ 3 => "\x00", // $03 UTF-8 encoded Unicode. Terminated with $00.
3246
+ 255 => "\x00\x00"
3247
+ );
3248
+ return (isset($TextEncodingTerminatorLookup[$encoding]) ? $TextEncodingTerminatorLookup[$encoding] : '');
3249
+ }
3250
+
3251
+ static function TextEncodingNameLookup($encoding) {
3252
+ // http://www.id3.org/id3v2.4.0-structure.txt
3253
+ // Frames that allow different types of text encoding contains a text encoding description byte. Possible encodings:
3254
+ static $TextEncodingNameLookup = array(
3255
+ 0 => 'ISO-8859-1', // $00 ISO-8859-1. Terminated with $00.
3256
+ 1 => 'UTF-16', // $01 UTF-16 encoded Unicode with BOM. All strings in the same frame SHALL have the same byteorder. Terminated with $00 00.
3257
+ 2 => 'UTF-16BE', // $02 UTF-16BE encoded Unicode without BOM. Terminated with $00 00.
3258
+ 3 => 'UTF-8', // $03 UTF-8 encoded Unicode. Terminated with $00.
3259
+ 255 => 'UTF-16BE'
3260
+ );
3261
+ return (isset($TextEncodingNameLookup[$encoding]) ? $TextEncodingNameLookup[$encoding] : 'ISO-8859-1');
3262
+ }
3263
+
3264
+ static function IsValidID3v2FrameName($framename, $id3v2majorversion) {
3265
+ switch ($id3v2majorversion) {
3266
+ case 2:
3267
+ return preg_match('#[A-Z][A-Z0-9]{2}#', $framename);
3268
+ break;
3269
+
3270
+ case 3:
3271
+ case 4:
3272
+ return preg_match('#[A-Z][A-Z0-9]{3}#', $framename);
3273
+ break;
3274
+ }
3275
+ return false;
3276
+ }
3277
+
3278
+ static function IsANumber($numberstring, $allowdecimal=false, $allownegative=false) {
3279
+ for ($i = 0; $i < strlen($numberstring); $i++) {
3280
+ if ((chr($numberstring{$i}) < chr('0')) || (chr($numberstring{$i}) > chr('9'))) {
3281
+ if (($numberstring{$i} == '.') && $allowdecimal) {
3282
+ // allowed
3283
+ } elseif (($numberstring{$i} == '-') && $allownegative && ($i == 0)) {
3284
+ // allowed
3285
+ } else {
3286
+ return false;
3287
+ }
3288
+ }
3289
+ }
3290
+ return true;
3291
+ }
3292
+
3293
+ static function IsValidDateStampString($datestamp) {
3294
+ if (strlen($datestamp) != 8) {
3295
+ return false;
3296
+ }
3297
+ if (!self::IsANumber($datestamp, false)) {
3298
+ return false;
3299
+ }
3300
+ $year = substr($datestamp, 0, 4);
3301
+ $month = substr($datestamp, 4, 2);
3302
+ $day = substr($datestamp, 6, 2);
3303
+ if (($year == 0) || ($month == 0) || ($day == 0)) {
3304
+ return false;
3305
+ }
3306
+ if ($month > 12) {
3307
+ return false;
3308
+ }
3309
+ if ($day > 31) {
3310
+ return false;
3311
+ }
3312
+ if (($day > 30) && (($month == 4) || ($month == 6) || ($month == 9) || ($month == 11))) {
3313
+ return false;
3314
+ }
3315
+ if (($day > 29) && ($month == 2)) {
3316
+ return false;
3317
+ }
3318
+ return true;
3319
+ }
3320
+
3321
+ static function ID3v2HeaderLength($majorversion) {
3322
+ return (($majorversion == 2) ? 6 : 10);
3323
+ }
3324
+
3325
+ }
3326
+
3327
+ ?>
includes/lib/getid3/module.tag.lyrics3.php ADDED
@@ -0,0 +1,297 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /////////////////////////////////////////////////////////////////
3
+ /// getID3() by James Heinrich <info@getid3.org> //
4
+ // available at http://getid3.sourceforge.net //
5
+ // or http://www.getid3.org //
6
+ /////////////////////////////////////////////////////////////////
7
+ // See readme.txt for more details //
8
+ /////////////////////////////////////////////////////////////////
9
+ /// //
10
+ // module.tag.lyrics3.php //
11
+ // module for analyzing Lyrics3 tags //
12
+ // dependencies: module.tag.apetag.php (optional) //
13
+ // ///
14
+ /////////////////////////////////////////////////////////////////
15
+
16
+
17
+ class getid3_lyrics3 extends getid3_handler
18
+ {
19
+
20
+ function Analyze() {
21
+ $info = &$this->getid3->info;
22
+
23
+ // http://www.volweb.cz/str/tags.htm
24
+
25
+ if (!getid3_lib::intValueSupported($info['filesize'])) {
26
+ $info['warning'][] = 'Unable to check for Lyrics3 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB';
27
+ return false;
28
+ }
29
+
30
+ fseek($this->getid3->fp, (0 - 128 - 9 - 6), SEEK_END); // end - ID3v1 - "LYRICSEND" - [Lyrics3size]
31
+ $lyrics3_id3v1 = fread($this->getid3->fp, 128 + 9 + 6);
32
+ $lyrics3lsz = substr($lyrics3_id3v1, 0, 6); // Lyrics3size
33
+ $lyrics3end = substr($lyrics3_id3v1, 6, 9); // LYRICSEND or LYRICS200
34
+ $id3v1tag = substr($lyrics3_id3v1, 15, 128); // ID3v1
35
+
36
+ if ($lyrics3end == 'LYRICSEND') {
37
+ // Lyrics3v1, ID3v1, no APE
38
+
39
+ $lyrics3size = 5100;
40
+ $lyrics3offset = $info['filesize'] - 128 - $lyrics3size;
41
+ $lyrics3version = 1;
42
+
43
+ } elseif ($lyrics3end == 'LYRICS200') {
44
+ // Lyrics3v2, ID3v1, no APE
45
+
46
+ // LSZ = lyrics + 'LYRICSBEGIN'; add 6-byte size field; add 'LYRICS200'
47
+ $lyrics3size = $lyrics3lsz + 6 + strlen('LYRICS200');
48
+ $lyrics3offset = $info['filesize'] - 128 - $lyrics3size;
49
+ $lyrics3version = 2;
50
+
51
+ } elseif (substr(strrev($lyrics3_id3v1), 0, 9) == strrev('LYRICSEND')) {
52
+ // Lyrics3v1, no ID3v1, no APE
53
+
54
+ $lyrics3size = 5100;
55
+ $lyrics3offset = $info['filesize'] - $lyrics3size;
56
+ $lyrics3version = 1;
57
+ $lyrics3offset = $info['filesize'] - $lyrics3size;
58
+
59
+ } elseif (substr(strrev($lyrics3_id3v1), 0, 9) == strrev('LYRICS200')) {
60
+
61
+ // Lyrics3v2, no ID3v1, no APE
62
+
63
+ $lyrics3size = strrev(substr(strrev($lyrics3_id3v1), 9, 6)) + 6 + strlen('LYRICS200'); // LSZ = lyrics + 'LYRICSBEGIN'; add 6-byte size field; add 'LYRICS200'
64
+ $lyrics3offset = $info['filesize'] - $lyrics3size;
65
+ $lyrics3version = 2;
66
+
67
+ } else {
68
+
69
+ if (isset($info['ape']['tag_offset_start']) && ($info['ape']['tag_offset_start'] > 15)) {
70
+
71
+ fseek($this->getid3->fp, $info['ape']['tag_offset_start'] - 15, SEEK_SET);
72
+ $lyrics3lsz = fread($this->getid3->fp, 6);
73
+ $lyrics3end = fread($this->getid3->fp, 9);
74
+
75
+ if ($lyrics3end == 'LYRICSEND') {
76
+ // Lyrics3v1, APE, maybe ID3v1
77
+
78
+ $lyrics3size = 5100;
79
+ $lyrics3offset = $info['ape']['tag_offset_start'] - $lyrics3size;
80
+ $info['avdataend'] = $lyrics3offset;
81
+ $lyrics3version = 1;
82
+ $info['warning'][] = 'APE tag located after Lyrics3, will probably break Lyrics3 compatability';
83
+
84
+ } elseif ($lyrics3end == 'LYRICS200') {
85
+ // Lyrics3v2, APE, maybe ID3v1
86
+
87
+ $lyrics3size = $lyrics3lsz + 6 + strlen('LYRICS200'); // LSZ = lyrics + 'LYRICSBEGIN'; add 6-byte size field; add 'LYRICS200'
88
+ $lyrics3offset = $info['ape']['tag_offset_start'] - $lyrics3size;
89
+ $lyrics3version = 2;
90
+ $info['warning'][] = 'APE tag located after Lyrics3, will probably break Lyrics3 compatability';
91
+
92
+ }
93
+
94
+ }
95
+
96
+ }
97
+
98
+ if (isset($lyrics3offset)) {
99
+ $info['avdataend'] = $lyrics3offset;
100
+ $this->getLyrics3Data($lyrics3offset, $lyrics3version, $lyrics3size);
101
+
102
+ if (!isset($info['ape'])) {
103
+ $GETID3_ERRORARRAY = &$info['warning'];
104
+ if (getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.apetag.php', __FILE__, false)) {
105
+ $getid3_temp = new getID3();
106
+ $getid3_temp->openfile($this->getid3->filename);
107
+ $getid3_apetag = new getid3_apetag($getid3_temp);
108
+ $getid3_apetag->overrideendoffset = $info['lyrics3']['tag_offset_start'];
109
+ $getid3_apetag->Analyze();
110
+ if (!empty($getid3_temp->info['ape'])) {
111
+ $info['ape'] = $getid3_temp->info['ape'];
112
+ }
113
+ if (!empty($getid3_temp->info['replay_gain'])) {
114
+ $info['replay_gain'] = $getid3_temp->info['replay_gain'];
115
+ }
116
+ unset($getid3_temp, $getid3_apetag);
117
+ }
118
+ }
119
+
120
+ }
121
+
122
+ return true;
123
+ }
124
+
125
+ function getLyrics3Data($endoffset, $version, $length) {
126
+ // http://www.volweb.cz/str/tags.htm
127
+
128
+ $info = &$this->getid3->info;
129
+
130
+ if (!getid3_lib::intValueSupported($endoffset)) {
131
+ $info['warning'][] = 'Unable to check for Lyrics3 because file is larger than '.round(PHP_INT_MAX / 1073741824).'GB';
132
+ return false;
133
+ }
134
+
135
+ fseek($this->getid3->fp, $endoffset, SEEK_SET);
136
+ if ($length <= 0) {
137
+ return false;
138
+ }
139
+ $rawdata = fread($this->getid3->fp, $length);
140
+
141
+ $ParsedLyrics3['raw']['lyrics3version'] = $version;
142
+ $ParsedLyrics3['raw']['lyrics3tagsize'] = $length;
143
+ $ParsedLyrics3['tag_offset_start'] = $endoffset;
144
+ $ParsedLyrics3['tag_offset_end'] = $endoffset + $length - 1;
145
+
146
+ if (substr($rawdata, 0, 11) != 'LYRICSBEGIN') {
147
+ if (strpos($rawdata, 'LYRICSBEGIN') !== false) {
148
+
149
+ $info['warning'][] = '"LYRICSBEGIN" expected at '.$endoffset.' but actually found at '.($endoffset + strpos($rawdata, 'LYRICSBEGIN')).' - this is invalid for Lyrics3 v'.$version;
150
+ $info['avdataend'] = $endoffset + strpos($rawdata, 'LYRICSBEGIN');
151
+ $rawdata = substr($rawdata, strpos($rawdata, 'LYRICSBEGIN'));
152
+ $length = strlen($rawdata);
153
+ $ParsedLyrics3['tag_offset_start'] = $info['avdataend'];
154
+ $ParsedLyrics3['raw']['lyrics3tagsize'] = $length;
155
+
156
+ } else {
157
+
158
+ $info['error'][] = '"LYRICSBEGIN" expected at '.$endoffset.' but found "'.substr($rawdata, 0, 11).'" instead';
159
+ return false;
160
+
161
+ }
162
+
163
+ }
164
+
165
+ switch ($version) {
166
+
167
+ case 1:
168
+ if (substr($rawdata, strlen($rawdata) - 9, 9) == 'LYRICSEND') {
169
+ $ParsedLyrics3['raw']['LYR'] = trim(substr($rawdata, 11, strlen($rawdata) - 11 - 9));
170
+ $this->Lyrics3LyricsTimestampParse($ParsedLyrics3);
171
+ } else {
172
+ $info['error'][] = '"LYRICSEND" expected at '.(ftell($this->getid3->fp) - 11 + $length - 9).' but found "'.substr($rawdata, strlen($rawdata) - 9, 9).'" instead';
173
+ return false;
174
+ }
175
+ break;
176
+
177
+ case 2:
178
+ if (substr($rawdata, strlen($rawdata) - 9, 9) == 'LYRICS200') {
179
+ $ParsedLyrics3['raw']['unparsed'] = substr($rawdata, 11, strlen($rawdata) - 11 - 9 - 6); // LYRICSBEGIN + LYRICS200 + LSZ
180
+ $rawdata = $ParsedLyrics3['raw']['unparsed'];
181
+ while (strlen($rawdata) > 0) {
182
+ $fieldname = substr($rawdata, 0, 3);
183
+ $fieldsize = (int) substr($rawdata, 3, 5);
184
+ $ParsedLyrics3['raw'][$fieldname] = substr($rawdata, 8, $fieldsize);
185
+ $rawdata = substr($rawdata, 3 + 5 + $fieldsize);
186
+ }
187
+
188
+ if (isset($ParsedLyrics3['raw']['IND'])) {
189
+ $i = 0;
190
+ $flagnames = array('lyrics', 'timestamps', 'inhibitrandom');
191
+ foreach ($flagnames as $flagname) {
192
+ if (strlen($ParsedLyrics3['raw']['IND']) > $i++) {
193
+ $ParsedLyrics3['flags'][$flagname] = $this->IntString2Bool(substr($ParsedLyrics3['raw']['IND'], $i, 1 - 1));
194
+ }
195
+ }
196
+ }
197
+
198
+ $fieldnametranslation = array('ETT'=>'title', 'EAR'=>'artist', 'EAL'=>'album', 'INF'=>'comment', 'AUT'=>'author');
199
+ foreach ($fieldnametranslation as $key => $value) {
200
+ if (isset($ParsedLyrics3['raw'][$key])) {
201
+ $ParsedLyrics3['comments'][$value][] = trim($ParsedLyrics3['raw'][$key]);
202
+ }
203
+ }
204
+
205
+ if (isset($ParsedLyrics3['raw']['IMG'])) {
206
+ $imagestrings = explode("\r\n", $ParsedLyrics3['raw']['IMG']);
207
+ foreach ($imagestrings as $key => $imagestring) {
208
+ if (strpos($imagestring, '||') !== false) {
209
+ $imagearray = explode('||', $imagestring);
210
+ $ParsedLyrics3['images'][$key]['filename'] = (isset($imagearray[0]) ? $imagearray[0] : '');
211
+ $ParsedLyrics3['images'][$key]['description'] = (isset($imagearray[1]) ? $imagearray[1] : '');
212
+ $ParsedLyrics3['images'][$key]['timestamp'] = $this->Lyrics3Timestamp2Seconds(isset($imagearray[2]) ? $imagearray[2] : '');
213
+ }
214
+ }
215
+ }
216
+ if (isset($ParsedLyrics3['raw']['LYR'])) {
217
+ $this->Lyrics3LyricsTimestampParse($ParsedLyrics3);
218
+ }
219
+ } else {
220
+ $info['error'][] = '"LYRICS200" expected at '.(ftell($this->getid3->fp) - 11 + $length - 9).' but found "'.substr($rawdata, strlen($rawdata) - 9, 9).'" instead';
221
+ return false;
222
+ }
223
+ break;
224
+
225
+ default:
226
+ $info['error'][] = 'Cannot process Lyrics3 version '.$version.' (only v1 and v2)';
227
+ return false;
228
+ break;
229
+ }
230
+
231
+
232
+ if (isset($info['id3v1']['tag_offset_start']) && ($info['id3v1']['tag_offset_start'] <= $ParsedLyrics3['tag_offset_end'])) {
233
+ $info['warning'][] = 'ID3v1 tag information ignored since it appears to be a false synch in Lyrics3 tag data';
234
+ unset($info['id3v1']);
235
+ foreach ($info['warning'] as $key => $value) {
236
+ if ($value == 'Some ID3v1 fields do not use NULL characters for padding') {
237
+ unset($info['warning'][$key]);
238
+ sort($info['warning']);
239
+ break;
240
+ }
241
+ }
242
+ }
243
+
244
+ $info['lyrics3'] = $ParsedLyrics3;
245
+
246
+ return true;
247
+ }
248
+
249
+ function Lyrics3Timestamp2Seconds($rawtimestamp) {
250
+ if (preg_match('#^\\[([0-9]{2}):([0-9]{2})\\]$#', $rawtimestamp, $regs)) {
251
+ return (int) (($regs[1] * 60) + $regs[2]);
252
+ }
253
+ return false;
254
+ }
255
+
256
+ function Lyrics3LyricsTimestampParse(&$Lyrics3data) {
257
+ $lyricsarray = explode("\r\n", $Lyrics3data['raw']['LYR']);
258
+ foreach ($lyricsarray as $key => $lyricline) {
259
+ $regs = array();
260
+ unset($thislinetimestamps);
261
+ while (preg_match('#^(\\[[0-9]{2}:[0-9]{2}\\])#', $lyricline, $regs)) {
262
+ $thislinetimestamps[] = $this->Lyrics3Timestamp2Seconds($regs[0]);
263
+ $lyricline = str_replace($regs[0], '', $lyricline);
264
+ }
265
+ $notimestamplyricsarray[$key] = $lyricline;
266
+ if (isset($thislinetimestamps) && is_array($thislinetimestamps)) {
267
+ sort($thislinetimestamps);
268
+ foreach ($thislinetimestamps as $timestampkey => $timestamp) {
269
+ if (isset($Lyrics3data['synchedlyrics'][$timestamp])) {
270
+ // timestamps only have a 1-second resolution, it's possible that multiple lines
271
+ // could have the same timestamp, if so, append
272
+ $Lyrics3data['synchedlyrics'][$timestamp] .= "\r\n".$lyricline;
273
+ } else {
274
+ $Lyrics3data['synchedlyrics'][$timestamp] = $lyricline;
275
+ }
276
+ }
277
+ }
278
+ }
279
+ $Lyrics3data['unsynchedlyrics'] = implode("\r\n", $notimestamplyricsarray);
280
+ if (isset($Lyrics3data['synchedlyrics']) && is_array($Lyrics3data['synchedlyrics'])) {
281
+ ksort($Lyrics3data['synchedlyrics']);
282
+ }
283
+ return true;
284
+ }
285
+
286
+ function IntString2Bool($char) {
287
+ if ($char == '1') {
288
+ return true;
289
+ } elseif ($char == '0') {
290
+ return false;
291
+ }
292
+ return null;
293
+ }
294
+ }
295
+
296
+
297
+ ?>
loader.php CHANGED
@@ -1,18 +1,18 @@
1
  <?php
2
  /*
3
- Plugin Name: BuddyPress Media
4
- Plugin URI: http://rtcamp.com/buddypress-media/
5
- Description: This component adds missing media rich features like photos, videos and audios uploading to BuddyPress which are essential if you are building social network, seriously!
6
- Version: 2.0
7
- Author: rtCamp
8
- Author URI: http://rtcamp.com
9
- */
10
 
11
  /* A constant that can be checked to see if the BP Media is installed or not. */
12
  define('BP_MEDIA_IS_INSTALLED', 1);
13
 
14
  /* Constant to store the current version of the BP Media Plugin. */
15
- define('BP_MEDIA_VERSION', '2.0');
16
 
17
  /* A constant to be used as base for other URLs throughout the plugin */
18
  define('BP_MEDIA_PLUGIN_DIR', dirname(__FILE__));
@@ -53,4 +53,4 @@ function bp_media_deactivate() {
53
  //todo
54
  }
55
  register_deactivation_hook(__FILE__, 'bp_media_deactivate');
56
- ?>
1
  <?php
2
  /*
3
+ Plugin Name: BuddyPress Media Component
4
+ Plugin URI: http://rtcamp.com/buddypress-media/
5
+ Description: This component adds missing media rich features like photos, videos and audios uploading to BuddyPress which are essential if you are building social network, seriously!
6
+ Version: 2.0.1
7
+ Author: rtCamp
8
+ Author URI: http://rtcamp.com
9
+ */
10
 
11
  /* A constant that can be checked to see if the BP Media is installed or not. */
12
  define('BP_MEDIA_IS_INSTALLED', 1);
13
 
14
  /* Constant to store the current version of the BP Media Plugin. */
15
+ define('BP_MEDIA_VERSION', '2.0.1');
16
 
17
  /* A constant to be used as base for other URLs throughout the plugin */
18
  define('BP_MEDIA_PLUGIN_DIR', dirname(__FILE__));
53
  //todo
54
  }
55
  register_deactivation_hook(__FILE__, 'bp_media_deactivate');
56
+ ?>
readme.txt CHANGED
@@ -1,40 +1,48 @@
1
- === BuddyPress Media Component ===
2
- Contributors: rtcamp
3
- Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=9488824
4
- Tags: BuddyPress, media, multimedia, audio, video, photo, images, upload, share, MediaElement.js
5
- License: GPLv2 or later
6
- License URI: http://www.gnu.org/licenses/gpl-2.0.html
7
- Requires at least: 3.3.2
8
- Tested up to: 3.4.1
9
- Stable tag: 2.0
10
-
11
- BuddyPress Media Component adds multimedia features to your BuddyPress based social network.
12
-
13
- == Description ==
14
- [BuddyPress Media Component](http://rtcamp.com/buddypress-media/) adds multimedia features to your BuddyPress based social network, so that your members can upload and share photos, audio and videos with their friends.
15
-
16
- = Features =
17
- * Images, Audio and Video Support
18
- * Superior Performance
19
- * HTML5 player with fall back
20
- * Highly Scalable
21
-
22
- == Installation ==
23
- Install the plugin from the 'Plugins' section in your dashboard (Plugins > Add New > Search for BuddyPress Media).
24
-
25
- Alternatively you can [download lastest version](http://downloads.wordpress.org/plugin/buddypress-media.2.0.zip) of BuddyPress Media Component plugin from the repository. Unzip it and upload it to the plugins folder of your WordPress installation (wp-content/plugins/ directory of your WordPress installation).
26
-
27
- Activate it through the 'Plugins' section.
28
-
29
- == Frequently Asked Questions ==
30
- Please visit [BuddyPress Media Component's FAQ page](http://rtcamp.com/buddypress-media/faq/)
31
-
32
- == Screenshots ==
33
- Please visit [BuddyPress Media Component's Features page](http://rtcamp.com/buddypress-media/features/)
34
-
35
- == Changelog ==
36
-
37
- = 2.0 =
38
- * Integration into BuddyPress Activities
39
- * HTML5 Audio Tag Support (with fallback)
40
- * HTML5 Video Tag Support (with fallback)
 
 
 
 
 
 
 
 
1
+ === BuddyPress Media Component ===
2
+ Contributors: rtcamp
3
+ Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=9488824
4
+ Tags: BuddyPress, media, multimedia, audio, video, photo, images, upload, share, MediaElement.js
5
+ License: GPLv2 or later
6
+ License URI: http://www.gnu.org/licenses/gpl-2.0.html
7
+ Requires at least: 3.3.2
8
+ Tested up to: 3.4.1
9
+ Stable tag: 2.0.1
10
+
11
+ BuddyPress Media Component adds multimedia features to your BuddyPress based social network.
12
+
13
+ == Description ==
14
+
15
+ [BuddyPress Media Component](http://rtcamp.com/buddypress-media/) adds multimedia features to your BuddyPress based social network, so that your members can upload and share photos, audio and videos with their friends.
16
+
17
+ = Features =
18
+ * Images, Audio and Video Support
19
+ * Superior Performance
20
+ * HTML5 player with fall back
21
+ * Highly Scalable
22
+
23
+ == Installation ==
24
+
25
+ Install the plugin from the 'Plugins' section in your dashboard (Plugins > Add New > Search for BuddyPress Media).
26
+
27
+ Alternatively you can [download](http://downloads.wordpress.org/plugin/buddypress-media.zip) the plugin from the repository. Unzip it and upload it to the plugins folder of your WordPress installation (wp-content/plugins/ directory of your WordPress installation).
28
+
29
+ Activate it through the 'Plugins' section.
30
+
31
+ == Frequently Asked Questions ==
32
+
33
+ Please visit [BuddyPress Media Component's FAQ page](http://rtcamp.com/buddypress-media/faq/)
34
+
35
+ == Screenshots ==
36
+
37
+ Please visit [BuddyPress Media Component's Features page](http://rtcamp.com/buddypress-media/features/)
38
+
39
+ == Changelog ==
40
+
41
+ = 2.0.1 =
42
+ * Replaced codec finding library
43
+ * Fixed warning on activities page
44
+
45
+ = 2.0 =
46
+ * Integration into BuddyPress Activities
47
+ * HTML5 Audio Tag Support (with fallback)
48
+ * HTML5 Video Tag Support (with fallback)