| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | #------------------------------------------------------------------------------ | 
| 2 |  |  |  |  |  |  | # File:         MPEG.pm | 
| 3 |  |  |  |  |  |  | # | 
| 4 |  |  |  |  |  |  | # Description:  Read MPEG-1 and MPEG-2 meta information | 
| 5 |  |  |  |  |  |  | # | 
| 6 |  |  |  |  |  |  | # Revisions:    05/11/2006 - P. Harvey Created | 
| 7 |  |  |  |  |  |  | # | 
| 8 |  |  |  |  |  |  | # References:   1) http://www.mp3-tech.org/ | 
| 9 |  |  |  |  |  |  | #               2) http://www.getid3.org/ | 
| 10 |  |  |  |  |  |  | #               3) http://dvd.sourceforge.net/dvdinfo/dvdmpeg.html | 
| 11 |  |  |  |  |  |  | #               4) http://ffmpeg.org/ | 
| 12 |  |  |  |  |  |  | #               5) http://sourceforge.net/projects/mediainfo/ | 
| 13 |  |  |  |  |  |  | #------------------------------------------------------------------------------ | 
| 14 |  |  |  |  |  |  |  | 
| 15 |  |  |  |  |  |  | package Image::ExifTool::MPEG; | 
| 16 |  |  |  |  |  |  |  | 
| 17 | 4 |  |  | 4 |  | 530 | use strict; | 
|  | 4 |  |  |  |  | 10 |  | 
|  | 4 |  |  |  |  | 156 |  | 
| 18 | 4 |  |  | 4 |  | 26 | use vars qw($VERSION); | 
|  | 4 |  |  |  |  | 8 |  | 
|  | 4 |  |  |  |  | 169 |  | 
| 19 | 4 |  |  | 4 |  | 28 | use Image::ExifTool qw(:DataAccess :Utils); | 
|  | 4 |  |  |  |  | 9 |  | 
|  | 4 |  |  |  |  | 9507 |  | 
| 20 |  |  |  |  |  |  |  | 
| 21 |  |  |  |  |  |  | $VERSION = '1.17'; | 
| 22 |  |  |  |  |  |  |  | 
| 23 |  |  |  |  |  |  | %Image::ExifTool::MPEG::Audio = ( | 
| 24 |  |  |  |  |  |  | GROUPS => { 2 => 'Audio' }, | 
| 25 |  |  |  |  |  |  | 'Bit11-12' => { | 
| 26 |  |  |  |  |  |  | Name => 'MPEGAudioVersion', | 
| 27 |  |  |  |  |  |  | RawConv => '$self->{MPEG_Vers} = $val', | 
| 28 |  |  |  |  |  |  | PrintConv => { | 
| 29 |  |  |  |  |  |  | 0 => 2.5, | 
| 30 |  |  |  |  |  |  | 2 => 2, | 
| 31 |  |  |  |  |  |  | 3 => 1, | 
| 32 |  |  |  |  |  |  | }, | 
| 33 |  |  |  |  |  |  | }, | 
| 34 |  |  |  |  |  |  | 'Bit13-14' => { | 
| 35 |  |  |  |  |  |  | Name => 'AudioLayer', | 
| 36 |  |  |  |  |  |  | RawConv => '$self->{MPEG_Layer} = $val', | 
| 37 |  |  |  |  |  |  | PrintConv => { | 
| 38 |  |  |  |  |  |  | 1 => 3, | 
| 39 |  |  |  |  |  |  | 2 => 2, | 
| 40 |  |  |  |  |  |  | 3 => 1, | 
| 41 |  |  |  |  |  |  | }, | 
| 42 |  |  |  |  |  |  | }, | 
| 43 |  |  |  |  |  |  | # Bit 15 indicates CRC protection | 
| 44 |  |  |  |  |  |  | 'Bit16-19' => [ | 
| 45 |  |  |  |  |  |  | { | 
| 46 |  |  |  |  |  |  | Name => 'AudioBitrate', | 
| 47 |  |  |  |  |  |  | Condition => '$self->{MPEG_Vers} == 3 and $self->{MPEG_Layer} == 3', | 
| 48 |  |  |  |  |  |  | Notes => 'version 1, layer 1', | 
| 49 |  |  |  |  |  |  | PrintConvColumns => 3, | 
| 50 |  |  |  |  |  |  | ValueConv => { | 
| 51 |  |  |  |  |  |  | 0 => 'free', | 
| 52 |  |  |  |  |  |  | 1 => 32000, | 
| 53 |  |  |  |  |  |  | 2 => 64000, | 
| 54 |  |  |  |  |  |  | 3 => 96000, | 
| 55 |  |  |  |  |  |  | 4 => 128000, | 
| 56 |  |  |  |  |  |  | 5 => 160000, | 
| 57 |  |  |  |  |  |  | 6 => 192000, | 
| 58 |  |  |  |  |  |  | 7 => 224000, | 
| 59 |  |  |  |  |  |  | 8 => 256000, | 
| 60 |  |  |  |  |  |  | 9 => 288000, | 
| 61 |  |  |  |  |  |  | 10 => 320000, | 
| 62 |  |  |  |  |  |  | 11 => 352000, | 
| 63 |  |  |  |  |  |  | 12 => 384000, | 
| 64 |  |  |  |  |  |  | 13 => 416000, | 
| 65 |  |  |  |  |  |  | 14 => 448000, | 
| 66 |  |  |  |  |  |  | }, | 
| 67 |  |  |  |  |  |  | PrintConv => 'ConvertBitrate($val)', | 
| 68 |  |  |  |  |  |  | }, | 
| 69 |  |  |  |  |  |  | { | 
| 70 |  |  |  |  |  |  | Name => 'AudioBitrate', | 
| 71 |  |  |  |  |  |  | Condition => '$self->{MPEG_Vers} == 3 and $self->{MPEG_Layer} == 2', | 
| 72 |  |  |  |  |  |  | Notes => 'version 1, layer 2', | 
| 73 |  |  |  |  |  |  | PrintConvColumns => 3, | 
| 74 |  |  |  |  |  |  | ValueConv => { | 
| 75 |  |  |  |  |  |  | 0 => 'free', | 
| 76 |  |  |  |  |  |  | 1 => 32000, | 
| 77 |  |  |  |  |  |  | 2 => 48000, | 
| 78 |  |  |  |  |  |  | 3 => 56000, | 
| 79 |  |  |  |  |  |  | 4 => 64000, | 
| 80 |  |  |  |  |  |  | 5 => 80000, | 
| 81 |  |  |  |  |  |  | 6 => 96000, | 
| 82 |  |  |  |  |  |  | 7 => 112000, | 
| 83 |  |  |  |  |  |  | 8 => 128000, | 
| 84 |  |  |  |  |  |  | 9 => 160000, | 
| 85 |  |  |  |  |  |  | 10 => 192000, | 
| 86 |  |  |  |  |  |  | 11 => 224000, | 
| 87 |  |  |  |  |  |  | 12 => 256000, | 
| 88 |  |  |  |  |  |  | 13 => 320000, | 
| 89 |  |  |  |  |  |  | 14 => 384000, | 
| 90 |  |  |  |  |  |  | }, | 
| 91 |  |  |  |  |  |  | PrintConv => 'ConvertBitrate($val)', | 
| 92 |  |  |  |  |  |  | }, | 
| 93 |  |  |  |  |  |  | { | 
| 94 |  |  |  |  |  |  | Name => 'AudioBitrate', | 
| 95 |  |  |  |  |  |  | Condition => '$self->{MPEG_Vers} == 3 and $self->{MPEG_Layer} == 1', | 
| 96 |  |  |  |  |  |  | Notes => 'version 1, layer 3', | 
| 97 |  |  |  |  |  |  | PrintConvColumns => 3, | 
| 98 |  |  |  |  |  |  | ValueConv => { | 
| 99 |  |  |  |  |  |  | 0 => 'free', | 
| 100 |  |  |  |  |  |  | 1 => 32000, | 
| 101 |  |  |  |  |  |  | 2 => 40000, | 
| 102 |  |  |  |  |  |  | 3 => 48000, | 
| 103 |  |  |  |  |  |  | 4 => 56000, | 
| 104 |  |  |  |  |  |  | 5 => 64000, | 
| 105 |  |  |  |  |  |  | 6 => 80000, | 
| 106 |  |  |  |  |  |  | 7 => 96000, | 
| 107 |  |  |  |  |  |  | 8 => 112000, | 
| 108 |  |  |  |  |  |  | 9 => 128000, | 
| 109 |  |  |  |  |  |  | 10 => 160000, | 
| 110 |  |  |  |  |  |  | 11 => 192000, | 
| 111 |  |  |  |  |  |  | 12 => 224000, | 
| 112 |  |  |  |  |  |  | 13 => 256000, | 
| 113 |  |  |  |  |  |  | 14 => 320000, | 
| 114 |  |  |  |  |  |  | }, | 
| 115 |  |  |  |  |  |  | PrintConv => 'ConvertBitrate($val)', | 
| 116 |  |  |  |  |  |  | }, | 
| 117 |  |  |  |  |  |  | { | 
| 118 |  |  |  |  |  |  | Name => 'AudioBitrate', | 
| 119 |  |  |  |  |  |  | Condition => '$self->{MPEG_Vers} != 3 and $self->{MPEG_Layer} == 3', | 
| 120 |  |  |  |  |  |  | Notes => 'version 2 or 2.5, layer 1', | 
| 121 |  |  |  |  |  |  | PrintConvColumns => 3, | 
| 122 |  |  |  |  |  |  | ValueConv => { | 
| 123 |  |  |  |  |  |  | 0 => 'free', | 
| 124 |  |  |  |  |  |  | 1 => 32000, | 
| 125 |  |  |  |  |  |  | 2 => 48000, | 
| 126 |  |  |  |  |  |  | 3 => 56000, | 
| 127 |  |  |  |  |  |  | 4 => 64000, | 
| 128 |  |  |  |  |  |  | 5 => 80000, | 
| 129 |  |  |  |  |  |  | 6 => 96000, | 
| 130 |  |  |  |  |  |  | 7 => 112000, | 
| 131 |  |  |  |  |  |  | 8 => 128000, | 
| 132 |  |  |  |  |  |  | 9 => 144000, | 
| 133 |  |  |  |  |  |  | 10 => 160000, | 
| 134 |  |  |  |  |  |  | 11 => 176000, | 
| 135 |  |  |  |  |  |  | 12 => 192000, | 
| 136 |  |  |  |  |  |  | 13 => 224000, | 
| 137 |  |  |  |  |  |  | 14 => 256000, | 
| 138 |  |  |  |  |  |  | }, | 
| 139 |  |  |  |  |  |  | PrintConv => 'ConvertBitrate($val)', | 
| 140 |  |  |  |  |  |  | }, | 
| 141 |  |  |  |  |  |  | { | 
| 142 |  |  |  |  |  |  | Name => 'AudioBitrate', | 
| 143 |  |  |  |  |  |  | Condition => '$self->{MPEG_Vers} != 3 and $self->{MPEG_Layer}', | 
| 144 |  |  |  |  |  |  | Notes => 'version 2 or 2.5, layer 2 or 3', | 
| 145 |  |  |  |  |  |  | PrintConvColumns => 3, | 
| 146 |  |  |  |  |  |  | ValueConv => { | 
| 147 |  |  |  |  |  |  | 0 => 'free', | 
| 148 |  |  |  |  |  |  | 1 => 8000, | 
| 149 |  |  |  |  |  |  | 2 => 16000, | 
| 150 |  |  |  |  |  |  | 3 => 24000, | 
| 151 |  |  |  |  |  |  | 4 => 32000, | 
| 152 |  |  |  |  |  |  | 5 => 40000, | 
| 153 |  |  |  |  |  |  | 6 => 48000, | 
| 154 |  |  |  |  |  |  | 7 => 56000, | 
| 155 |  |  |  |  |  |  | 8 => 64000, | 
| 156 |  |  |  |  |  |  | 9 => 80000, | 
| 157 |  |  |  |  |  |  | 10 => 96000, | 
| 158 |  |  |  |  |  |  | 11 => 112000, | 
| 159 |  |  |  |  |  |  | 12 => 128000, | 
| 160 |  |  |  |  |  |  | 13 => 144000, | 
| 161 |  |  |  |  |  |  | 14 => 160000, | 
| 162 |  |  |  |  |  |  | }, | 
| 163 |  |  |  |  |  |  | PrintConv => 'ConvertBitrate($val)', | 
| 164 |  |  |  |  |  |  | }, | 
| 165 |  |  |  |  |  |  | ], | 
| 166 |  |  |  |  |  |  | 'Bit20-21' => [ | 
| 167 |  |  |  |  |  |  | { | 
| 168 |  |  |  |  |  |  | Name => 'SampleRate', | 
| 169 |  |  |  |  |  |  | Condition => '$self->{MPEG_Vers} == 3', | 
| 170 |  |  |  |  |  |  | Notes => 'version 1', | 
| 171 |  |  |  |  |  |  | PrintConv => { | 
| 172 |  |  |  |  |  |  | 0 => 44100, | 
| 173 |  |  |  |  |  |  | 1 => 48000, | 
| 174 |  |  |  |  |  |  | 2 => 32000, | 
| 175 |  |  |  |  |  |  | }, | 
| 176 |  |  |  |  |  |  | }, | 
| 177 |  |  |  |  |  |  | { | 
| 178 |  |  |  |  |  |  | Name => 'SampleRate', | 
| 179 |  |  |  |  |  |  | Condition => '$self->{MPEG_Vers} == 2', | 
| 180 |  |  |  |  |  |  | Notes => 'version 2', | 
| 181 |  |  |  |  |  |  | PrintConv => { | 
| 182 |  |  |  |  |  |  | 0 => 22050, | 
| 183 |  |  |  |  |  |  | 1 => 24000, | 
| 184 |  |  |  |  |  |  | 2 => 16000, | 
| 185 |  |  |  |  |  |  | }, | 
| 186 |  |  |  |  |  |  | }, | 
| 187 |  |  |  |  |  |  | { | 
| 188 |  |  |  |  |  |  | Name => 'SampleRate', | 
| 189 |  |  |  |  |  |  | Condition => '$self->{MPEG_Vers} == 0', | 
| 190 |  |  |  |  |  |  | Notes => 'version 2.5', | 
| 191 |  |  |  |  |  |  | PrintConv => { | 
| 192 |  |  |  |  |  |  | 0 => 11025, | 
| 193 |  |  |  |  |  |  | 1 => 12000, | 
| 194 |  |  |  |  |  |  | 2 => 8000, | 
| 195 |  |  |  |  |  |  | }, | 
| 196 |  |  |  |  |  |  | }, | 
| 197 |  |  |  |  |  |  | ], | 
| 198 |  |  |  |  |  |  | # Bit 22 - padding flag | 
| 199 |  |  |  |  |  |  | # Bit 23 - private bit | 
| 200 |  |  |  |  |  |  | 'Bit24-25' => { | 
| 201 |  |  |  |  |  |  | Name => 'ChannelMode', | 
| 202 |  |  |  |  |  |  | RawConv => '$self->{MPEG_Mode} = $val', | 
| 203 |  |  |  |  |  |  | PrintConv => { | 
| 204 |  |  |  |  |  |  | 0 => 'Stereo', | 
| 205 |  |  |  |  |  |  | 1 => 'Joint Stereo', | 
| 206 |  |  |  |  |  |  | 2 => 'Dual Channel', | 
| 207 |  |  |  |  |  |  | 3 => 'Single Channel', | 
| 208 |  |  |  |  |  |  | }, | 
| 209 |  |  |  |  |  |  | }, | 
| 210 |  |  |  |  |  |  | 'Bit26' => { | 
| 211 |  |  |  |  |  |  | Name => 'MSStereo', | 
| 212 |  |  |  |  |  |  | Condition => '$self->{MPEG_Layer} == 1', | 
| 213 |  |  |  |  |  |  | Notes => 'layer 3', | 
| 214 |  |  |  |  |  |  | PrintConv => { 0 => 'Off', 1 => 'On' }, | 
| 215 |  |  |  |  |  |  | }, | 
| 216 |  |  |  |  |  |  | 'Bit27' => { | 
| 217 |  |  |  |  |  |  | Name => 'IntensityStereo', | 
| 218 |  |  |  |  |  |  | Condition => '$self->{MPEG_Layer} == 1', | 
| 219 |  |  |  |  |  |  | Notes => 'layer 3', | 
| 220 |  |  |  |  |  |  | PrintConv => { 0 => 'Off', 1 => 'On' }, | 
| 221 |  |  |  |  |  |  | }, | 
| 222 |  |  |  |  |  |  | 'Bit26-27' => { | 
| 223 |  |  |  |  |  |  | Name => 'ModeExtension', | 
| 224 |  |  |  |  |  |  | Condition => '$self->{MPEG_Layer} > 1', | 
| 225 |  |  |  |  |  |  | Notes => 'layer 1 or 2', | 
| 226 |  |  |  |  |  |  | PrintConv => { | 
| 227 |  |  |  |  |  |  | 0 => 'Bands 4-31', | 
| 228 |  |  |  |  |  |  | 1 => 'Bands 8-31', | 
| 229 |  |  |  |  |  |  | 2 => 'Bands 12-31', | 
| 230 |  |  |  |  |  |  | 3 => 'Bands 16-31', | 
| 231 |  |  |  |  |  |  | }, | 
| 232 |  |  |  |  |  |  | }, | 
| 233 |  |  |  |  |  |  | 'Bit28'    => { | 
| 234 |  |  |  |  |  |  | Name => 'CopyrightFlag', | 
| 235 |  |  |  |  |  |  | PrintConv => { | 
| 236 |  |  |  |  |  |  | 0 => 'False', | 
| 237 |  |  |  |  |  |  | 1 => 'True', | 
| 238 |  |  |  |  |  |  | }, | 
| 239 |  |  |  |  |  |  | }, | 
| 240 |  |  |  |  |  |  | 'Bit29'    => { | 
| 241 |  |  |  |  |  |  | Name => 'OriginalMedia', | 
| 242 |  |  |  |  |  |  | PrintConv => { | 
| 243 |  |  |  |  |  |  | 0 => 'False', | 
| 244 |  |  |  |  |  |  | 1 => 'True', | 
| 245 |  |  |  |  |  |  | }, | 
| 246 |  |  |  |  |  |  | }, | 
| 247 |  |  |  |  |  |  | 'Bit30-31' => { | 
| 248 |  |  |  |  |  |  | Name => 'Emphasis', | 
| 249 |  |  |  |  |  |  | PrintConv => { | 
| 250 |  |  |  |  |  |  | 0 => 'None', | 
| 251 |  |  |  |  |  |  | 1 => '50/15 ms', | 
| 252 |  |  |  |  |  |  | 2 => 'reserved', | 
| 253 |  |  |  |  |  |  | 3 => 'CCIT J.17', | 
| 254 |  |  |  |  |  |  | }, | 
| 255 |  |  |  |  |  |  | }, | 
| 256 |  |  |  |  |  |  | ); | 
| 257 |  |  |  |  |  |  |  | 
| 258 |  |  |  |  |  |  | %Image::ExifTool::MPEG::Video = ( | 
| 259 |  |  |  |  |  |  | GROUPS => { 2 => 'Video' }, | 
| 260 |  |  |  |  |  |  | 'Bit00-11' => 'ImageWidth', | 
| 261 |  |  |  |  |  |  | 'Bit12-23' => 'ImageHeight', | 
| 262 |  |  |  |  |  |  | 'Bit24-27' => { | 
| 263 |  |  |  |  |  |  | Name => 'AspectRatio', | 
| 264 |  |  |  |  |  |  | ValueConv => { | 
| 265 |  |  |  |  |  |  | 1 => 1, | 
| 266 |  |  |  |  |  |  | 2 => 0.6735, | 
| 267 |  |  |  |  |  |  | 3 => 0.7031, | 
| 268 |  |  |  |  |  |  | 4 => 0.7615, | 
| 269 |  |  |  |  |  |  | 5 => 0.8055, | 
| 270 |  |  |  |  |  |  | 6 => 0.8437, | 
| 271 |  |  |  |  |  |  | 7 => 0.8935, | 
| 272 |  |  |  |  |  |  | 8 => 0.9157, | 
| 273 |  |  |  |  |  |  | 9 => 0.9815, | 
| 274 |  |  |  |  |  |  | 10 => 1.0255, | 
| 275 |  |  |  |  |  |  | 11 => 1.0695, | 
| 276 |  |  |  |  |  |  | 12 => 1.0950, | 
| 277 |  |  |  |  |  |  | 13 => 1.1575, | 
| 278 |  |  |  |  |  |  | 14 => 1.2015, | 
| 279 |  |  |  |  |  |  | }, | 
| 280 |  |  |  |  |  |  | PrintConv => { | 
| 281 |  |  |  |  |  |  | 1      => '1:1', | 
| 282 |  |  |  |  |  |  | 0.6735 => '0.6735', | 
| 283 |  |  |  |  |  |  | 0.7031 => '16:9, 625 line, PAL', | 
| 284 |  |  |  |  |  |  | 0.7615 => '0.7615', | 
| 285 |  |  |  |  |  |  | 0.8055 => '0.8055', | 
| 286 |  |  |  |  |  |  | 0.8437 => '16:9, 525 line, NTSC', | 
| 287 |  |  |  |  |  |  | 0.8935 => '0.8935', | 
| 288 |  |  |  |  |  |  | 0.9157 => '4:3, 625 line, PAL, CCIR601', | 
| 289 |  |  |  |  |  |  | 0.9815 => '0.9815', | 
| 290 |  |  |  |  |  |  | 1.0255 => '1.0255', | 
| 291 |  |  |  |  |  |  | 1.0695 => '1.0695', | 
| 292 |  |  |  |  |  |  | 1.0950 => '4:3, 525 line, NTSC, CCIR601', | 
| 293 |  |  |  |  |  |  | 1.1575 => '1.1575', | 
| 294 |  |  |  |  |  |  | 1.2015 => '1.2015', | 
| 295 |  |  |  |  |  |  | }, | 
| 296 |  |  |  |  |  |  | }, | 
| 297 |  |  |  |  |  |  | 'Bit28-31' => { | 
| 298 |  |  |  |  |  |  | Name => 'FrameRate', | 
| 299 |  |  |  |  |  |  | ValueConv => { | 
| 300 |  |  |  |  |  |  | 1 => 23.976, | 
| 301 |  |  |  |  |  |  | 2 => 24, | 
| 302 |  |  |  |  |  |  | 3 => 25, | 
| 303 |  |  |  |  |  |  | 4 => 29.97, | 
| 304 |  |  |  |  |  |  | 5 => 30, | 
| 305 |  |  |  |  |  |  | 6 => 50, | 
| 306 |  |  |  |  |  |  | 7 => 59.94, | 
| 307 |  |  |  |  |  |  | 8 => 60, | 
| 308 |  |  |  |  |  |  | }, | 
| 309 |  |  |  |  |  |  | PrintConv => '"$val fps"', | 
| 310 |  |  |  |  |  |  | }, | 
| 311 |  |  |  |  |  |  | 'Bit32-49' => { | 
| 312 |  |  |  |  |  |  | Name => 'VideoBitrate', | 
| 313 |  |  |  |  |  |  | ValueConv => '$val eq 0x3ffff ? "Variable" : $val * 400', | 
| 314 |  |  |  |  |  |  | PrintConv => 'ConvertBitrate($val)', | 
| 315 |  |  |  |  |  |  | }, | 
| 316 |  |  |  |  |  |  | # these tags not very interesting | 
| 317 |  |  |  |  |  |  | #'Bit50'    => 'MarkerBit', | 
| 318 |  |  |  |  |  |  | #'Bit51-60' => 'VBVBufferSize', | 
| 319 |  |  |  |  |  |  | #'Bit61'    => 'ConstrainedParamFlag', | 
| 320 |  |  |  |  |  |  | #'Bit62'    => 'IntraQuantMatrixFlag', | 
| 321 |  |  |  |  |  |  | ); | 
| 322 |  |  |  |  |  |  |  | 
| 323 |  |  |  |  |  |  | %Image::ExifTool::MPEG::Xing = ( | 
| 324 |  |  |  |  |  |  | GROUPS => { 2 => 'Audio' }, | 
| 325 |  |  |  |  |  |  | VARS => { NO_ID => 1 }, | 
| 326 |  |  |  |  |  |  | NOTES => 'These tags are extracted from the Xing/Info frame.', | 
| 327 |  |  |  |  |  |  | 1 => { Name => 'VBRFrames' }, | 
| 328 |  |  |  |  |  |  | 2 => { Name => 'VBRBytes' }, | 
| 329 |  |  |  |  |  |  | 3 => { Name => 'VBRScale' }, | 
| 330 |  |  |  |  |  |  | 4 => { Name => 'Encoder' }, | 
| 331 |  |  |  |  |  |  | 5 => { Name => 'LameVBRQuality' }, | 
| 332 |  |  |  |  |  |  | 6 => { Name => 'LameQuality' }, | 
| 333 |  |  |  |  |  |  | 7 => { # (for documentation only) | 
| 334 |  |  |  |  |  |  | Name => 'LameHeader', | 
| 335 |  |  |  |  |  |  | SubDirectory => { TagTable => 'Image::ExifTool::MPEG::Lame' }, | 
| 336 |  |  |  |  |  |  | }, | 
| 337 |  |  |  |  |  |  | ); | 
| 338 |  |  |  |  |  |  |  | 
| 339 |  |  |  |  |  |  | # Lame header tags (ref 5) | 
| 340 |  |  |  |  |  |  | %Image::ExifTool::MPEG::Lame = ( | 
| 341 |  |  |  |  |  |  | PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, | 
| 342 |  |  |  |  |  |  | GROUPS => { 2 => 'Audio' }, | 
| 343 |  |  |  |  |  |  | NOTES => 'Tags extracted from Lame 3.90 or later header.', | 
| 344 |  |  |  |  |  |  | 9 => { | 
| 345 |  |  |  |  |  |  | Name => 'LameMethod', | 
| 346 |  |  |  |  |  |  | Mask => 0x0f, | 
| 347 |  |  |  |  |  |  | PrintConv => { | 
| 348 |  |  |  |  |  |  | 1 => 'CBR', | 
| 349 |  |  |  |  |  |  | 2 => 'ABR', | 
| 350 |  |  |  |  |  |  | 3 => 'VBR (old/rh)', | 
| 351 |  |  |  |  |  |  | 4 => 'VBR (new/mtrh)', | 
| 352 |  |  |  |  |  |  | 5 => 'VBR (old/rh)', | 
| 353 |  |  |  |  |  |  | 6 => 'VBR', | 
| 354 |  |  |  |  |  |  | 8 => 'CBR (2-pass)', | 
| 355 |  |  |  |  |  |  | 9 => 'ABR (2-pass)', | 
| 356 |  |  |  |  |  |  | }, | 
| 357 |  |  |  |  |  |  | }, | 
| 358 |  |  |  |  |  |  | 10 => { | 
| 359 |  |  |  |  |  |  | Name => 'LameLowPassFilter', | 
| 360 |  |  |  |  |  |  | ValueConv => '$val * 100', | 
| 361 |  |  |  |  |  |  | PrintConv => '($val / 1000) . " kHz"', | 
| 362 |  |  |  |  |  |  | }, | 
| 363 |  |  |  |  |  |  | # 19 - EncodingFlags | 
| 364 |  |  |  |  |  |  | 20 => { | 
| 365 |  |  |  |  |  |  | Name => 'LameBitrate', | 
| 366 |  |  |  |  |  |  | ValueConv => '$val * 1000', | 
| 367 |  |  |  |  |  |  | PrintConv => 'ConvertBitrate($val)', | 
| 368 |  |  |  |  |  |  | }, | 
| 369 |  |  |  |  |  |  | 24 => { | 
| 370 |  |  |  |  |  |  | Name => 'LameStereoMode', | 
| 371 |  |  |  |  |  |  | Mask => 0x1c, | 
| 372 |  |  |  |  |  |  | PrintConv => { | 
| 373 |  |  |  |  |  |  | 0 => 'Mono', | 
| 374 |  |  |  |  |  |  | 1 => 'Stereo', | 
| 375 |  |  |  |  |  |  | 2 => 'Dual Channels', | 
| 376 |  |  |  |  |  |  | 3 => 'Joint Stereo', | 
| 377 |  |  |  |  |  |  | 4 => 'Forced Joint Stereo', | 
| 378 |  |  |  |  |  |  | 6 => 'Auto', | 
| 379 |  |  |  |  |  |  | 7 => 'Intensity Stereo', | 
| 380 |  |  |  |  |  |  | }, | 
| 381 |  |  |  |  |  |  | }, | 
| 382 |  |  |  |  |  |  | ); | 
| 383 |  |  |  |  |  |  |  | 
| 384 |  |  |  |  |  |  | # composite tags | 
| 385 |  |  |  |  |  |  | %Image::ExifTool::MPEG::Composite = ( | 
| 386 |  |  |  |  |  |  | Duration => { | 
| 387 |  |  |  |  |  |  | Groups => { 2 => 'Video' }, | 
| 388 |  |  |  |  |  |  | Require => { | 
| 389 |  |  |  |  |  |  | 0 => 'FileSize', | 
| 390 |  |  |  |  |  |  | }, | 
| 391 |  |  |  |  |  |  | Desire => { | 
| 392 |  |  |  |  |  |  | 1 => 'ID3Size', | 
| 393 |  |  |  |  |  |  | 2 => 'MPEG:AudioBitrate', | 
| 394 |  |  |  |  |  |  | 3 => 'MPEG:VideoBitrate', | 
| 395 |  |  |  |  |  |  | 4 => 'MPEG:VBRFrames', | 
| 396 |  |  |  |  |  |  | 5 => 'MPEG:SampleRate', | 
| 397 |  |  |  |  |  |  | 6 => 'MPEG:MPEGAudioVersion', | 
| 398 |  |  |  |  |  |  | }, | 
| 399 |  |  |  |  |  |  | Priority => -1, # (don't want to replace any other Duration tag) | 
| 400 |  |  |  |  |  |  | ValueConv => q{ | 
| 401 |  |  |  |  |  |  | if ($val[4] and defined $val[5] and defined $val[6]) { | 
| 402 |  |  |  |  |  |  | # calculate from number of VBR audio frames | 
| 403 |  |  |  |  |  |  | my $mfs = $prt[5] / ($val[6] == 3 ? 144 : 72); | 
| 404 |  |  |  |  |  |  | # calculate using VBR length | 
| 405 |  |  |  |  |  |  | return 8 * $val[4] / $mfs; | 
| 406 |  |  |  |  |  |  | } | 
| 407 |  |  |  |  |  |  | # calculate duration as file size divided by total bitrate | 
| 408 |  |  |  |  |  |  | # (note: this is only approximate!) | 
| 409 |  |  |  |  |  |  | return undef unless $val[2] or $val[3]; | 
| 410 |  |  |  |  |  |  | return undef if $val[2] and not $val[2] =~ /^\d+$/; | 
| 411 |  |  |  |  |  |  | return undef if $val[3] and not $val[3] =~ /^\d+$/; | 
| 412 |  |  |  |  |  |  | return (8 * ($val[0] - ($val[1]||0))) / (($val[2]||0) + ($val[3]||0)); | 
| 413 |  |  |  |  |  |  | }, | 
| 414 |  |  |  |  |  |  | PrintConv => 'ConvertDuration($val) . " (approx)"', | 
| 415 |  |  |  |  |  |  | }, | 
| 416 |  |  |  |  |  |  | AudioBitrate => { | 
| 417 |  |  |  |  |  |  | Groups => { 2 => 'Audio' }, | 
| 418 |  |  |  |  |  |  | Notes => 'calculated for variable-bitrate MPEG audio', | 
| 419 |  |  |  |  |  |  | Require => { | 
| 420 |  |  |  |  |  |  | 0 => 'MPEG:MPEGAudioVersion', | 
| 421 |  |  |  |  |  |  | 1 => 'MPEG:SampleRate', | 
| 422 |  |  |  |  |  |  | 2 => 'MPEG:VBRBytes', | 
| 423 |  |  |  |  |  |  | 3 => 'MPEG:VBRFrames', | 
| 424 |  |  |  |  |  |  | }, | 
| 425 |  |  |  |  |  |  | ValueConv => q{ | 
| 426 |  |  |  |  |  |  | return undef unless $val[3]; | 
| 427 |  |  |  |  |  |  | my $mfs = $prt[1] / ($val[0] == 3 ? 144 : 72); | 
| 428 |  |  |  |  |  |  | return $mfs * $val[2] / $val[3]; | 
| 429 |  |  |  |  |  |  | }, | 
| 430 |  |  |  |  |  |  | PrintConv => 'ConvertBitrate($val)', | 
| 431 |  |  |  |  |  |  | }, | 
| 432 |  |  |  |  |  |  | ); | 
| 433 |  |  |  |  |  |  |  | 
| 434 |  |  |  |  |  |  | # add our composite tags | 
| 435 |  |  |  |  |  |  | Image::ExifTool::AddCompositeTags('Image::ExifTool::MPEG'); | 
| 436 |  |  |  |  |  |  |  | 
| 437 |  |  |  |  |  |  |  | 
| 438 |  |  |  |  |  |  | #------------------------------------------------------------------------------ | 
| 439 |  |  |  |  |  |  | # Process information in an MPEG audio or video frame header | 
| 440 |  |  |  |  |  |  | # Inputs: 0) ExifTool object ref, 1) tag table ref, 2-N) list of 32-bit data words | 
| 441 |  |  |  |  |  |  | sub ProcessFrameHeader($$@) | 
| 442 |  |  |  |  |  |  | { | 
| 443 | 1 |  |  | 1 | 0 | 12 | my ($et, $tagTablePtr, @data) = @_; | 
| 444 | 1 |  |  |  |  | 4 | my $tag; | 
| 445 | 1 |  |  |  |  | 10 | foreach $tag (sort keys %$tagTablePtr) { | 
| 446 | 15 | 100 |  |  |  | 68 | next unless $tag =~ /^Bit(\d{2})-?(\d{2})?/; | 
| 447 | 11 |  | 66 |  |  | 50 | my ($b1, $b2) = ($1, $2 || $1); | 
| 448 | 11 |  |  |  |  | 42 | my $index = int($b1 / 32); | 
| 449 | 11 |  |  |  |  | 21 | my $word = $data[$index]; | 
| 450 | 11 |  |  |  |  | 15 | my $mask = 0; | 
| 451 | 11 |  |  |  |  | 29 | foreach (0 .. ($b2 - $b1)) { | 
| 452 | 20 |  |  |  |  | 34 | $mask += (1 << $_); | 
| 453 |  |  |  |  |  |  | } | 
| 454 | 11 |  |  |  |  | 23 | my $val = ($word >> (31 + 32*$index - $b2)) & $mask; | 
| 455 | 11 |  |  |  |  | 32 | $et->HandleTag($tagTablePtr, $tag, $val); | 
| 456 |  |  |  |  |  |  | } | 
| 457 |  |  |  |  |  |  | } | 
| 458 |  |  |  |  |  |  |  | 
| 459 |  |  |  |  |  |  | #------------------------------------------------------------------------------ | 
| 460 |  |  |  |  |  |  | # Read MPEG audio frame header | 
| 461 |  |  |  |  |  |  | # Inputs: 0) ExifTool object reference, 1) Reference to audio data | 
| 462 |  |  |  |  |  |  | #         2) flag set if we are trying to recognized MP3 file only | 
| 463 |  |  |  |  |  |  | # Returns: 1 on success, 0 if no audio header was found | 
| 464 |  |  |  |  |  |  | sub ParseMPEGAudio($$;$) | 
| 465 |  |  |  |  |  |  | { | 
| 466 | 9 |  |  | 9 | 0 | 31 | my ($et, $buffPt, $mp3) = @_; | 
| 467 | 9 |  |  |  |  | 21 | my ($word, $pos); | 
| 468 | 9 |  | 100 |  |  | 51 | my $ext = $$et{FILE_EXT} || ''; | 
| 469 |  |  |  |  |  |  |  | 
| 470 | 9 |  |  |  |  | 21 | for (;;) { | 
| 471 |  |  |  |  |  |  | # find frame sync | 
| 472 | 9 | 100 |  |  |  | 93 | return 0 unless $$buffPt =~ m{(\xff.{3})}sg; | 
| 473 | 1 |  |  |  |  | 5 | $word = unpack('N', $1);    # get audio frame header word | 
| 474 | 1 | 50 |  |  |  | 4 | unless (($word & 0xffe00000) == 0xffe00000) { | 
| 475 | 0 |  |  |  |  | 0 | pos($$buffPt) = pos($$buffPt) - 2;  # next possible location for frame sync | 
| 476 | 0 |  |  |  |  | 0 | next; | 
| 477 |  |  |  |  |  |  | } | 
| 478 |  |  |  |  |  |  | # validate header as much as possible | 
| 479 | 1 | 50 | 33 |  |  | 20 | if (($word & 0x180000) == 0x080000 or   # 01 is a reserved version ID | 
|  |  |  | 33 |  |  |  |  | 
|  |  |  | 33 |  |  |  |  | 
|  |  |  | 33 |  |  |  |  | 
|  |  |  | 33 |  |  |  |  | 
|  |  |  | 33 |  |  |  |  | 
|  |  |  | 33 |  |  |  |  | 
| 480 |  |  |  |  |  |  | ($word & 0x060000) == 0x000000 or   # 00 is a reserved layer description | 
| 481 |  |  |  |  |  |  | ($word & 0x00f000) == 0x000000 or   # 0000 is the "free" bitrate index | 
| 482 |  |  |  |  |  |  | ($word & 0x00f000) == 0x00f000 or   # 1111 is a bad bitrate index | 
| 483 |  |  |  |  |  |  | ($word & 0x000c00) == 0x000c00 or   # 11 is a reserved sampling frequency | 
| 484 |  |  |  |  |  |  | ($word & 0x000003) == 0x000002 or   # 10 is a reserved emphasis | 
| 485 |  |  |  |  |  |  | (($mp3 and ($word & 0x060000) != 0x020000))) # must be layer 3 for MP3 | 
| 486 |  |  |  |  |  |  | { | 
| 487 |  |  |  |  |  |  | # give up easily unless this really should be an MP3 file | 
| 488 | 0 | 0 |  |  |  | 0 | return 0 unless $ext eq 'MP3'; | 
| 489 | 0 |  |  |  |  | 0 | pos($$buffPt) = pos($$buffPt) - 1; | 
| 490 | 0 |  |  |  |  | 0 | next; | 
| 491 |  |  |  |  |  |  | } | 
| 492 | 1 |  |  |  |  | 3 | $pos = pos($$buffPt); | 
| 493 | 1 |  |  |  |  | 2 | last; | 
| 494 |  |  |  |  |  |  | } | 
| 495 |  |  |  |  |  |  | # set file type if not done already | 
| 496 | 1 |  |  |  |  | 6 | $et->SetFileType(); | 
| 497 |  |  |  |  |  |  |  | 
| 498 | 1 |  |  |  |  | 4 | my $tagTablePtr = GetTagTable('Image::ExifTool::MPEG::Audio'); | 
| 499 | 1 |  |  |  |  | 4 | ProcessFrameHeader($et, $tagTablePtr, $word); | 
| 500 |  |  |  |  |  |  |  | 
| 501 |  |  |  |  |  |  | # extract the VBR information (ref MP3::Info) | 
| 502 | 1 |  |  |  |  | 8 | my ($v, $m) = ($$et{MPEG_Vers}, $$et{MPEG_Mode}); | 
| 503 | 1 |  | 33 |  |  | 8 | while (defined $v and defined $m) { | 
| 504 | 1 |  |  |  |  | 3 | my $len = length $$buffPt; | 
| 505 | 1 | 50 |  |  |  | 4 | $pos += $v == 3 ? ($m == 3 ? 17 : 32) : ($m == 3 ?  9 : 17); | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
| 506 | 1 | 50 |  |  |  | 4 | last if $pos + 8 > $len; | 
| 507 | 1 |  |  |  |  | 4 | my $buff = substr($$buffPt, $pos, 8); | 
| 508 | 1 | 50 |  |  |  | 6 | last unless $buff =~ /^(Xing|Info)/; | 
| 509 | 0 |  |  |  |  | 0 | my $xingTable = GetTagTable('Image::ExifTool::MPEG::Xing'); | 
| 510 | 0 |  |  |  |  | 0 | my $vbrScale; | 
| 511 | 0 |  |  |  |  | 0 | my $flags = unpack('x4N', $buff); | 
| 512 | 0 |  |  |  |  | 0 | my $isVBR = ($buff !~ /^Info/);     # Info frame is not VBR (ref 5) | 
| 513 | 0 |  |  |  |  | 0 | $pos += 8; | 
| 514 | 0 | 0 |  |  |  | 0 | if ($flags & 0x01) {    # VBRFrames | 
| 515 | 0 | 0 |  |  |  | 0 | last if $pos + 4 > $len; | 
| 516 | 0 | 0 |  |  |  | 0 | $et->HandleTag($xingTable, 1, unpack("x${pos}N", $$buffPt)) if $isVBR; | 
| 517 | 0 |  |  |  |  | 0 | $pos += 4; | 
| 518 |  |  |  |  |  |  | } | 
| 519 | 0 | 0 |  |  |  | 0 | if ($flags & 0x02) {    # VBRBytes | 
| 520 | 0 | 0 |  |  |  | 0 | last if $pos + 4 > $len; | 
| 521 | 0 | 0 |  |  |  | 0 | $et->HandleTag($xingTable, 2, unpack("x${pos}N", $$buffPt)) if $isVBR; | 
| 522 | 0 |  |  |  |  | 0 | $pos += 4; | 
| 523 |  |  |  |  |  |  | } | 
| 524 | 0 | 0 |  |  |  | 0 | if ($flags & 0x04) {    # VBR_TOC | 
| 525 | 0 | 0 |  |  |  | 0 | last if $pos + 100 > $len; | 
| 526 |  |  |  |  |  |  | # (ignore toc for now) | 
| 527 | 0 |  |  |  |  | 0 | $pos += 100; | 
| 528 |  |  |  |  |  |  | } | 
| 529 | 0 | 0 |  |  |  | 0 | if ($flags & 0x08) {    # VBRScale | 
| 530 | 0 | 0 |  |  |  | 0 | last if $pos + 4 > $len; | 
| 531 | 0 |  |  |  |  | 0 | $vbrScale = unpack("x${pos}N", $$buffPt); | 
| 532 | 0 | 0 |  |  |  | 0 | $et->HandleTag($xingTable, 3, $vbrScale) if $isVBR; | 
| 533 | 0 |  |  |  |  | 0 | $pos += 4; | 
| 534 |  |  |  |  |  |  | } | 
| 535 |  |  |  |  |  |  | # process Lame header (ref 5) | 
| 536 | 0 | 0 |  |  |  | 0 | if ($flags & 0x10) {    # Lame | 
|  |  | 0 |  |  |  |  |  | 
| 537 | 0 | 0 |  |  |  | 0 | last if $pos + 348 > $len; | 
| 538 |  |  |  |  |  |  | } elsif ($pos + 4 <= $len) { | 
| 539 | 0 |  |  |  |  | 0 | my $lib = substr($$buffPt, $pos, 4); | 
| 540 | 0 | 0 | 0 |  |  | 0 | unless ($lib eq 'LAME' or $lib eq 'GOGO') { | 
| 541 |  |  |  |  |  |  | # attempt to identify other encoders | 
| 542 | 0 |  |  |  |  | 0 | my $n; | 
| 543 | 0 | 0 |  |  |  | 0 | if (index($$buffPt, 'RCA mp3PRO Encoder') >= 0) { | 
|  |  | 0 |  |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 544 | 0 |  |  |  |  | 0 | $lib = 'RCA mp3PRO'; | 
| 545 |  |  |  |  |  |  | } elsif (($n = index($$buffPt, 'THOMSON mp3PRO Encoder')) >= 0) { | 
| 546 | 0 |  |  |  |  | 0 | $lib = 'Thomson mp3PRO'; | 
| 547 | 0 |  |  |  |  | 0 | $n += 22; | 
| 548 | 0 | 0 |  |  |  | 0 | $lib .= ' ' . substr($$buffPt, $n, 6) if length($$buffPt) - $n >= 6; | 
| 549 |  |  |  |  |  |  | } elsif (index($$buffPt, 'MPGE') >= 0) { | 
| 550 | 0 |  |  |  |  | 0 | $lib = 'Gogo (<3.0)'; | 
| 551 |  |  |  |  |  |  | } else { | 
| 552 | 0 |  |  |  |  | 0 | last; | 
| 553 |  |  |  |  |  |  | } | 
| 554 | 0 |  |  |  |  | 0 | $et->HandleTag($xingTable, 4, $lib); | 
| 555 | 0 |  |  |  |  | 0 | last; | 
| 556 |  |  |  |  |  |  | } | 
| 557 |  |  |  |  |  |  | } | 
| 558 | 0 |  |  |  |  | 0 | my $lameLen = $len - $pos; | 
| 559 | 0 | 0 |  |  |  | 0 | last if $lameLen < 9; | 
| 560 | 0 |  |  |  |  | 0 | my $enc = substr($$buffPt, $pos, 9); | 
| 561 | 0 | 0 |  |  |  | 0 | if ($enc ge 'LAME3.90') { | 
| 562 | 0 |  |  |  |  | 0 | $et->HandleTag($xingTable, 4, $enc); | 
| 563 | 0 | 0 |  |  |  | 0 | if ($vbrScale <= 100) { | 
| 564 | 0 |  |  |  |  | 0 | $et->HandleTag($xingTable, 5, int((100 - $vbrScale) / 10)); | 
| 565 | 0 |  |  |  |  | 0 | $et->HandleTag($xingTable, 6, (100 - $vbrScale) % 10); | 
| 566 |  |  |  |  |  |  | } | 
| 567 | 0 |  |  |  |  | 0 | my %dirInfo = ( | 
| 568 |  |  |  |  |  |  | DataPt   => $buffPt, | 
| 569 |  |  |  |  |  |  | DirStart => $pos, | 
| 570 |  |  |  |  |  |  | DirLen   => length($$buffPt) - $pos, | 
| 571 |  |  |  |  |  |  | ); | 
| 572 | 0 |  |  |  |  | 0 | my $subTablePtr = GetTagTable('Image::ExifTool::MPEG::Lame'); | 
| 573 | 0 |  |  |  |  | 0 | $et->ProcessDirectory(\%dirInfo, $subTablePtr); | 
| 574 |  |  |  |  |  |  | } else { | 
| 575 | 0 |  |  |  |  | 0 | $et->HandleTag($xingTable, 4, substr($$buffPt, $pos, 20)); | 
| 576 |  |  |  |  |  |  | } | 
| 577 | 0 |  |  |  |  | 0 | last;   # (didn't want to loop anyway) | 
| 578 |  |  |  |  |  |  | } | 
| 579 |  |  |  |  |  |  |  | 
| 580 | 1 |  |  |  |  | 5 | return 1; | 
| 581 |  |  |  |  |  |  | } | 
| 582 |  |  |  |  |  |  |  | 
| 583 |  |  |  |  |  |  | #------------------------------------------------------------------------------ | 
| 584 |  |  |  |  |  |  | # Read MPEG video frame header | 
| 585 |  |  |  |  |  |  | # Inputs: 0) ExifTool object reference, 1) Reference to video data | 
| 586 |  |  |  |  |  |  | # Returns: 1 on success, 0 if no video header was found | 
| 587 |  |  |  |  |  |  | sub ProcessMPEGVideo($$) | 
| 588 |  |  |  |  |  |  | { | 
| 589 | 0 |  |  | 0 | 0 |  | my ($et, $buffPt) = @_; | 
| 590 |  |  |  |  |  |  |  | 
| 591 | 0 | 0 |  |  |  |  | return 0 unless length $$buffPt >= 4; | 
| 592 | 0 |  |  |  |  |  | my ($w1, $w2) = unpack('N2', $$buffPt); | 
| 593 |  |  |  |  |  |  | # validate as much as possible | 
| 594 | 0 | 0 | 0 |  |  |  | if (($w1 & 0x000000f0) == 0x00000000 or     # 0000 is a forbidden aspect ratio | 
|  |  |  | 0 |  |  |  |  | 
|  |  |  | 0 |  |  |  |  | 
| 595 |  |  |  |  |  |  | ($w1 & 0x000000f0) == 0x000000f0 or     # 1111 is a reserved aspect ratio | 
| 596 |  |  |  |  |  |  | ($w1 & 0x0000000f) == 0 or              # frame rate must be 1-8 | 
| 597 |  |  |  |  |  |  | ($w1 & 0x0000000f) > 8) | 
| 598 |  |  |  |  |  |  | { | 
| 599 | 0 |  |  |  |  |  | return 0; | 
| 600 |  |  |  |  |  |  | } | 
| 601 |  |  |  |  |  |  | # set file type if not done already | 
| 602 | 0 | 0 |  |  |  |  | $et->SetFileType('MPEG') unless $$et{FileType}; | 
| 603 |  |  |  |  |  |  |  | 
| 604 | 0 |  |  |  |  |  | my $tagTablePtr = GetTagTable('Image::ExifTool::MPEG::Video'); | 
| 605 | 0 |  |  |  |  |  | ProcessFrameHeader($et, $tagTablePtr, $w1, $w2); | 
| 606 | 0 |  |  |  |  |  | return 1; | 
| 607 |  |  |  |  |  |  | } | 
| 608 |  |  |  |  |  |  |  | 
| 609 |  |  |  |  |  |  | #------------------------------------------------------------------------------ | 
| 610 |  |  |  |  |  |  | # Read MPEG audio and video frame headers | 
| 611 |  |  |  |  |  |  | # Inputs: 0) ExifTool object reference, 1) Reference to audio/video data | 
| 612 |  |  |  |  |  |  | # Returns: 1 on success, 0 if no video header was found | 
| 613 |  |  |  |  |  |  | # To Do: Properly parse MPEG streams: | 
| 614 |  |  |  |  |  |  | #   0xb7 - sequence end | 
| 615 |  |  |  |  |  |  | #   0xb9 - end code | 
| 616 |  |  |  |  |  |  | #   0xba - pack start code | 
| 617 |  |  |  |  |  |  | #   0xbb - system header | 
| 618 |  |  |  |  |  |  | #   0xbc - program map <-- should parse this | 
| 619 |  |  |  |  |  |  | #   0xbd - private stream 1 --> for VOB, this contains sub-streams: | 
| 620 |  |  |  |  |  |  | #           0x20-0x3f - pictures | 
| 621 |  |  |  |  |  |  | #           0x80-0x87 - audio (AC3,DTS,SDDS) | 
| 622 |  |  |  |  |  |  | #           0xa0-0xa7 - audio (LPCM) | 
| 623 |  |  |  |  |  |  | #   0xbe - padding | 
| 624 |  |  |  |  |  |  | #   0xbf - private stream 2 | 
| 625 |  |  |  |  |  |  | #   0xc0-0xdf - audio stream | 
| 626 |  |  |  |  |  |  | #   0xe0-0xef - video stream | 
| 627 |  |  |  |  |  |  | sub ParseMPEGAudioVideo($$) | 
| 628 |  |  |  |  |  |  | { | 
| 629 | 0 |  |  | 0 | 0 |  | my ($et, $buffPt) = @_; | 
| 630 | 0 |  |  |  |  |  | my (%found, $didHdr); | 
| 631 | 0 |  |  |  |  |  | my $rtnVal = 0; | 
| 632 | 0 |  |  |  |  |  | my %proc = ( audio => \&ParseMPEGAudio, video => \&ProcessMPEGVideo ); | 
| 633 |  |  |  |  |  |  |  | 
| 634 | 0 |  |  |  |  |  | delete $$et{AudioBitrate}; | 
| 635 | 0 |  |  |  |  |  | delete $$et{VideoBitrate}; | 
| 636 |  |  |  |  |  |  |  | 
| 637 | 0 |  |  |  |  |  | while ($$buffPt =~ /\0\0\x01(\xb3|\xc0)/g) { | 
| 638 | 0 | 0 |  |  |  |  | my $type = $1 eq "\xb3" ? 'video' : 'audio'; | 
| 639 | 0 | 0 |  |  |  |  | unless ($didHdr) { | 
| 640 |  |  |  |  |  |  | # make sure we didn't miss an audio frame sync before this (eg. MP3 file) | 
| 641 |  |  |  |  |  |  | # (the last byte of the 4-byte MP3 audio frame header word may be zero, | 
| 642 |  |  |  |  |  |  | # but the 2nd last must be non-zero, so we need only check to pos-3) | 
| 643 | 0 |  |  |  |  |  | my $buff = substr($$buffPt, 0, pos($$buffPt) - 3); | 
| 644 | 0 | 0 |  |  |  |  | $found{audio} = 1 if ParseMPEGAudio($et, \$buff); | 
| 645 | 0 |  |  |  |  |  | $didHdr = 1; | 
| 646 |  |  |  |  |  |  | } | 
| 647 | 0 | 0 |  |  |  |  | next if $found{$type}; | 
| 648 | 0 |  |  |  |  |  | my $len = length($$buffPt) - pos($$buffPt); | 
| 649 | 0 | 0 |  |  |  |  | last if $len < 4; | 
| 650 | 0 | 0 |  |  |  |  | $len > 256 and $len = 256; | 
| 651 | 0 |  |  |  |  |  | my $dat = substr($$buffPt, pos($$buffPt), $len); | 
| 652 |  |  |  |  |  |  | # process MPEG audio or video | 
| 653 | 0 | 0 |  |  |  |  | if (&{$proc{$type}}($et, \$dat)) { | 
|  | 0 |  |  |  |  |  |  | 
| 654 | 0 |  |  |  |  |  | $rtnVal = 1; | 
| 655 | 0 |  |  |  |  |  | $found{$type} = 1; | 
| 656 |  |  |  |  |  |  | # done if we found audio and video | 
| 657 | 0 | 0 |  |  |  |  | last if scalar(keys %found) == 2; | 
| 658 |  |  |  |  |  |  | } | 
| 659 |  |  |  |  |  |  | } | 
| 660 | 0 |  |  |  |  |  | return $rtnVal; | 
| 661 |  |  |  |  |  |  | } | 
| 662 |  |  |  |  |  |  |  | 
| 663 |  |  |  |  |  |  | #------------------------------------------------------------------------------ | 
| 664 |  |  |  |  |  |  | # Read information from an MPEG file | 
| 665 |  |  |  |  |  |  | # Inputs: 0) ExifTool object reference, 1) Directory information reference | 
| 666 |  |  |  |  |  |  | # Returns: 1 on success, 0 if this wasn't a valid MPEG file | 
| 667 |  |  |  |  |  |  | sub ProcessMPEG($$) | 
| 668 |  |  |  |  |  |  | { | 
| 669 | 0 |  |  | 0 | 0 |  | my ($et, $dirInfo) = @_; | 
| 670 | 0 |  |  |  |  |  | my $raf = $$dirInfo{RAF}; | 
| 671 | 0 |  |  |  |  |  | my $buff; | 
| 672 |  |  |  |  |  |  |  | 
| 673 | 0 | 0 |  |  |  |  | $raf->Read($buff, 4) == 4 or return 0; | 
| 674 | 0 | 0 |  |  |  |  | return 0 unless $buff =~ /^\0\0\x01[\xb0-\xbf]/; | 
| 675 | 0 |  |  |  |  |  | $et->SetFileType(); | 
| 676 |  |  |  |  |  |  |  | 
| 677 | 0 |  |  |  |  |  | $raf->Seek(0,0); | 
| 678 | 0 | 0 |  |  |  |  | $raf->Read($buff, 65536*4) or return 0; | 
| 679 |  |  |  |  |  |  |  | 
| 680 | 0 |  |  |  |  |  | return ParseMPEGAudioVideo($et, \$buff); | 
| 681 |  |  |  |  |  |  | } | 
| 682 |  |  |  |  |  |  |  | 
| 683 |  |  |  |  |  |  | 1;  # end | 
| 684 |  |  |  |  |  |  |  | 
| 685 |  |  |  |  |  |  | __END__ |