| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | #------------------------------------------------------------------------------ | 
| 2 |  |  |  |  |  |  | # File:         H264.pm | 
| 3 |  |  |  |  |  |  | # | 
| 4 |  |  |  |  |  |  | # Description:  Read meta information from H.264 video | 
| 5 |  |  |  |  |  |  | # | 
| 6 |  |  |  |  |  |  | # Revisions:    2010/01/31 - P. Harvey Created | 
| 7 |  |  |  |  |  |  | # | 
| 8 |  |  |  |  |  |  | # References:   1) http://www.itu.int/rec/T-REC-H.264/e (T-REC-H.264-200305-S!!PDF-E.pdf) | 
| 9 |  |  |  |  |  |  | #               2) http://miffteevee.co.uk/documentation/development/H264Parser_8cpp-source.html | 
| 10 |  |  |  |  |  |  | #               3) http://ffmpeg.org/ | 
| 11 |  |  |  |  |  |  | #               4) US Patent 2009/0052875 A1 | 
| 12 |  |  |  |  |  |  | #               5) European Patent (EP2 051 528A1) application no. 07792522.0 filed 08.08.2007 | 
| 13 |  |  |  |  |  |  | #               6) Dave Nicholson private communication | 
| 14 |  |  |  |  |  |  | #               7) http://www.freepatentsonline.com/20050076039.pdf | 
| 15 |  |  |  |  |  |  | #               8) Michael Reitinger private communication (RX100) | 
| 16 |  |  |  |  |  |  | # | 
| 17 |  |  |  |  |  |  | # Glossary:     RBSP = Raw Byte Sequence Payload | 
| 18 |  |  |  |  |  |  | #------------------------------------------------------------------------------ | 
| 19 |  |  |  |  |  |  |  | 
| 20 |  |  |  |  |  |  | package Image::ExifTool::H264; | 
| 21 |  |  |  |  |  |  |  | 
| 22 | 1 |  |  | 1 |  | 10 | use strict; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 41 |  | 
| 23 | 1 |  |  | 1 |  | 5 | use vars qw($VERSION %convMake); | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 59 |  | 
| 24 | 1 |  |  | 1 |  | 5 | use Image::ExifTool qw(:DataAccess :Utils); | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 221 |  | 
| 25 | 1 |  |  | 1 |  | 7 | use Image::ExifTool::Exif; | 
|  | 1 |  |  |  |  | 1 |  | 
|  | 1 |  |  |  |  | 20 |  | 
| 26 | 1 |  |  | 1 |  | 556 | use Image::ExifTool::GPS; | 
|  | 1 |  |  |  |  | 5 |  | 
|  | 1 |  |  |  |  | 3805 |  | 
| 27 |  |  |  |  |  |  |  | 
| 28 |  |  |  |  |  |  | $VERSION = '1.17'; | 
| 29 |  |  |  |  |  |  |  | 
| 30 |  |  |  |  |  |  | sub ProcessSEI($$); | 
| 31 |  |  |  |  |  |  |  | 
| 32 |  |  |  |  |  |  | my $parsePictureTiming; # flag to enable parsing of picture timing information (test only) | 
| 33 |  |  |  |  |  |  |  | 
| 34 |  |  |  |  |  |  | # lookup for camera manufacturer name | 
| 35 |  |  |  |  |  |  | %convMake = ( | 
| 36 |  |  |  |  |  |  | 0x0103 => 'Panasonic', | 
| 37 |  |  |  |  |  |  | 0x0108 => 'Sony', | 
| 38 |  |  |  |  |  |  | 0x1011 => 'Canon', | 
| 39 |  |  |  |  |  |  | 0x1104 => 'JVC', #Rob Lewis | 
| 40 |  |  |  |  |  |  | ); | 
| 41 |  |  |  |  |  |  |  | 
| 42 |  |  |  |  |  |  | # information extracted from H.264 video streams | 
| 43 |  |  |  |  |  |  | %Image::ExifTool::H264::Main = ( | 
| 44 |  |  |  |  |  |  | GROUPS => { 2 => 'Video' }, | 
| 45 |  |  |  |  |  |  | VARS => { NO_ID => 1 }, | 
| 46 |  |  |  |  |  |  | NOTES => q{ | 
| 47 |  |  |  |  |  |  | Tags extracted from H.264 video streams.  The metadata for AVCHD videos is | 
| 48 |  |  |  |  |  |  | stored in this stream. | 
| 49 |  |  |  |  |  |  | }, | 
| 50 |  |  |  |  |  |  | ImageWidth => { }, | 
| 51 |  |  |  |  |  |  | ImageHeight => { }, | 
| 52 |  |  |  |  |  |  | MDPM => { SubDirectory => { TagTable => 'Image::ExifTool::H264::MDPM' } }, | 
| 53 |  |  |  |  |  |  | ); | 
| 54 |  |  |  |  |  |  |  | 
| 55 |  |  |  |  |  |  | # H.264 Supplemental Enhancement Information User Data (ref PH/4) | 
| 56 |  |  |  |  |  |  | %Image::ExifTool::H264::MDPM = ( | 
| 57 |  |  |  |  |  |  | GROUPS => { 2 => 'Camera' }, | 
| 58 |  |  |  |  |  |  | PROCESS_PROC => \&ProcessSEI, | 
| 59 |  |  |  |  |  |  | TAG_PREFIX => 'MDPM', | 
| 60 |  |  |  |  |  |  | NOTES => q{ | 
| 61 |  |  |  |  |  |  | The following tags are decoded from the Modified Digital Video Pack Metadata | 
| 62 |  |  |  |  |  |  | (MDPM) of the unregistered user data with UUID | 
| 63 |  |  |  |  |  |  | 17ee8c60f84d11d98cd60800200c9a66 in the H.264 Supplemental Enhancement | 
| 64 |  |  |  |  |  |  | Information (SEI).  I<[Yes, this description is confusing, but nothing | 
| 65 |  |  |  |  |  |  | compared to the challenge of actually decoding the data!]>  This information | 
| 66 |  |  |  |  |  |  | may exist at regular intervals through the entire video, but only the first | 
| 67 |  |  |  |  |  |  | occurrence is extracted unless the L (-ee) option is used (in | 
| 68 |  |  |  |  |  |  | which case subsequent occurrences are extracted as sub-documents). | 
| 69 |  |  |  |  |  |  | }, | 
| 70 |  |  |  |  |  |  | # (Note: all these are explained in IEC 61834-4, but it costs money so it is useless to me) | 
| 71 |  |  |  |  |  |  | # 0x00 - ControlCassetteID (ref 7) | 
| 72 |  |  |  |  |  |  | # 0x01 - ControlTapeLength (ref 7) | 
| 73 |  |  |  |  |  |  | # 0x02 - ControlTimerActDate (ref 7) | 
| 74 |  |  |  |  |  |  | # 0x03 - ControlTimerACS_S_S (ref 7) | 
| 75 |  |  |  |  |  |  | # 0x04-0x05 - ControlPR_StartPoint (ref 7) | 
| 76 |  |  |  |  |  |  | # 0x06 - ControlTagIDNoGenre (ref 7) | 
| 77 |  |  |  |  |  |  | # 0x07 - ControlTopicPageHeader (ref 7) | 
| 78 |  |  |  |  |  |  | # 0x08 - ControlTextHeader (ref 7) | 
| 79 |  |  |  |  |  |  | # 0x09 - ControlText (ref 7) | 
| 80 |  |  |  |  |  |  | # 0x0a-0x0b - ControlTag (ref 7) | 
| 81 |  |  |  |  |  |  | # 0x0c - ControlTeletextInfo (ref 7) | 
| 82 |  |  |  |  |  |  | # 0x0d - ControlKey (ref 7) | 
| 83 |  |  |  |  |  |  | # 0x0e-0x0f - ControlZoneEnd (ref 7) | 
| 84 |  |  |  |  |  |  | # 0x10 - TitleTotalTime (ref 7) | 
| 85 |  |  |  |  |  |  | # 0x11 - TitleRemainTime (ref 7) | 
| 86 |  |  |  |  |  |  | # 0x12 - TitleChapterTotalNo (ref 7) | 
| 87 |  |  |  |  |  |  | 0x13 => { | 
| 88 |  |  |  |  |  |  | Name => 'TimeCode', | 
| 89 |  |  |  |  |  |  | Notes => 'hours:minutes:seconds:frames', | 
| 90 |  |  |  |  |  |  | ValueConv => 'sprintf("%.2x:%.2x:%.2x:%.2x",reverse unpack("C*",$val))', | 
| 91 |  |  |  |  |  |  | }, | 
| 92 |  |  |  |  |  |  | # 0x14 - TitleBinaryGroup - val: 0x00000000,0x14200130 | 
| 93 |  |  |  |  |  |  | # 0x15 - TitleCassetteNo (ref 7) | 
| 94 |  |  |  |  |  |  | # 0x16-0x17 - TitleSoftID (ref 7) | 
| 95 |  |  |  |  |  |  | # (0x18,0x19 listed as TitleTextHeader/TitleText by ref 7) | 
| 96 |  |  |  |  |  |  | 0x18 => { | 
| 97 |  |  |  |  |  |  | Name => 'DateTimeOriginal', | 
| 98 |  |  |  |  |  |  | Description => 'Date/Time Original', | 
| 99 |  |  |  |  |  |  | Groups => { 2 => 'Time' }, | 
| 100 |  |  |  |  |  |  | Notes => 'combined with tag 0x19', | 
| 101 |  |  |  |  |  |  | Combine => 1,   # the next tag (0x19) contains the rest of the date | 
| 102 |  |  |  |  |  |  | # first byte is timezone information: | 
| 103 |  |  |  |  |  |  | #   0x80 - unused | 
| 104 |  |  |  |  |  |  | #   0x40 - DST flag | 
| 105 |  |  |  |  |  |  | #   0x20 - TimeZoneSign | 
| 106 |  |  |  |  |  |  | #   0x1e - TimeZoneValue | 
| 107 |  |  |  |  |  |  | #   0x01 - half-hour flag | 
| 108 |  |  |  |  |  |  | ValueConv => q{ | 
| 109 |  |  |  |  |  |  | my ($tz, @a) = unpack('C*',$val); | 
| 110 |  |  |  |  |  |  | return sprintf('%.2x%.2x:%.2x:%.2x %.2x:%.2x:%.2x%s%.2d:%s%s', @a, | 
| 111 |  |  |  |  |  |  | $tz & 0x20 ? '-' : '+', ($tz >> 1) & 0x0f, | 
| 112 |  |  |  |  |  |  | $tz & 0x01 ? '30' : '00', | 
| 113 |  |  |  |  |  |  | $tz & 0x40 ? ' DST' : ''); | 
| 114 |  |  |  |  |  |  | }, | 
| 115 |  |  |  |  |  |  | PrintConv => '$self->ConvertDateTime($val)', | 
| 116 |  |  |  |  |  |  | }, | 
| 117 |  |  |  |  |  |  | # 0x1a-0x1b - TitleStart (ref 7) | 
| 118 |  |  |  |  |  |  | # 0x1c-0x1d - TitleReelID (ref 7) | 
| 119 |  |  |  |  |  |  | # 0x1e-0x1f - TitleEnd (ref 7) | 
| 120 |  |  |  |  |  |  | # 0x20 - ChapterTotalTime (ref 7) | 
| 121 |  |  |  |  |  |  | # 0x42 - ProgramRecDTime (ref 7) | 
| 122 |  |  |  |  |  |  | # 0x50/0x60 - (AAUX/VAUX)Source (ref 7) | 
| 123 |  |  |  |  |  |  | # 0x51/0x61 - (AAUX/VAUX)SourceControl (ref 7) | 
| 124 |  |  |  |  |  |  | # 0x52/0x62 - (AAUX/VAUX)RecDate (ref 7) | 
| 125 |  |  |  |  |  |  | # 0x53/0x63 - (AAUX/VAUX)RecTime (ref 7) | 
| 126 |  |  |  |  |  |  | # 0x54/0x64 - (AAUX/VAUX)BinaryGroup (ref 7) | 
| 127 |  |  |  |  |  |  | # 0x55/0x65 - (AAUX/VAUX)ClosedCaption (ref 7) | 
| 128 |  |  |  |  |  |  | # 0x56/0x66 - (AAUX/VAUX)TR (ref 7) | 
| 129 |  |  |  |  |  |  | 0x70 => { # ConsumerCamera1 | 
| 130 |  |  |  |  |  |  | Name => 'Camera1', | 
| 131 |  |  |  |  |  |  | SubDirectory => { TagTable => 'Image::ExifTool::H264::Camera1' }, | 
| 132 |  |  |  |  |  |  | }, | 
| 133 |  |  |  |  |  |  | 0x71 => { # ConsumerCamera2 | 
| 134 |  |  |  |  |  |  | Name => 'Camera2', | 
| 135 |  |  |  |  |  |  | SubDirectory => { TagTable => 'Image::ExifTool::H264::Camera2' }, | 
| 136 |  |  |  |  |  |  | }, | 
| 137 |  |  |  |  |  |  | # 0x73 Lens - val: 0x04ffffd3,0x0effffd3,0x15ffffd3,0x41ffffd3,0x52ffffd3,0x59ffffd3,0x65ffffd3,0x71ffffd3,0x75ffffd3,0x79ffffd3,0x7fffffd3,0xffffffd3... | 
| 138 |  |  |  |  |  |  | # 0x74 Gain - val: 0xb8ffff0f | 
| 139 |  |  |  |  |  |  | # 0x75 Pedestal | 
| 140 |  |  |  |  |  |  | # 0x76 Gamma | 
| 141 |  |  |  |  |  |  | # 0x77 Detail | 
| 142 |  |  |  |  |  |  | # 0x7b CameraPreset | 
| 143 |  |  |  |  |  |  | # 0x7c Flare | 
| 144 |  |  |  |  |  |  | # 0x7d Shading | 
| 145 |  |  |  |  |  |  | # 0x7e Knee | 
| 146 |  |  |  |  |  |  | 0x7f => { # Shutter | 
| 147 |  |  |  |  |  |  | Name => 'Shutter', | 
| 148 |  |  |  |  |  |  | SubDirectory => { | 
| 149 |  |  |  |  |  |  | TagTable => 'Image::ExifTool::H264::Shutter', | 
| 150 |  |  |  |  |  |  | ByteOrder => 'LittleEndian', # weird | 
| 151 |  |  |  |  |  |  | }, | 
| 152 |  |  |  |  |  |  | }, | 
| 153 |  |  |  |  |  |  | 0xa0 => { | 
| 154 |  |  |  |  |  |  | Name => 'ExposureTime', | 
| 155 |  |  |  |  |  |  | Format => 'rational32u', | 
| 156 |  |  |  |  |  |  | Groups => { 2 => 'Image' }, | 
| 157 |  |  |  |  |  |  | PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', | 
| 158 |  |  |  |  |  |  | }, | 
| 159 |  |  |  |  |  |  | 0xa1 => { | 
| 160 |  |  |  |  |  |  | Name => 'FNumber', | 
| 161 |  |  |  |  |  |  | Format => 'rational32u', | 
| 162 |  |  |  |  |  |  | Groups => { 2 => 'Image' }, | 
| 163 |  |  |  |  |  |  | }, | 
| 164 |  |  |  |  |  |  | 0xa2 => { | 
| 165 |  |  |  |  |  |  | Name => 'ExposureProgram', | 
| 166 |  |  |  |  |  |  | Format => 'int32u', # (guess) | 
| 167 |  |  |  |  |  |  | PrintConv => { | 
| 168 |  |  |  |  |  |  | 0 => 'Not Defined', | 
| 169 |  |  |  |  |  |  | 1 => 'Manual', | 
| 170 |  |  |  |  |  |  | 2 => 'Program AE', | 
| 171 |  |  |  |  |  |  | 3 => 'Aperture-priority AE', | 
| 172 |  |  |  |  |  |  | 4 => 'Shutter speed priority AE', | 
| 173 |  |  |  |  |  |  | 5 => 'Creative (Slow speed)', | 
| 174 |  |  |  |  |  |  | 6 => 'Action (High speed)', | 
| 175 |  |  |  |  |  |  | 7 => 'Portrait', | 
| 176 |  |  |  |  |  |  | 8 => 'Landscape', | 
| 177 |  |  |  |  |  |  | }, | 
| 178 |  |  |  |  |  |  | }, | 
| 179 |  |  |  |  |  |  | 0xa3 => { | 
| 180 |  |  |  |  |  |  | Name => 'BrightnessValue', | 
| 181 |  |  |  |  |  |  | Format => 'rational32s', | 
| 182 |  |  |  |  |  |  | Groups => { 2 => 'Image' }, | 
| 183 |  |  |  |  |  |  | }, | 
| 184 |  |  |  |  |  |  | 0xa4 => { | 
| 185 |  |  |  |  |  |  | Name => 'ExposureCompensation', | 
| 186 |  |  |  |  |  |  | Format => 'rational32s', | 
| 187 |  |  |  |  |  |  | Groups => { 2 => 'Image' }, | 
| 188 |  |  |  |  |  |  | PrintConv => 'Image::ExifTool::Exif::PrintFraction($val)', | 
| 189 |  |  |  |  |  |  | }, | 
| 190 |  |  |  |  |  |  | 0xa5 => { | 
| 191 |  |  |  |  |  |  | Name => 'MaxApertureValue', | 
| 192 |  |  |  |  |  |  | Format => 'rational32u', | 
| 193 |  |  |  |  |  |  | ValueConv => '2 ** ($val / 2)', | 
| 194 |  |  |  |  |  |  | PrintConv => 'sprintf("%.1f",$val)', | 
| 195 |  |  |  |  |  |  | }, | 
| 196 |  |  |  |  |  |  | 0xa6 => { | 
| 197 |  |  |  |  |  |  | Name => 'Flash', | 
| 198 |  |  |  |  |  |  | Format => 'int32u', # (guess) | 
| 199 |  |  |  |  |  |  | Flags => 'PrintHex', | 
| 200 |  |  |  |  |  |  | SeparateTable => 'EXIF Flash', | 
| 201 |  |  |  |  |  |  | PrintConv =>  \%Image::ExifTool::Exif::flash, | 
| 202 |  |  |  |  |  |  | }, | 
| 203 |  |  |  |  |  |  | 0xa7 => { | 
| 204 |  |  |  |  |  |  | Name => 'CustomRendered', | 
| 205 |  |  |  |  |  |  | Format => 'int32u', # (guess) | 
| 206 |  |  |  |  |  |  | Groups => { 2 => 'Image' }, | 
| 207 |  |  |  |  |  |  | PrintConv => { | 
| 208 |  |  |  |  |  |  | 0 => 'Normal', | 
| 209 |  |  |  |  |  |  | 1 => 'Custom', | 
| 210 |  |  |  |  |  |  | }, | 
| 211 |  |  |  |  |  |  | }, | 
| 212 |  |  |  |  |  |  | 0xa8 => { | 
| 213 |  |  |  |  |  |  | Name => 'WhiteBalance', | 
| 214 |  |  |  |  |  |  | Format => 'int32u', # (guess) | 
| 215 |  |  |  |  |  |  | Priority => 0, | 
| 216 |  |  |  |  |  |  | PrintConv => { | 
| 217 |  |  |  |  |  |  | 0 => 'Auto', | 
| 218 |  |  |  |  |  |  | 1 => 'Manual', | 
| 219 |  |  |  |  |  |  | }, | 
| 220 |  |  |  |  |  |  | }, | 
| 221 |  |  |  |  |  |  | 0xa9 => { | 
| 222 |  |  |  |  |  |  | Name => 'FocalLengthIn35mmFormat', | 
| 223 |  |  |  |  |  |  | Format => 'rational32u', | 
| 224 |  |  |  |  |  |  | PrintConv => '"$val mm"', | 
| 225 |  |  |  |  |  |  | }, | 
| 226 |  |  |  |  |  |  | 0xaa => { | 
| 227 |  |  |  |  |  |  | Name => 'SceneCaptureType', | 
| 228 |  |  |  |  |  |  | Format => 'int32u', # (guess) | 
| 229 |  |  |  |  |  |  | PrintConv => { | 
| 230 |  |  |  |  |  |  | 0 => 'Standard', | 
| 231 |  |  |  |  |  |  | 1 => 'Landscape', | 
| 232 |  |  |  |  |  |  | 2 => 'Portrait', | 
| 233 |  |  |  |  |  |  | 3 => 'Night', | 
| 234 |  |  |  |  |  |  | }, | 
| 235 |  |  |  |  |  |  | }, | 
| 236 |  |  |  |  |  |  | # 0xab-0xaf ExifOption | 
| 237 |  |  |  |  |  |  | 0xb0 => { | 
| 238 |  |  |  |  |  |  | Name => 'GPSVersionID', | 
| 239 |  |  |  |  |  |  | Format => 'int8u', | 
| 240 |  |  |  |  |  |  | Count => 4, | 
| 241 |  |  |  |  |  |  | Groups => { 1 => 'GPS', 2 => 'Location' }, | 
| 242 |  |  |  |  |  |  | PrintConv => '$val =~ tr/ /./; $val', | 
| 243 |  |  |  |  |  |  | }, | 
| 244 |  |  |  |  |  |  | 0xb1 => { | 
| 245 |  |  |  |  |  |  | Name => 'GPSLatitudeRef', | 
| 246 |  |  |  |  |  |  | Format => 'string', | 
| 247 |  |  |  |  |  |  | Groups => { 1 => 'GPS', 2 => 'Location' }, | 
| 248 |  |  |  |  |  |  | PrintConv => { | 
| 249 |  |  |  |  |  |  | N => 'North', | 
| 250 |  |  |  |  |  |  | S => 'South', | 
| 251 |  |  |  |  |  |  | }, | 
| 252 |  |  |  |  |  |  | }, | 
| 253 |  |  |  |  |  |  | 0xb2 => { | 
| 254 |  |  |  |  |  |  | Name => 'GPSLatitude', | 
| 255 |  |  |  |  |  |  | Format => 'rational32u', | 
| 256 |  |  |  |  |  |  | Groups => { 1 => 'GPS', 2 => 'Location' }, | 
| 257 |  |  |  |  |  |  | Notes => 'combined with tags 0xb3 and 0xb4', | 
| 258 |  |  |  |  |  |  | Combine => 2,   # combine the next 2 tags (0xb2=deg, 0xb3=min, 0xb4=sec) | 
| 259 |  |  |  |  |  |  | ValueConv => 'Image::ExifTool::GPS::ToDegrees($val)', | 
| 260 |  |  |  |  |  |  | PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1)', | 
| 261 |  |  |  |  |  |  | }, | 
| 262 |  |  |  |  |  |  | 0xb5 => { | 
| 263 |  |  |  |  |  |  | Name => 'GPSLongitudeRef', | 
| 264 |  |  |  |  |  |  | Format => 'string', | 
| 265 |  |  |  |  |  |  | Groups => { 1 => 'GPS', 2 => 'Location' }, | 
| 266 |  |  |  |  |  |  | PrintConv => { | 
| 267 |  |  |  |  |  |  | E => 'East', | 
| 268 |  |  |  |  |  |  | W => 'West', | 
| 269 |  |  |  |  |  |  | }, | 
| 270 |  |  |  |  |  |  | }, | 
| 271 |  |  |  |  |  |  | 0xb6 => { | 
| 272 |  |  |  |  |  |  | Name => 'GPSLongitude', | 
| 273 |  |  |  |  |  |  | Format => 'rational32u', | 
| 274 |  |  |  |  |  |  | Groups => { 1 => 'GPS', 2 => 'Location' }, | 
| 275 |  |  |  |  |  |  | Combine => 2,   # combine the next 2 tags (0xb6=deg, 0xb7=min, 0xb8=sec) | 
| 276 |  |  |  |  |  |  | Notes => 'combined with tags 0xb7 and 0xb8', | 
| 277 |  |  |  |  |  |  | ValueConv => 'Image::ExifTool::GPS::ToDegrees($val)', | 
| 278 |  |  |  |  |  |  | PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1)', | 
| 279 |  |  |  |  |  |  | }, | 
| 280 |  |  |  |  |  |  | 0xb9 => { | 
| 281 |  |  |  |  |  |  | Name => 'GPSAltitudeRef', | 
| 282 |  |  |  |  |  |  | Format => 'int32u', # (guess) | 
| 283 |  |  |  |  |  |  | Groups => { 1 => 'GPS', 2 => 'Location' }, | 
| 284 |  |  |  |  |  |  | ValueConv => '$val ? 1 : 0', # because I'm not sure about the Format | 
| 285 |  |  |  |  |  |  | PrintConv => { | 
| 286 |  |  |  |  |  |  | 0 => 'Above Sea Level', | 
| 287 |  |  |  |  |  |  | 1 => 'Below Sea Level', | 
| 288 |  |  |  |  |  |  | }, | 
| 289 |  |  |  |  |  |  | }, | 
| 290 |  |  |  |  |  |  | 0xba => { | 
| 291 |  |  |  |  |  |  | Name => 'GPSAltitude', | 
| 292 |  |  |  |  |  |  | Format => 'rational32u', | 
| 293 |  |  |  |  |  |  | Groups => { 1 => 'GPS', 2 => 'Location' }, | 
| 294 |  |  |  |  |  |  | }, | 
| 295 |  |  |  |  |  |  | 0xbb => { | 
| 296 |  |  |  |  |  |  | Name => 'GPSTimeStamp', | 
| 297 |  |  |  |  |  |  | Format => 'rational32u', | 
| 298 |  |  |  |  |  |  | Groups => { 1 => 'GPS', 2 => 'Time' }, | 
| 299 |  |  |  |  |  |  | Combine => 2,    # the next tags (0xbc/0xbd) contain the minutes/seconds | 
| 300 |  |  |  |  |  |  | Notes => 'combined with tags 0xbc and 0xbd', | 
| 301 |  |  |  |  |  |  | ValueConv => 'Image::ExifTool::GPS::ConvertTimeStamp($val)', | 
| 302 |  |  |  |  |  |  | PrintConv => 'Image::ExifTool::GPS::PrintTimeStamp($val)', | 
| 303 |  |  |  |  |  |  | }, | 
| 304 |  |  |  |  |  |  | 0xbe => { | 
| 305 |  |  |  |  |  |  | Name => 'GPSStatus', | 
| 306 |  |  |  |  |  |  | Format => 'string', | 
| 307 |  |  |  |  |  |  | Groups => { 1 => 'GPS', 2 => 'Location' }, | 
| 308 |  |  |  |  |  |  | PrintConv => { | 
| 309 |  |  |  |  |  |  | A => 'Measurement Active', | 
| 310 |  |  |  |  |  |  | V => 'Measurement Void', | 
| 311 |  |  |  |  |  |  | }, | 
| 312 |  |  |  |  |  |  | }, | 
| 313 |  |  |  |  |  |  | 0xbf => { | 
| 314 |  |  |  |  |  |  | Name => 'GPSMeasureMode', | 
| 315 |  |  |  |  |  |  | Format => 'string', | 
| 316 |  |  |  |  |  |  | Groups => { 1 => 'GPS', 2 => 'Location' }, | 
| 317 |  |  |  |  |  |  | PrintConv => { | 
| 318 |  |  |  |  |  |  | 2 => '2-Dimensional Measurement', | 
| 319 |  |  |  |  |  |  | 3 => '3-Dimensional Measurement', | 
| 320 |  |  |  |  |  |  | }, | 
| 321 |  |  |  |  |  |  | }, | 
| 322 |  |  |  |  |  |  | 0xc0 => { | 
| 323 |  |  |  |  |  |  | Name => 'GPSDOP', | 
| 324 |  |  |  |  |  |  | Description => 'GPS Dilution Of Precision', | 
| 325 |  |  |  |  |  |  | Format => 'rational32u', | 
| 326 |  |  |  |  |  |  | Groups => { 1 => 'GPS', 2 => 'Location' }, | 
| 327 |  |  |  |  |  |  | }, | 
| 328 |  |  |  |  |  |  | 0xc1 => { | 
| 329 |  |  |  |  |  |  | Name => 'GPSSpeedRef', | 
| 330 |  |  |  |  |  |  | Format => 'string', | 
| 331 |  |  |  |  |  |  | Groups => { 1 => 'GPS', 2 => 'Location' }, | 
| 332 |  |  |  |  |  |  | PrintConv => { | 
| 333 |  |  |  |  |  |  | K => 'km/h', | 
| 334 |  |  |  |  |  |  | M => 'mph', | 
| 335 |  |  |  |  |  |  | N => 'knots', | 
| 336 |  |  |  |  |  |  | }, | 
| 337 |  |  |  |  |  |  | }, | 
| 338 |  |  |  |  |  |  | 0xc2 => { | 
| 339 |  |  |  |  |  |  | Name => 'GPSSpeed', | 
| 340 |  |  |  |  |  |  | Format => 'rational32u', | 
| 341 |  |  |  |  |  |  | Groups => { 1 => 'GPS', 2 => 'Location' }, | 
| 342 |  |  |  |  |  |  | }, | 
| 343 |  |  |  |  |  |  | 0xc3 => { | 
| 344 |  |  |  |  |  |  | Name => 'GPSTrackRef', | 
| 345 |  |  |  |  |  |  | Format => 'string', | 
| 346 |  |  |  |  |  |  | Groups => { 1 => 'GPS', 2 => 'Location' }, | 
| 347 |  |  |  |  |  |  | PrintConv => { | 
| 348 |  |  |  |  |  |  | M => 'Magnetic North', | 
| 349 |  |  |  |  |  |  | T => 'True North', | 
| 350 |  |  |  |  |  |  | }, | 
| 351 |  |  |  |  |  |  | }, | 
| 352 |  |  |  |  |  |  | 0xc4 => { | 
| 353 |  |  |  |  |  |  | Name => 'GPSTrack', | 
| 354 |  |  |  |  |  |  | Format => 'rational32u', | 
| 355 |  |  |  |  |  |  | Groups => { 1 => 'GPS', 2 => 'Location' }, | 
| 356 |  |  |  |  |  |  | }, | 
| 357 |  |  |  |  |  |  | 0xc5 => { | 
| 358 |  |  |  |  |  |  | Name => 'GPSImgDirectionRef', | 
| 359 |  |  |  |  |  |  | Format => 'string', | 
| 360 |  |  |  |  |  |  | Groups => { 1 => 'GPS', 2 => 'Location' }, | 
| 361 |  |  |  |  |  |  | PrintConv => { | 
| 362 |  |  |  |  |  |  | M => 'Magnetic North', | 
| 363 |  |  |  |  |  |  | T => 'True North', | 
| 364 |  |  |  |  |  |  | }, | 
| 365 |  |  |  |  |  |  | }, | 
| 366 |  |  |  |  |  |  | 0xc6 => { | 
| 367 |  |  |  |  |  |  | Name => 'GPSImgDirection', | 
| 368 |  |  |  |  |  |  | Format => 'rational32u', | 
| 369 |  |  |  |  |  |  | Groups => { 1 => 'GPS', 2 => 'Location' }, | 
| 370 |  |  |  |  |  |  | }, | 
| 371 |  |  |  |  |  |  | 0xc7 => { | 
| 372 |  |  |  |  |  |  | Name => 'GPSMapDatum', | 
| 373 |  |  |  |  |  |  | Format => 'string', | 
| 374 |  |  |  |  |  |  | Groups => { 1 => 'GPS', 2 => 'Location' }, | 
| 375 |  |  |  |  |  |  | Combine => 1,    # the next tag (0xc8) contains the rest of the string | 
| 376 |  |  |  |  |  |  | Notes => 'combined with tag 0xc8', | 
| 377 |  |  |  |  |  |  | }, | 
| 378 |  |  |  |  |  |  | # 0xc9-0xcf - GPSOption | 
| 379 |  |  |  |  |  |  | # 0xc9 - val: 0x001d0203 | 
| 380 |  |  |  |  |  |  | 0xca => { #PH (Sony DSC-HX7V) | 
| 381 |  |  |  |  |  |  | Name => 'GPSDateStamp', | 
| 382 |  |  |  |  |  |  | Format => 'string', | 
| 383 |  |  |  |  |  |  | Groups => { 1 => 'GPS', 2 => 'Time' }, | 
| 384 |  |  |  |  |  |  | Combine => 2,    # the next 2 tags contain the rest of the string | 
| 385 |  |  |  |  |  |  | Notes => 'combined with tags 0xcb and 0xcc', | 
| 386 |  |  |  |  |  |  | ValueConv => 'Image::ExifTool::Exif::ExifDate($val)', | 
| 387 |  |  |  |  |  |  | }, | 
| 388 |  |  |  |  |  |  | 0xe0 => { | 
| 389 |  |  |  |  |  |  | Name => 'MakeModel', | 
| 390 |  |  |  |  |  |  | SubDirectory => { TagTable => 'Image::ExifTool::H264::MakeModel' }, | 
| 391 |  |  |  |  |  |  | }, | 
| 392 |  |  |  |  |  |  | # 0xe1-0xef - MakerOption | 
| 393 |  |  |  |  |  |  | # 0xe1 - val: 0x01000670,0x01000678,0x06ffffff,0x01ffffff,0x01000020,0x01000400... | 
| 394 |  |  |  |  |  |  | 0xe1 => { #6 | 
| 395 |  |  |  |  |  |  | Name => 'RecInfo', | 
| 396 |  |  |  |  |  |  | Condition => '$$self{Make} eq "Canon"', | 
| 397 |  |  |  |  |  |  | Notes => 'Canon only', | 
| 398 |  |  |  |  |  |  | SubDirectory => { TagTable => 'Image::ExifTool::H264::RecInfo' }, | 
| 399 |  |  |  |  |  |  | }, | 
| 400 |  |  |  |  |  |  | # 0xe2-0xe8 - val: 0x00000000 in many samples | 
| 401 |  |  |  |  |  |  | # 0xe2 - val: 0x00000000,0x01000000,0x01010000,0x8080900c,0x8080a074 | 
| 402 |  |  |  |  |  |  | # 0xe3 - val: 0x00801f89,0x00801f8b,0x00c01f89,0xc9c01f80 | 
| 403 |  |  |  |  |  |  | 0xe4 => { #PH | 
| 404 |  |  |  |  |  |  | Name => 'Model', | 
| 405 |  |  |  |  |  |  | Condition => '$$self{Make} eq "Sony"', # (possibly also Canon models?) | 
| 406 |  |  |  |  |  |  | Description => 'Camera Model Name', | 
| 407 |  |  |  |  |  |  | Notes => 'Sony only, combined with tags 0xe5 and 0xe6', | 
| 408 |  |  |  |  |  |  | Format => 'string', | 
| 409 |  |  |  |  |  |  | Combine => 2, # (not sure about 0xe6, but include it just in case) | 
| 410 |  |  |  |  |  |  | RawConv => '$val eq "" ? undef : $val', | 
| 411 |  |  |  |  |  |  | }, | 
| 412 |  |  |  |  |  |  | # 0xeb - val: 0x008a0a00,0x0a300000,0x508a0a00,0x52880a00,0x528a0a00 | 
| 413 |  |  |  |  |  |  | # 0xec - val: 0x0b700000 | 
| 414 |  |  |  |  |  |  | # 0xed - val: 0x0ce0f819 | 
| 415 |  |  |  |  |  |  | 0xee => { #6 (HFS200) | 
| 416 |  |  |  |  |  |  | Name => 'FrameInfo', | 
| 417 |  |  |  |  |  |  | Condition => '$$self{Make} eq "Canon"', | 
| 418 |  |  |  |  |  |  | Notes => 'Canon only', | 
| 419 |  |  |  |  |  |  | SubDirectory => { TagTable => 'Image::ExifTool::H264::FrameInfo' }, | 
| 420 |  |  |  |  |  |  | }, | 
| 421 |  |  |  |  |  |  | # 0xef - val: 0x01c00000,0x0e00000c | 
| 422 |  |  |  |  |  |  | ); | 
| 423 |  |  |  |  |  |  |  | 
| 424 |  |  |  |  |  |  | # ConsumerCamera1 information (ref PH) | 
| 425 |  |  |  |  |  |  | %Image::ExifTool::H264::Camera1 = ( | 
| 426 |  |  |  |  |  |  | PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, | 
| 427 |  |  |  |  |  |  | GROUPS => { 2 => 'Camera' }, | 
| 428 |  |  |  |  |  |  | TAG_PREFIX => 'Camera1', | 
| 429 |  |  |  |  |  |  | PRINT_CONV => 'sprintf("0x%.2x",$val)', | 
| 430 |  |  |  |  |  |  | FIRST_ENTRY => 0, | 
| 431 |  |  |  |  |  |  | 0 => { | 
| 432 |  |  |  |  |  |  | Name => 'ApertureSetting', | 
| 433 |  |  |  |  |  |  | PrintHex => 1, | 
| 434 |  |  |  |  |  |  | PrintConv => { | 
| 435 |  |  |  |  |  |  | 0xff => 'Auto', | 
| 436 |  |  |  |  |  |  | 0xfe => 'Closed', | 
| 437 |  |  |  |  |  |  | OTHER => sub { sprintf('%.1f', 2 ** (($_[0] & 0x3f) / 8)) }, | 
| 438 |  |  |  |  |  |  | }, | 
| 439 |  |  |  |  |  |  | }, | 
| 440 |  |  |  |  |  |  | 1 => { | 
| 441 |  |  |  |  |  |  | Name => 'Gain', | 
| 442 |  |  |  |  |  |  | Mask => 0x0f, | 
| 443 |  |  |  |  |  |  | # (0x0f would translate to 42 dB, but this value is used by the Sony | 
| 444 |  |  |  |  |  |  | #  HXR-NX5U for any out-of-range value such as -6 dB or "hyper gain" - PH) | 
| 445 |  |  |  |  |  |  | ValueConv => '($val - 1) * 3', | 
| 446 |  |  |  |  |  |  | PrintConv => '$val==42 ? "Out of range" : "$val dB"', | 
| 447 |  |  |  |  |  |  | }, | 
| 448 |  |  |  |  |  |  | 1.1 => { | 
| 449 |  |  |  |  |  |  | Name => 'ExposureProgram', | 
| 450 |  |  |  |  |  |  | Mask => 0xf0, | 
| 451 |  |  |  |  |  |  | ValueConv => '$val == 15 ? undef : $val', | 
| 452 |  |  |  |  |  |  | PrintConv => { | 
| 453 |  |  |  |  |  |  | 0 => 'Program AE', | 
| 454 |  |  |  |  |  |  | 1 => 'Gain', #? | 
| 455 |  |  |  |  |  |  | 2 => 'Shutter speed priority AE', | 
| 456 |  |  |  |  |  |  | 3 => 'Aperture-priority AE', | 
| 457 |  |  |  |  |  |  | 4 => 'Manual', | 
| 458 |  |  |  |  |  |  | }, | 
| 459 |  |  |  |  |  |  | }, | 
| 460 |  |  |  |  |  |  | 2.1 => { | 
| 461 |  |  |  |  |  |  | Name => 'WhiteBalance', | 
| 462 |  |  |  |  |  |  | Mask => 0xe0, | 
| 463 |  |  |  |  |  |  | ValueConv => '$val == 7 ? undef : $val', | 
| 464 |  |  |  |  |  |  | PrintConv => { | 
| 465 |  |  |  |  |  |  | 0 => 'Auto', | 
| 466 |  |  |  |  |  |  | 1 => 'Hold', | 
| 467 |  |  |  |  |  |  | 2 => '1-Push', | 
| 468 |  |  |  |  |  |  | 3 => 'Daylight', | 
| 469 |  |  |  |  |  |  | }, | 
| 470 |  |  |  |  |  |  | }, | 
| 471 |  |  |  |  |  |  | 3 => { | 
| 472 |  |  |  |  |  |  | Name => 'Focus', | 
| 473 |  |  |  |  |  |  | ValueConv => '$val == 0xff ? undef : $val', | 
| 474 |  |  |  |  |  |  | PrintConv => q{ | 
| 475 |  |  |  |  |  |  | my $foc = ($val & 0x7e) / (($val & 0x01) ? 40 : 400); | 
| 476 |  |  |  |  |  |  | return(($val & 0x80 ? 'Manual' : 'Auto') . " ($foc)"); | 
| 477 |  |  |  |  |  |  | }, | 
| 478 |  |  |  |  |  |  | }, | 
| 479 |  |  |  |  |  |  | ); | 
| 480 |  |  |  |  |  |  |  | 
| 481 |  |  |  |  |  |  | # ConsumerCamera2 information (ref PH) | 
| 482 |  |  |  |  |  |  | %Image::ExifTool::H264::Camera2 = ( | 
| 483 |  |  |  |  |  |  | PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, | 
| 484 |  |  |  |  |  |  | GROUPS => { 2 => 'Camera' }, | 
| 485 |  |  |  |  |  |  | TAG_PREFIX => 'Camera2', | 
| 486 |  |  |  |  |  |  | PRINT_CONV => 'sprintf("0x%.2x",$val)', | 
| 487 |  |  |  |  |  |  | FIRST_ENTRY => 0, | 
| 488 |  |  |  |  |  |  | 1 => { | 
| 489 |  |  |  |  |  |  | Name => 'ImageStabilization', | 
| 490 |  |  |  |  |  |  | PrintHex => 1, | 
| 491 |  |  |  |  |  |  | PrintConv => { | 
| 492 |  |  |  |  |  |  | 0 => 'Off', | 
| 493 |  |  |  |  |  |  | 0x3f => 'On (0x3f)', #8 | 
| 494 |  |  |  |  |  |  | 0xbf => 'Off (0xbf)', #8 | 
| 495 |  |  |  |  |  |  | 0xff => 'n/a', | 
| 496 |  |  |  |  |  |  | OTHER => sub { | 
| 497 |  |  |  |  |  |  | my $val = shift; | 
| 498 |  |  |  |  |  |  | sprintf("%s (0x%.2x)", $val & 0x10 ? "On" : "Off", $val); | 
| 499 |  |  |  |  |  |  | }, | 
| 500 |  |  |  |  |  |  | }, | 
| 501 |  |  |  |  |  |  | }, | 
| 502 |  |  |  |  |  |  | ); | 
| 503 |  |  |  |  |  |  |  | 
| 504 |  |  |  |  |  |  | # camera info 0x7f (ref PH) | 
| 505 |  |  |  |  |  |  | %Image::ExifTool::H264::Shutter = ( | 
| 506 |  |  |  |  |  |  | PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, | 
| 507 |  |  |  |  |  |  | GROUPS => { 2 => 'Image' }, | 
| 508 |  |  |  |  |  |  | TAG_PREFIX => 'Shutter', | 
| 509 |  |  |  |  |  |  | PRINT_CONV => 'sprintf("0x%.2x",$val)', | 
| 510 |  |  |  |  |  |  | FIRST_ENTRY => 0, | 
| 511 |  |  |  |  |  |  | FORMAT => 'int16u', | 
| 512 |  |  |  |  |  |  | 1.1 => { #6 | 
| 513 |  |  |  |  |  |  | Name => 'ExposureTime', | 
| 514 |  |  |  |  |  |  | Mask => 0x7fff, # (what is bit 0x8000 for?) | 
| 515 |  |  |  |  |  |  | RawConv => '$val == 0x7fff ? undef : $val', #7 | 
| 516 |  |  |  |  |  |  | ValueConv => '$val / 28125', #PH (Vixia HF G30, ref forum5588) (was $val/33640 until 9.49) | 
| 517 |  |  |  |  |  |  | PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', | 
| 518 |  |  |  |  |  |  | }, | 
| 519 |  |  |  |  |  |  | ); | 
| 520 |  |  |  |  |  |  |  | 
| 521 |  |  |  |  |  |  | # camera info 0xe0 (ref PH) | 
| 522 |  |  |  |  |  |  | %Image::ExifTool::H264::MakeModel = ( | 
| 523 |  |  |  |  |  |  | PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, | 
| 524 |  |  |  |  |  |  | GROUPS => { 2 => 'Camera' }, | 
| 525 |  |  |  |  |  |  | FORMAT => 'int16u', | 
| 526 |  |  |  |  |  |  | FIRST_ENTRY => 0, | 
| 527 |  |  |  |  |  |  | 0 => { | 
| 528 |  |  |  |  |  |  | Name => 'Make', | 
| 529 |  |  |  |  |  |  | PrintHex => 1, | 
| 530 |  |  |  |  |  |  | RawConv => '$$self{Make} = ($Image::ExifTool::H264::convMake{$val} || "Unknown"); $val', | 
| 531 |  |  |  |  |  |  | PrintConv => \%convMake, | 
| 532 |  |  |  |  |  |  | }, | 
| 533 |  |  |  |  |  |  | # 1 => ModelIDCode according to ref 4/5 (I think not - PH) | 
| 534 |  |  |  |  |  |  | # 1 => { Name => 'ModelIDCode', PrintConv => 'sprintf("%.4x",$val)' }, | 
| 535 |  |  |  |  |  |  | # vals: 0x0313 - various Pansonic HDC models | 
| 536 |  |  |  |  |  |  | #       0x0345 - Panasonic HC-V7272 | 
| 537 |  |  |  |  |  |  | #       0x0414 - Panasonic AG-AF100 | 
| 538 |  |  |  |  |  |  | #       0x0591 - various Panasonic DMC models | 
| 539 |  |  |  |  |  |  | #       0x0802 - Panasonic DMC-TZ60 with GPS information off | 
| 540 |  |  |  |  |  |  | #       0x0803 - Panasonic DMC-TZ60 with GPS information on | 
| 541 |  |  |  |  |  |  | #       0x3001 - various Sony DSC, HDR, NEX and SLT models | 
| 542 |  |  |  |  |  |  | #       0x3003 - various Sony DSC models | 
| 543 |  |  |  |  |  |  | #       0x3100 - various Sony DSC, ILCE, NEX and SLT models | 
| 544 |  |  |  |  |  |  | #       0x1000 - Sony HDR-UX1 | 
| 545 |  |  |  |  |  |  | #       0x2000 - Canon HF100 (60i) | 
| 546 |  |  |  |  |  |  | #       0x3000 - Canon HF100 (30p) | 
| 547 |  |  |  |  |  |  | #       0x3101 - Canon HFM300 (PH, all qualities and frame rates) | 
| 548 |  |  |  |  |  |  | #       0x3102 - Canon HFS200 | 
| 549 |  |  |  |  |  |  | #       0x4300 - Canon HFG30 | 
| 550 |  |  |  |  |  |  | ); | 
| 551 |  |  |  |  |  |  |  | 
| 552 |  |  |  |  |  |  | # camera info 0xe1 (ref 6) | 
| 553 |  |  |  |  |  |  | %Image::ExifTool::H264::RecInfo = ( | 
| 554 |  |  |  |  |  |  | PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, | 
| 555 |  |  |  |  |  |  | GROUPS => { 2 => 'Camera' }, | 
| 556 |  |  |  |  |  |  | FORMAT => 'int8u', | 
| 557 |  |  |  |  |  |  | NOTES => 'Recording information stored by some Canon video cameras.', | 
| 558 |  |  |  |  |  |  | FIRST_ENTRY => 0, | 
| 559 |  |  |  |  |  |  | 0 => { | 
| 560 |  |  |  |  |  |  | Name => 'RecordingMode', | 
| 561 |  |  |  |  |  |  | PrintConv => { | 
| 562 |  |  |  |  |  |  | 0x02 => 'XP+', # High Quality 12 Mbps | 
| 563 |  |  |  |  |  |  | 0x04 => 'SP',  # Standard Play 7 Mbps | 
| 564 |  |  |  |  |  |  | 0x05 => 'LP',  # Long Play 5 Mbps | 
| 565 |  |  |  |  |  |  | 0x06 => 'FXP', # High Quality 17 Mbps | 
| 566 |  |  |  |  |  |  | 0x07 => 'MXP', # High Quality 24 Mbps | 
| 567 |  |  |  |  |  |  | }, | 
| 568 |  |  |  |  |  |  | }, | 
| 569 |  |  |  |  |  |  | ); | 
| 570 |  |  |  |  |  |  |  | 
| 571 |  |  |  |  |  |  | # camera info 0xee (ref 6) | 
| 572 |  |  |  |  |  |  | %Image::ExifTool::H264::FrameInfo = ( | 
| 573 |  |  |  |  |  |  | PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, | 
| 574 |  |  |  |  |  |  | GROUPS => { 2 => 'Video' }, | 
| 575 |  |  |  |  |  |  | FORMAT => 'int8u', | 
| 576 |  |  |  |  |  |  | NOTES => 'Frame rate information stored by some Canon video cameras.', | 
| 577 |  |  |  |  |  |  | FIRST_ENTRY => 0, | 
| 578 |  |  |  |  |  |  | 0 => 'CaptureFrameRate', | 
| 579 |  |  |  |  |  |  | 1 => 'VideoFrameRate', | 
| 580 |  |  |  |  |  |  | # 2 - 8=60i, 10=PF30, 74=PF24 (PH, HFM300) | 
| 581 |  |  |  |  |  |  | ); | 
| 582 |  |  |  |  |  |  |  | 
| 583 |  |  |  |  |  |  | #============================================================================== | 
| 584 |  |  |  |  |  |  | # Bitstream functions (used for H264 video) | 
| 585 |  |  |  |  |  |  | # | 
| 586 |  |  |  |  |  |  | # Member variables: | 
| 587 |  |  |  |  |  |  | #   Mask    = mask for next bit to read (0 when all data has been read) | 
| 588 |  |  |  |  |  |  | #   Pos     = byte offset of next word to read | 
| 589 |  |  |  |  |  |  | #   Word    = current data word | 
| 590 |  |  |  |  |  |  | #   Len     = total data length in bytes | 
| 591 |  |  |  |  |  |  | #   DataPt  = data pointer | 
| 592 |  |  |  |  |  |  | #.............................................................................. | 
| 593 |  |  |  |  |  |  |  | 
| 594 |  |  |  |  |  |  | #------------------------------------------------------------------------------ | 
| 595 |  |  |  |  |  |  | # Read next word from bitstream | 
| 596 |  |  |  |  |  |  | # Inputs: 0) BitStream ref | 
| 597 |  |  |  |  |  |  | # Returns: true if there is more data (and updates | 
| 598 |  |  |  |  |  |  | #          Mask, Pos and Word for first bit in next word) | 
| 599 |  |  |  |  |  |  | sub ReadNextWord($) | 
| 600 |  |  |  |  |  |  | { | 
| 601 | 3 |  |  | 3 | 0 | 6 | my $bstr = shift; | 
| 602 | 3 |  |  |  |  | 5 | my $pos = $$bstr{Pos}; | 
| 603 | 3 | 50 |  |  |  | 7 | if ($pos + 4 <= $$bstr{Len}) { | 
|  |  | 0 |  |  |  |  |  | 
| 604 | 3 |  |  |  |  | 6 | $$bstr{Word} = unpack("x$pos N", ${$$bstr{DataPt}}); | 
|  | 3 |  |  |  |  | 9 |  | 
| 605 | 3 |  |  |  |  | 5 | $$bstr{Mask} = 0x80000000; | 
| 606 | 3 |  |  |  |  | 4 | $$bstr{Pos} += 4; | 
| 607 |  |  |  |  |  |  | } elsif ($pos < $$bstr{Len}) { | 
| 608 | 0 |  |  |  |  | 0 | my @bytes = unpack("x$pos C*", ${$$bstr{DataPt}}); | 
|  | 0 |  |  |  |  | 0 |  | 
| 609 | 0 |  |  |  |  | 0 | my ($word, $mask) = (shift(@bytes), 0x80); | 
| 610 | 0 |  |  |  |  | 0 | while (@bytes) { | 
| 611 | 0 |  |  |  |  | 0 | $word = ($word << 8) | shift(@bytes); | 
| 612 | 0 |  |  |  |  | 0 | $mask <<= 8; | 
| 613 |  |  |  |  |  |  | } | 
| 614 | 0 |  |  |  |  | 0 | $$bstr{Word} = $word; | 
| 615 | 0 |  |  |  |  | 0 | $$bstr{Mask} = $mask; | 
| 616 | 0 |  |  |  |  | 0 | $$bstr{Pos} = $$bstr{Len}; | 
| 617 |  |  |  |  |  |  | } else { | 
| 618 | 0 |  |  |  |  | 0 | return 0; | 
| 619 |  |  |  |  |  |  | } | 
| 620 | 3 |  |  |  |  | 11 | return 1; | 
| 621 |  |  |  |  |  |  | } | 
| 622 |  |  |  |  |  |  |  | 
| 623 |  |  |  |  |  |  | #------------------------------------------------------------------------------ | 
| 624 |  |  |  |  |  |  | # Create a new BitStream object | 
| 625 |  |  |  |  |  |  | # Inputs: 0) data ref | 
| 626 |  |  |  |  |  |  | # Returns: BitStream ref, or null if data is empty | 
| 627 |  |  |  |  |  |  | sub NewBitStream($) | 
| 628 |  |  |  |  |  |  | { | 
| 629 | 1 |  |  | 1 | 0 | 2 | my $dataPt = shift; | 
| 630 | 1 |  |  |  |  | 4 | my $bstr = { | 
| 631 |  |  |  |  |  |  | DataPt => $dataPt, | 
| 632 |  |  |  |  |  |  | Len    => length($$dataPt), | 
| 633 |  |  |  |  |  |  | Pos    => 0, | 
| 634 |  |  |  |  |  |  | Mask   => 0, | 
| 635 |  |  |  |  |  |  | }; | 
| 636 | 1 | 50 |  |  |  | 2 | ReadNextWord($bstr) or undef $bstr; | 
| 637 | 1 |  |  |  |  | 4 | return $bstr; | 
| 638 |  |  |  |  |  |  | } | 
| 639 |  |  |  |  |  |  |  | 
| 640 |  |  |  |  |  |  | #------------------------------------------------------------------------------ | 
| 641 |  |  |  |  |  |  | # Get number of bits remaining in bit stream | 
| 642 |  |  |  |  |  |  | # Inputs: 0) BitStream ref | 
| 643 |  |  |  |  |  |  | # Returns: number of bits remaining | 
| 644 |  |  |  |  |  |  | sub BitsLeft($) | 
| 645 |  |  |  |  |  |  | { | 
| 646 | 0 |  |  | 0 | 0 | 0 | my $bstr = shift; | 
| 647 | 0 |  |  |  |  | 0 | my $bits = 0; | 
| 648 | 0 |  |  |  |  | 0 | my $mask = $$bstr{Mask}; | 
| 649 | 0 |  |  |  |  | 0 | while ($mask) { | 
| 650 | 0 |  |  |  |  | 0 | ++$bits; | 
| 651 | 0 |  |  |  |  | 0 | $mask >>= 1; | 
| 652 |  |  |  |  |  |  | } | 
| 653 | 0 |  |  |  |  | 0 | return $bits + 8 * ($$bstr{Len} - $$bstr{Pos}); | 
| 654 |  |  |  |  |  |  | } | 
| 655 |  |  |  |  |  |  |  | 
| 656 |  |  |  |  |  |  | #------------------------------------------------------------------------------ | 
| 657 |  |  |  |  |  |  | # Get integer from bitstream | 
| 658 |  |  |  |  |  |  | # Inputs: 0) BitStream ref, 1) number of bits | 
| 659 |  |  |  |  |  |  | # Returns: integer (and increments position in bitstream) | 
| 660 |  |  |  |  |  |  | sub GetIntN($$) | 
| 661 |  |  |  |  |  |  | { | 
| 662 | 31 |  |  | 31 | 0 | 54 | my ($bstr, $bits) = @_; | 
| 663 | 31 |  |  |  |  | 34 | my $val = 0; | 
| 664 | 31 |  |  |  |  | 54 | while ($bits--) { | 
| 665 | 67 |  |  |  |  | 85 | $val <<= 1; | 
| 666 | 67 | 100 |  |  |  | 124 | ++$val if $$bstr{Mask} & $$bstr{Word}; | 
| 667 | 67 | 100 |  |  |  | 133 | $$bstr{Mask} >>= 1 and next; | 
| 668 | 1 | 50 |  |  |  | 2 | ReadNextWord($bstr) or last; | 
| 669 |  |  |  |  |  |  | } | 
| 670 | 31 |  |  |  |  | 57 | return $val; | 
| 671 |  |  |  |  |  |  | } | 
| 672 |  |  |  |  |  |  |  | 
| 673 |  |  |  |  |  |  | #------------------------------------------------------------------------------ | 
| 674 |  |  |  |  |  |  | # Get Exp-Golomb integer from bitstream | 
| 675 |  |  |  |  |  |  | # Inputs: 0) BitStream ref | 
| 676 |  |  |  |  |  |  | # Returns: integer (and increments position in bitstream) | 
| 677 |  |  |  |  |  |  | sub GetGolomb($) | 
| 678 |  |  |  |  |  |  | { | 
| 679 | 14 |  |  | 14 | 0 | 20 | my $bstr = shift; | 
| 680 |  |  |  |  |  |  | # first, count the number of zero bits to get the integer bit width | 
| 681 | 14 |  |  |  |  | 17 | my $count = 0; | 
| 682 | 14 |  |  |  |  | 31 | until ($$bstr{Mask} & $$bstr{Word}) { | 
| 683 | 14 |  |  |  |  | 15 | ++$count; | 
| 684 | 14 | 100 |  |  |  | 34 | $$bstr{Mask} >>= 1 and next; | 
| 685 | 1 | 50 |  |  |  | 2 | ReadNextWord($bstr) or last; | 
| 686 |  |  |  |  |  |  | } | 
| 687 |  |  |  |  |  |  | # then return the adjusted integer | 
| 688 | 14 |  |  |  |  | 21 | return GetIntN($bstr, $count + 1) - 1; | 
| 689 |  |  |  |  |  |  | } | 
| 690 |  |  |  |  |  |  |  | 
| 691 |  |  |  |  |  |  | #------------------------------------------------------------------------------ | 
| 692 |  |  |  |  |  |  | # Get signed Exp-Golomb integer from bitstream | 
| 693 |  |  |  |  |  |  | # Inputs: 0) BitStream ref | 
| 694 |  |  |  |  |  |  | # Returns: integer (and increments position in bitstream) | 
| 695 |  |  |  |  |  |  | sub GetGolombS($) | 
| 696 |  |  |  |  |  |  | { | 
| 697 | 0 |  |  | 0 | 0 | 0 | my $bstr = shift; | 
| 698 | 0 |  |  |  |  | 0 | my $val = GetGolomb($bstr) + 1; | 
| 699 | 0 | 0 |  |  |  | 0 | return ($val & 1) ? -($val >> 1) : ($val >> 1); | 
| 700 |  |  |  |  |  |  | } | 
| 701 |  |  |  |  |  |  |  | 
| 702 |  |  |  |  |  |  | # end bitstream functions | 
| 703 |  |  |  |  |  |  | #============================================================================== | 
| 704 |  |  |  |  |  |  |  | 
| 705 |  |  |  |  |  |  | #------------------------------------------------------------------------------ | 
| 706 |  |  |  |  |  |  | # Decode H.264 scaling matrices | 
| 707 |  |  |  |  |  |  | # Inputs: 0) BitStream ref | 
| 708 |  |  |  |  |  |  | # Reference: http://ffmpeg.org/ | 
| 709 |  |  |  |  |  |  | sub DecodeScalingMatrices($) | 
| 710 |  |  |  |  |  |  | { | 
| 711 | 1 |  |  | 1 | 0 | 2 | my $bstr = shift; | 
| 712 | 1 | 50 |  |  |  | 2 | if (GetIntN($bstr, 1)) { | 
| 713 | 1 |  |  |  |  | 2 | my ($i, $j); | 
| 714 | 1 |  |  |  |  | 3 | for ($i=0; $i<8; ++$i) { | 
| 715 | 8 | 100 |  |  |  | 27 | my $size = $i<6 ? 16 : 64; | 
| 716 | 8 | 50 |  |  |  | 12 | next unless GetIntN($bstr, 1); | 
| 717 | 0 |  |  |  |  | 0 | my ($last, $next) = (8, 8); | 
| 718 | 0 |  |  |  |  | 0 | for ($j=0; $j<$size; ++$j) { | 
| 719 | 0 | 0 |  |  |  | 0 | $next = ($last + GetGolombS($bstr)) & 0xff if $next; | 
| 720 | 0 | 0 | 0 |  |  | 0 | last unless $j or $next; | 
| 721 |  |  |  |  |  |  | } | 
| 722 |  |  |  |  |  |  | } | 
| 723 |  |  |  |  |  |  | } | 
| 724 |  |  |  |  |  |  | } | 
| 725 |  |  |  |  |  |  |  | 
| 726 |  |  |  |  |  |  | #------------------------------------------------------------------------------ | 
| 727 |  |  |  |  |  |  | # Parse H.264 sequence parameter set RBSP (ref 1) | 
| 728 |  |  |  |  |  |  | # Inputs: 0) ExifTool ref, 1) tag table ref, 2) data ref | 
| 729 |  |  |  |  |  |  | # Notes: All this just to get the image size! | 
| 730 |  |  |  |  |  |  | sub ParseSeqParamSet($$$) | 
| 731 |  |  |  |  |  |  | { | 
| 732 | 1 |  |  | 1 | 0 | 3 | my ($et, $tagTablePtr, $dataPt) = @_; | 
| 733 |  |  |  |  |  |  | # initialize our bitstream object | 
| 734 | 1 | 50 |  |  |  | 2 | my $bstr = NewBitStream($dataPt) or return; | 
| 735 | 1 |  |  |  |  | 2 | my ($t, $i, $j, $n); | 
| 736 |  |  |  |  |  |  | # the messy nature of H.264 encoding makes it difficult to use | 
| 737 |  |  |  |  |  |  | # data-driven structure parsing, so I code it explicitly (yuck!) | 
| 738 | 1 |  |  |  |  | 2 | $t = GetIntN($bstr, 8);         # profile_idc | 
| 739 | 1 |  |  |  |  | 3 | GetIntN($bstr, 16);             # constraints and level_idc | 
| 740 | 1 |  |  |  |  | 2 | GetGolomb($bstr);               # seq_parameter_set_id | 
| 741 | 1 | 50 |  |  |  | 2 | if ($t >= 100) { # (ref b) | 
| 742 | 1 |  |  |  |  | 3 | $t = GetGolomb($bstr);      # chroma_format_idc | 
| 743 | 1 | 50 |  |  |  | 3 | if ($t == 3) { | 
| 744 | 0 |  |  |  |  | 0 | GetIntN($bstr, 1);      # separate_colour_plane_flag | 
| 745 | 0 |  |  |  |  | 0 | $n = 12; | 
| 746 |  |  |  |  |  |  | } else { | 
| 747 | 1 |  |  |  |  | 1 | $n = 8; | 
| 748 |  |  |  |  |  |  | } | 
| 749 | 1 |  |  |  |  | 3 | GetGolomb($bstr);           # bit_depth_luma_minus8 | 
| 750 | 1 |  |  |  |  | 3 | GetGolomb($bstr);           # bit_depth_chroma_minus8 | 
| 751 | 1 |  |  |  |  | 9 | GetIntN($bstr, 1);          # qpprime_y_zero_transform_bypass_flag | 
| 752 | 1 |  |  |  |  | 3 | DecodeScalingMatrices($bstr); | 
| 753 |  |  |  |  |  |  | } | 
| 754 | 1 |  |  |  |  | 3 | GetGolomb($bstr);               # log2_max_frame_num_minus4 | 
| 755 | 1 |  |  |  |  | 2 | $t = GetGolomb($bstr);          # pic_order_cnt_type | 
| 756 | 1 | 50 |  |  |  | 3 | if ($t == 0) { | 
|  |  | 0 |  |  |  |  |  | 
| 757 | 1 |  |  |  |  | 2 | GetGolomb($bstr);           # log2_max_pic_order_cnt_lsb_minus4 | 
| 758 |  |  |  |  |  |  | } elsif ($t == 1) { | 
| 759 | 0 |  |  |  |  | 0 | GetIntN($bstr, 1);          # delta_pic_order_always_zero_flag | 
| 760 | 0 |  |  |  |  | 0 | GetGolomb($bstr);           # offset_for_non_ref_pic | 
| 761 | 0 |  |  |  |  | 0 | GetGolomb($bstr);           # offset_for_top_to_bottom_field | 
| 762 | 0 |  |  |  |  | 0 | $n = GetGolomb($bstr);      # num_ref_frames_in_pic_order_cnt_cycle | 
| 763 | 0 |  |  |  |  | 0 | for ($i=0; $i<$n; ++$i) { | 
| 764 | 0 |  |  |  |  | 0 | GetGolomb($bstr);       # offset_for_ref_frame[i] | 
| 765 |  |  |  |  |  |  | } | 
| 766 |  |  |  |  |  |  | } | 
| 767 | 1 |  |  |  |  | 3 | GetGolomb($bstr);               # num_ref_frames | 
| 768 | 1 |  |  |  |  | 2 | GetIntN($bstr, 1);              # gaps_in_frame_num_value_allowed_flag | 
| 769 | 1 |  |  |  |  | 1 | my $w = GetGolomb($bstr);       # pic_width_in_mbs_minus1 | 
| 770 | 1 |  |  |  |  | 2 | my $h = GetGolomb($bstr);       # pic_height_in_map_units_minus1 | 
| 771 | 1 |  |  |  |  | 6 | my $f = GetIntN($bstr, 1);      # frame_mbs_only_flag | 
| 772 | 1 | 50 |  |  |  | 3 | $f or GetIntN($bstr, 1);        # mb_adaptive_frame_field_flag | 
| 773 | 1 |  |  |  |  | 2 | GetIntN($bstr, 1);              # direct_8x8_inference_flag | 
| 774 |  |  |  |  |  |  | # convert image size to pixels | 
| 775 | 1 |  |  |  |  | 2 | $w = ($w + 1) * 16; | 
| 776 | 1 |  |  |  |  | 2 | $h = (2 - $f) * ($h + 1) * 16; | 
| 777 |  |  |  |  |  |  | # account for cropping (if any) | 
| 778 | 1 |  |  |  |  | 2 | $t = GetIntN($bstr, 1);         # frame_cropping_flag | 
| 779 | 1 | 50 |  |  |  | 3 | if ($t) { | 
| 780 | 1 |  |  |  |  | 3 | my $m = 4 - $f * 2; | 
| 781 | 1 |  |  |  |  | 2 | $w -=  4 * GetGolomb($bstr);# frame_crop_left_offset | 
| 782 | 1 |  |  |  |  | 10 | $w -=  4 * GetGolomb($bstr);# frame_crop_right_offset | 
| 783 | 1 |  |  |  |  | 2 | $h -= $m * GetGolomb($bstr);# frame_crop_top_offset | 
| 784 | 1 |  |  |  |  | 3 | $h -= $m * GetGolomb($bstr);# frame_crop_bottom_offset | 
| 785 |  |  |  |  |  |  | } | 
| 786 |  |  |  |  |  |  | # quick validity checks (just in case) | 
| 787 | 1 | 50 |  |  |  | 3 | return unless $$bstr{Mask}; | 
| 788 | 1 | 50 | 33 |  |  | 9 | if ($w>=160 and $w<=4096 and $h>=120 and $h<=3072) { | 
|  |  |  | 33 |  |  |  |  | 
|  |  |  | 33 |  |  |  |  | 
| 789 | 1 |  |  |  |  | 5 | $et->HandleTag($tagTablePtr, ImageWidth => $w); | 
| 790 | 1 |  |  |  |  | 3 | $et->HandleTag($tagTablePtr, ImageHeight => $h); | 
| 791 |  |  |  |  |  |  | # (whew! -- so much work just to get ImageSize!!) | 
| 792 |  |  |  |  |  |  | } | 
| 793 |  |  |  |  |  |  | # return now unless interested in picture timing information | 
| 794 | 1 | 50 |  |  |  | 5 | return unless $parsePictureTiming; | 
| 795 |  |  |  |  |  |  |  | 
| 796 |  |  |  |  |  |  | # parse vui parameters if they exist | 
| 797 | 0 | 0 |  |  |  | 0 | GetIntN($bstr, 1) or return;    # vui_parameters_present_flag | 
| 798 | 0 |  |  |  |  | 0 | $t = GetIntN($bstr, 1);         # aspect_ratio_info_present_flag | 
| 799 | 0 | 0 |  |  |  | 0 | if ($t) { | 
| 800 | 0 |  |  |  |  | 0 | $t = GetIntN($bstr, 8);     # aspect_ratio_idc | 
| 801 | 0 | 0 |  |  |  | 0 | if ($t == 255) {            # Extended_SAR ? | 
| 802 | 0 |  |  |  |  | 0 | GetIntN($bstr, 32);     # sar_width/sar_height | 
| 803 |  |  |  |  |  |  | } | 
| 804 |  |  |  |  |  |  | } | 
| 805 | 0 |  |  |  |  | 0 | $t = GetIntN($bstr, 1);         # overscan_info_present_flag | 
| 806 | 0 | 0 |  |  |  | 0 | GetIntN($bstr, 1) if $t;        # overscan_appropriate_flag | 
| 807 | 0 |  |  |  |  | 0 | $t = GetIntN($bstr, 1);         # video_signal_type_present_flag | 
| 808 | 0 | 0 |  |  |  | 0 | if ($t) { | 
| 809 | 0 |  |  |  |  | 0 | GetIntN($bstr, 4);          # video_format/video_full_range_flag | 
| 810 | 0 |  |  |  |  | 0 | $t = GetIntN($bstr, 1);     # colour_description_present_flag | 
| 811 | 0 | 0 |  |  |  | 0 | GetIntN($bstr, 24) if $t;   # colour_primaries/transfer_characteristics/matrix_coefficients | 
| 812 |  |  |  |  |  |  | } | 
| 813 | 0 |  |  |  |  | 0 | $t = GetIntN($bstr, 1);         # chroma_loc_info_present_flag | 
| 814 | 0 | 0 |  |  |  | 0 | if ($t) { | 
| 815 | 0 |  |  |  |  | 0 | GetGolomb($bstr);           # chroma_sample_loc_type_top_field | 
| 816 | 0 |  |  |  |  | 0 | GetGolomb($bstr);           # chroma_sample_loc_type_bottom_field | 
| 817 |  |  |  |  |  |  | } | 
| 818 | 0 |  |  |  |  | 0 | $t = GetIntN($bstr, 1);         # timing_info_present_flag | 
| 819 | 0 | 0 |  |  |  | 0 | if ($t) { | 
| 820 | 0 | 0 |  |  |  | 0 | return if BitsLeft($bstr) < 65; | 
| 821 | 0 |  |  |  |  | 0 | $$et{VUI_units} = GetIntN($bstr, 32); # num_units_in_tick | 
| 822 | 0 |  |  |  |  | 0 | $$et{VUI_scale} = GetIntN($bstr, 32); # time_scale | 
| 823 | 0 |  |  |  |  | 0 | GetIntN($bstr, 1);          # fixed_frame_rate_flag | 
| 824 |  |  |  |  |  |  | } | 
| 825 | 0 |  |  |  |  | 0 | my $hard; | 
| 826 | 0 |  |  |  |  | 0 | for ($j=0; $j<2; ++$j) { | 
| 827 | 0 |  |  |  |  | 0 | $t = GetIntN($bstr, 1);     # nal_/vcl_hrd_parameters_present_flag | 
| 828 | 0 | 0 |  |  |  | 0 | if ($t) { | 
| 829 | 0 |  |  |  |  | 0 | $$et{VUI_hard} = 1; | 
| 830 | 0 |  |  |  |  | 0 | $hard = 1; | 
| 831 | 0 |  |  |  |  | 0 | $n = GetGolomb($bstr);  # cpb_cnt_minus1 | 
| 832 | 0 |  |  |  |  | 0 | GetIntN($bstr, 8);      # bit_rate_scale/cpb_size_scale | 
| 833 | 0 |  |  |  |  | 0 | for ($i=0; $i<=$n; ++$i) { | 
| 834 | 0 |  |  |  |  | 0 | GetGolomb($bstr);   # bit_rate_value_minus1[SchedSelIdx] | 
| 835 | 0 |  |  |  |  | 0 | GetGolomb($bstr);   # cpb_size_value_minus1[SchedSelIdx] | 
| 836 | 0 |  |  |  |  | 0 | GetIntN($bstr, 1);  # cbr_flag[SchedSelIdx] | 
| 837 |  |  |  |  |  |  | } | 
| 838 | 0 |  |  |  |  | 0 | GetIntN($bstr, 5);      # initial_cpb_removal_delay_length_minus1 | 
| 839 | 0 |  |  |  |  | 0 | $$et{VUI_clen} = GetIntN($bstr, 5); # cpb_removal_delay_length_minus1 | 
| 840 | 0 |  |  |  |  | 0 | $$et{VUI_dlen} = GetIntN($bstr, 5); # dpb_output_delay_length_minus1 | 
| 841 | 0 |  |  |  |  | 0 | $$et{VUI_toff} = GetIntN($bstr, 5); # time_offset_length | 
| 842 |  |  |  |  |  |  | } | 
| 843 |  |  |  |  |  |  | } | 
| 844 | 0 | 0 |  |  |  | 0 | GetIntN($bstr, 1) if $hard;     # low_delay_hrd_flag | 
| 845 | 0 |  |  |  |  | 0 | $$et{VUI_pic} = GetIntN($bstr, 1);    # pic_struct_present_flag | 
| 846 |  |  |  |  |  |  | # (don't yet decode the rest of the vui data) | 
| 847 |  |  |  |  |  |  | } | 
| 848 |  |  |  |  |  |  |  | 
| 849 |  |  |  |  |  |  | #------------------------------------------------------------------------------ | 
| 850 |  |  |  |  |  |  | # Parse H.264 picture timing SEI message (payload type 1) (ref 1) | 
| 851 |  |  |  |  |  |  | # Inputs: 0) ExifTool ref, 1) data ref | 
| 852 |  |  |  |  |  |  | # Notes: this routine is for test purposes only, and not called unless the | 
| 853 |  |  |  |  |  |  | #        $parsePictureTiming flag is set | 
| 854 |  |  |  |  |  |  | sub ParsePictureTiming($$) | 
| 855 |  |  |  |  |  |  | { | 
| 856 | 0 |  |  | 0 | 0 | 0 | my ($et, $dataPt) = @_; | 
| 857 | 0 | 0 |  |  |  | 0 | my $bstr = NewBitStream($dataPt) or return; | 
| 858 | 0 |  |  |  |  | 0 | my ($i, $t, $n); | 
| 859 |  |  |  |  |  |  | # the specification is very odd on this point: the following delays | 
| 860 |  |  |  |  |  |  | # exist if the VUI hardware parameters are present, or if | 
| 861 |  |  |  |  |  |  | # "determined by the application, by some means not specified" -- WTF?? | 
| 862 | 0 | 0 |  |  |  | 0 | if ($$et{VUI_hard}) { | 
| 863 | 0 |  |  |  |  | 0 | GetIntN($bstr, $$et{VUI_clen} + 1);   # cpb_removal_delay | 
| 864 | 0 |  |  |  |  | 0 | GetIntN($bstr, $$et{VUI_dlen} + 1);   # dpb_output_delay | 
| 865 |  |  |  |  |  |  | } | 
| 866 | 0 | 0 |  |  |  | 0 | if ($$et{VUI_pic}) { | 
| 867 | 0 |  |  |  |  | 0 | $t = GetIntN($bstr, 4);     # pic_struct | 
| 868 |  |  |  |  |  |  | # determine NumClockTS ($n) | 
| 869 | 0 |  |  |  |  | 0 | $n = { 0=>1, 1=>1, 2=>1, 3=>2, 4=>2, 5=>3, 6=>3, 7=>2, 8=>3 }->{$t}; | 
| 870 | 0 | 0 |  |  |  | 0 | $n or return; | 
| 871 | 0 |  |  |  |  | 0 | for ($i=0; $i<$n; ++$i) { | 
| 872 | 0 |  |  |  |  | 0 | $t = GetIntN($bstr, 1); # clock_timestamp_flag[i] | 
| 873 | 0 | 0 |  |  |  | 0 | next unless $t; | 
| 874 | 0 |  |  |  |  | 0 | my ($nu, $s, $m, $h, $o); | 
| 875 | 0 |  |  |  |  | 0 | GetIntN($bstr, 2);      # ct_type | 
| 876 | 0 |  |  |  |  | 0 | $nu = GetIntN($bstr, 1);# nuit_field_based_flag | 
| 877 | 0 |  |  |  |  | 0 | GetIntN($bstr, 5);      # counting_type | 
| 878 | 0 |  |  |  |  | 0 | $t = GetIntN($bstr, 1); # full_timestamp_flag | 
| 879 | 0 |  |  |  |  | 0 | GetIntN($bstr, 1);      # discontinuity_flag | 
| 880 | 0 |  |  |  |  | 0 | GetIntN($bstr, 1);      # cnt_dropped_flag | 
| 881 | 0 |  |  |  |  | 0 | GetIntN($bstr, 8);      # n_frames | 
| 882 | 0 | 0 |  |  |  | 0 | if ($t) { | 
| 883 | 0 |  |  |  |  | 0 | $s = GetIntN($bstr, 6); # seconds_value | 
| 884 | 0 |  |  |  |  | 0 | $m = GetIntN($bstr, 6); # minutes_value | 
| 885 | 0 |  |  |  |  | 0 | $h = GetIntN($bstr, 5); # hours_value | 
| 886 |  |  |  |  |  |  | } else { | 
| 887 | 0 |  |  |  |  | 0 | $t = GetIntN($bstr, 1); # seconds_flag | 
| 888 | 0 | 0 |  |  |  | 0 | if ($t) { | 
| 889 | 0 |  |  |  |  | 0 | $s = GetIntN($bstr, 6); # seconds_value | 
| 890 | 0 |  |  |  |  | 0 | $t = GetIntN($bstr, 1); # minutes_flag | 
| 891 | 0 | 0 |  |  |  | 0 | if ($t) { | 
| 892 | 0 |  |  |  |  | 0 | $m = GetIntN($bstr, 6); # minutes_value | 
| 893 | 0 |  |  |  |  | 0 | $t = GetIntN($bstr, 1); # hours_flag | 
| 894 | 0 | 0 |  |  |  | 0 | $h = GetIntN($bstr, 5) if $t;   # hours_value | 
| 895 |  |  |  |  |  |  | } | 
| 896 |  |  |  |  |  |  | } | 
| 897 |  |  |  |  |  |  | } | 
| 898 | 0 | 0 |  |  |  | 0 | if ($$et{VUI_toff}) { | 
| 899 | 0 |  |  |  |  | 0 | $o = GetIntN($bstr, $$et{VUI_toff});  # time_offset | 
| 900 |  |  |  |  |  |  | } | 
| 901 | 0 |  |  |  |  | 0 | last;   # only parse the first clock timestamp found | 
| 902 |  |  |  |  |  |  | } | 
| 903 |  |  |  |  |  |  | } | 
| 904 |  |  |  |  |  |  | } | 
| 905 |  |  |  |  |  |  |  | 
| 906 |  |  |  |  |  |  | #------------------------------------------------------------------------------ | 
| 907 |  |  |  |  |  |  | # Process H.264 Supplementary Enhancement Information (ref 1/PH) | 
| 908 |  |  |  |  |  |  | # Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref | 
| 909 |  |  |  |  |  |  | # Returns: 1 if we processed payload type 5 | 
| 910 |  |  |  |  |  |  | # Payload types: | 
| 911 |  |  |  |  |  |  | #   0 - buffer period | 
| 912 |  |  |  |  |  |  | #   1 - pic timing | 
| 913 |  |  |  |  |  |  | #   2 - pan scan rect | 
| 914 |  |  |  |  |  |  | #   3 - filler payload | 
| 915 |  |  |  |  |  |  | #   4 - user data registered itu t t35 | 
| 916 |  |  |  |  |  |  | #   5 - user data unregistered | 
| 917 |  |  |  |  |  |  | #   6 - recovery point | 
| 918 |  |  |  |  |  |  | #   7 - dec ref pic marking repetition | 
| 919 |  |  |  |  |  |  | #   8 - spare pic | 
| 920 |  |  |  |  |  |  | #   9 - sene info | 
| 921 |  |  |  |  |  |  | #  10 - sub seq info | 
| 922 |  |  |  |  |  |  | #  11 - sub seq layer characteristics | 
| 923 |  |  |  |  |  |  | #  12 - sub seq characteristics | 
| 924 |  |  |  |  |  |  | #  13 - full frame freeze | 
| 925 |  |  |  |  |  |  | #  14 - full frame freeze release | 
| 926 |  |  |  |  |  |  | #  15 - full frame snapshot | 
| 927 |  |  |  |  |  |  | #  16 - progressive refinement segment start | 
| 928 |  |  |  |  |  |  | #  17 - progressive refinement segment end | 
| 929 |  |  |  |  |  |  | #  18 - motion constrained slice group set | 
| 930 |  |  |  |  |  |  | sub ProcessSEI($$) | 
| 931 |  |  |  |  |  |  | { | 
| 932 | 1 |  |  | 1 | 0 | 2 | my ($et, $dirInfo) = @_; | 
| 933 | 1 |  |  |  |  | 2 | my $dataPt = $$dirInfo{DataPt}; | 
| 934 | 1 |  |  |  |  | 2 | my $end = length($$dataPt); | 
| 935 | 1 |  |  |  |  | 2 | my $pos = 0; | 
| 936 | 1 |  |  |  |  | 1 | my ($type, $size, $index, $t); | 
| 937 |  |  |  |  |  |  |  | 
| 938 |  |  |  |  |  |  | # scan through SEI payload for type 5 (the unregistered user data) | 
| 939 | 1 |  |  |  |  | 2 | for (;;) { | 
| 940 | 2 |  |  |  |  | 3 | $type = 0; | 
| 941 | 2 |  |  |  |  | 11 | for (;;) { | 
| 942 | 2 | 50 |  |  |  | 6 | return 0 if $pos >= $end; | 
| 943 | 2 |  |  |  |  | 7 | $t = Get8u($dataPt, $pos++);    # payload type | 
| 944 | 2 |  |  |  |  | 3 | $type += $t; | 
| 945 | 2 | 50 |  |  |  | 5 | last unless $t == 255; | 
| 946 |  |  |  |  |  |  | } | 
| 947 | 2 | 50 |  |  |  | 4 | return 0 if $type == 0x80;  # terminator (ref PH - maybe byte alignment bits?) | 
| 948 | 2 |  |  |  |  | 4 | $size = 0; | 
| 949 | 2 |  |  |  |  | 3 | for (;;) { | 
| 950 | 2 | 50 |  |  |  | 4 | return 0 if $pos >= $end; | 
| 951 | 2 |  |  |  |  | 5 | $t = Get8u($dataPt, $pos++);    # payload data length | 
| 952 | 2 |  |  |  |  | 3 | $size += $t; | 
| 953 | 2 | 50 |  |  |  | 5 | last unless $t == 255; | 
| 954 |  |  |  |  |  |  | } | 
| 955 | 2 | 50 |  |  |  | 4 | return 0 if $pos + $size > $end; | 
| 956 | 2 |  |  |  |  | 22 | $et->VPrint(1,"    (SEI type $type)\n"); | 
| 957 | 2 | 50 |  |  |  | 12 | if ($type == 1) {                   # picture timing information | 
|  |  | 100 |  |  |  |  |  | 
| 958 | 0 | 0 |  |  |  | 0 | if ($parsePictureTiming) { | 
| 959 | 0 |  |  |  |  | 0 | my $buff = substr($$dataPt, $pos, $size); | 
| 960 | 0 |  |  |  |  | 0 | ParsePictureTiming($et, $dataPt); | 
| 961 |  |  |  |  |  |  | } | 
| 962 |  |  |  |  |  |  | } elsif ($type == 5) {              # unregistered user data | 
| 963 | 1 |  |  |  |  | 2 | last; # exit loop to process user data now | 
| 964 |  |  |  |  |  |  | } | 
| 965 | 1 |  |  |  |  | 2 | $pos += $size; | 
| 966 |  |  |  |  |  |  | } | 
| 967 |  |  |  |  |  |  |  | 
| 968 |  |  |  |  |  |  | # look for our 16-byte UUID | 
| 969 |  |  |  |  |  |  | # - plus "MDPM" for "ModifiedDVPackMeta" | 
| 970 |  |  |  |  |  |  | # - plus "GA94" for closed-caption data (currently not decoded) | 
| 971 | 1 | 50 | 33 |  |  | 5 | return 0 unless $size > 20 and substr($$dataPt, $pos, 20) eq | 
| 972 |  |  |  |  |  |  | "\x17\xee\x8c\x60\xf8\x4d\x11\xd9\x8c\xd6\x08\0\x20\x0c\x9a\x66MDPM"; | 
| 973 |  |  |  |  |  |  | # | 
| 974 |  |  |  |  |  |  | # parse the MDPM records in the UUID 17ee8c60f84d11d98cd60800200c9a66 | 
| 975 |  |  |  |  |  |  | # unregistered user data payload (ref PH) | 
| 976 |  |  |  |  |  |  | # | 
| 977 | 1 |  |  |  |  | 4 | my $tagTablePtr = GetTagTable('Image::ExifTool::H264::MDPM'); | 
| 978 | 1 |  |  |  |  | 3 | my $oldIndent = $$et{INDENT}; | 
| 979 | 1 |  |  |  |  | 3 | $$et{INDENT} .= '| '; | 
| 980 | 1 |  |  |  |  | 1 | $end = $pos + $size;    # end of payload | 
| 981 | 1 |  |  |  |  | 2 | $pos += 20;             # skip UUID + "MDPM" | 
| 982 | 1 |  |  |  |  | 2 | my $num = Get8u($dataPt, $pos++);   # get entry count | 
| 983 | 1 |  |  |  |  | 2 | my $lastTag = 0; | 
| 984 | 1 | 50 |  |  |  | 9 | $et->VerboseDir('MDPM', $num) if $et->Options('Verbose'); | 
| 985 |  |  |  |  |  |  | # walk through entries in the MDPM payload | 
| 986 | 1 |  | 66 |  |  | 5 | for ($index=0; $index<$num and $pos<$end; ++$index) { | 
| 987 | 6 |  |  |  |  | 17 | my $tag = Get8u($dataPt, $pos); | 
| 988 | 6 | 50 |  |  |  | 20 | if ($tag <= $lastTag) { # should be in numerical order (PH) | 
| 989 | 0 |  |  |  |  | 0 | $et->Warn('Entries in MDPM directory are out of sequence'); | 
| 990 | 0 |  |  |  |  | 0 | last; | 
| 991 |  |  |  |  |  |  | } | 
| 992 | 6 |  |  |  |  | 11 | $lastTag = $tag; | 
| 993 | 6 |  |  |  |  | 13 | my $buff = substr($$dataPt, $pos + 1, 4); | 
| 994 | 6 |  |  |  |  | 8 | my $from; | 
| 995 | 6 |  |  |  |  | 18 | my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag); | 
| 996 | 6 | 50 |  |  |  | 14 | if ($tagInfo) { | 
| 997 |  |  |  |  |  |  | # use our own print conversion for Unknown tags | 
| 998 | 6 | 50 | 33 |  |  | 13 | if ($$tagInfo{Unknown} and not $$tagInfo{SetPrintConv}) { | 
| 999 | 0 |  |  |  |  | 0 | $$tagInfo{PrintConv} = 'sprintf("0x%.8x", unpack("N", $val))'; | 
| 1000 | 0 |  |  |  |  | 0 | $$tagInfo{SetPrintConv} = 1; | 
| 1001 |  |  |  |  |  |  | } | 
| 1002 |  |  |  |  |  |  | # combine with next value(s) if necessary | 
| 1003 | 6 |  |  |  |  | 11 | my $combine = $$tagTablePtr{$tag}{Combine}; | 
| 1004 | 6 |  |  |  |  | 13 | while ($combine) { | 
| 1005 | 1 | 50 |  |  |  | 3 | last if $pos + 5 >= $end; | 
| 1006 | 1 |  |  |  |  | 3 | my $t =  Get8u($dataPt, $pos + 5); | 
| 1007 | 1 | 50 |  |  |  | 4 | last if $t != $lastTag + 1; # must be consecutive tag ID's | 
| 1008 | 1 |  |  |  |  | 9 | $pos += 5; | 
| 1009 | 1 |  |  |  |  | 4 | $buff .= substr($$dataPt, $pos + 1, 4); | 
| 1010 | 1 | 50 |  |  |  | 3 | $from = $index unless defined $from; | 
| 1011 | 1 |  |  |  |  | 2 | ++$index; | 
| 1012 | 1 |  |  |  |  | 2 | ++$lastTag; | 
| 1013 | 1 |  |  |  |  | 2 | --$combine; | 
| 1014 |  |  |  |  |  |  | } | 
| 1015 | 6 | 100 |  |  |  | 27 | $et->HandleTag($tagTablePtr, $tag, undef, | 
| 1016 |  |  |  |  |  |  | TagInfo => $tagInfo, | 
| 1017 |  |  |  |  |  |  | DataPt  => \$buff, | 
| 1018 |  |  |  |  |  |  | Size    => length($buff), | 
| 1019 |  |  |  |  |  |  | Index   => defined $from ? "$from-$index" : $index, | 
| 1020 |  |  |  |  |  |  | ); | 
| 1021 |  |  |  |  |  |  | } | 
| 1022 | 6 |  |  |  |  | 24 | $pos += 5; | 
| 1023 |  |  |  |  |  |  | } | 
| 1024 | 1 |  |  |  |  | 3 | $$et{INDENT} = $oldIndent; | 
| 1025 | 1 |  |  |  |  | 2 | return 1; | 
| 1026 |  |  |  |  |  |  | } | 
| 1027 |  |  |  |  |  |  |  | 
| 1028 |  |  |  |  |  |  | #------------------------------------------------------------------------------ | 
| 1029 |  |  |  |  |  |  | # Extract information from H.264 video stream | 
| 1030 |  |  |  |  |  |  | # Inputs: 0) ExifTool ref, 1) data ref | 
| 1031 |  |  |  |  |  |  | # Returns: 0 = done parsing, 1 = we want to parse more of these | 
| 1032 |  |  |  |  |  |  | sub ParseH264Video($$) | 
| 1033 |  |  |  |  |  |  | { | 
| 1034 | 1 |  |  | 1 | 0 | 3 | my ($et, $dataPt) = @_; | 
| 1035 | 1 |  |  |  |  | 5 | my $verbose = $et->Options('Verbose'); | 
| 1036 | 1 |  |  |  |  | 3 | my $out = $et->Options('TextOut'); | 
| 1037 | 1 |  |  |  |  | 4 | my $tagTablePtr = GetTagTable('Image::ExifTool::H264::Main'); | 
| 1038 | 1 |  |  |  |  | 15 | my %parseNalUnit = ( 0x06 => 1, 0x07 => 1 );    # NAL unit types to parse | 
| 1039 | 1 |  |  |  |  | 2 | my $foundUserData; | 
| 1040 | 1 |  |  |  |  | 21 | my $len = length $$dataPt; | 
| 1041 | 1 |  |  |  |  | 2 | my $pos = 0; | 
| 1042 | 1 |  |  |  |  | 3 | while ($pos < $len) { | 
| 1043 | 6 |  |  |  |  | 11 | my ($nextPos, $end); | 
| 1044 |  |  |  |  |  |  | # find start of next NAL unit | 
| 1045 | 6 | 100 |  |  |  | 29 | if ($$dataPt =~ /(\0{2,3}\x01)/g) { | 
| 1046 | 5 |  |  |  |  | 9 | $nextPos = pos $$dataPt; | 
| 1047 | 5 |  |  |  |  | 9 | $end = $nextPos - length $1; | 
| 1048 | 5 | 100 |  |  |  | 12 | $pos or $pos = $nextPos, next; | 
| 1049 |  |  |  |  |  |  | } else { | 
| 1050 | 1 | 50 |  |  |  | 3 | last unless $pos; | 
| 1051 | 1 |  |  |  |  | 2 | $nextPos = $end = $len; | 
| 1052 |  |  |  |  |  |  | } | 
| 1053 | 5 | 50 |  |  |  | 9 | last if $pos >= $len; | 
| 1054 |  |  |  |  |  |  | # parse NAL unit from $pos to $end | 
| 1055 | 5 |  |  |  |  | 13 | my $nal_unit_type = Get8u($dataPt, $pos); | 
| 1056 | 5 |  |  |  |  | 10 | ++$pos; | 
| 1057 |  |  |  |  |  |  | # check forbidden_zero_bit | 
| 1058 | 5 | 50 |  |  |  | 11 | $nal_unit_type & 0x80 and $et->Warn('H264 forbidden bit error'), last; | 
| 1059 | 5 |  |  |  |  | 7 | $nal_unit_type &= 0x1f; | 
| 1060 |  |  |  |  |  |  | # ignore this NAL unit unless we will parse it | 
| 1061 | 5 | 100 | 66 |  |  | 21 | $parseNalUnit{$nal_unit_type} or $verbose or $pos = $nextPos, next; | 
| 1062 |  |  |  |  |  |  | # read NAL unit (and convert all 0x000003's to 0x0000 as per spec.) | 
| 1063 | 2 |  |  |  |  | 4 | my $buff = ''; | 
| 1064 | 2 |  |  |  |  | 5 | pos($$dataPt) = $pos + 1; | 
| 1065 | 2 |  |  |  |  | 9 | while ($$dataPt =~ /\0\0\x03/g) { | 
| 1066 | 5 | 100 |  |  |  | 12 | last if pos $$dataPt > $end; | 
| 1067 | 4 |  |  |  |  | 8 | $buff .= substr($$dataPt, $pos, pos($$dataPt)-1-$pos); | 
| 1068 | 4 |  |  |  |  | 12 | $pos = pos $$dataPt; | 
| 1069 |  |  |  |  |  |  | } | 
| 1070 | 2 |  |  |  |  | 5 | $buff .= substr($$dataPt, $pos, $end - $pos); | 
| 1071 | 2 | 50 |  |  |  | 5 | if ($verbose > 1) { | 
| 1072 | 0 |  |  |  |  | 0 | printf $out "  NAL Unit Type: 0x%x (%d bytes)\n",$nal_unit_type, length $buff; | 
| 1073 | 0 |  |  |  |  | 0 | $et->VerboseDump(\$buff); | 
| 1074 |  |  |  |  |  |  | } | 
| 1075 | 2 |  |  |  |  | 15 | pos($$dataPt) = $pos = $nextPos; | 
| 1076 |  |  |  |  |  |  |  | 
| 1077 | 2 | 100 |  |  |  | 8 | if ($nal_unit_type == 0x06) {       # sei_rbsp (supplemental enhancement info) | 
|  |  | 50 |  |  |  |  |  | 
| 1078 |  |  |  |  |  |  |  | 
| 1079 | 1 | 50 |  |  |  | 3 | if ($$et{GotNAL06}) { | 
| 1080 |  |  |  |  |  |  | # process only the first SEI unless ExtractEmbedded is set | 
| 1081 | 0 | 0 |  |  |  | 0 | next unless $et->Options('ExtractEmbedded'); | 
| 1082 | 0 |  |  |  |  | 0 | $$et{DOC_NUM} = $$et{GotNAL06}; | 
| 1083 |  |  |  |  |  |  | } | 
| 1084 | 1 |  |  |  |  | 4 | $foundUserData = ProcessSEI($et, { DataPt => \$buff } ); | 
| 1085 | 1 |  |  |  |  | 3 | delete $$et{DOC_NUM}; | 
| 1086 |  |  |  |  |  |  | # keep parsing SEI's until we find the user data | 
| 1087 | 1 | 50 |  |  |  | 3 | next unless $foundUserData; | 
| 1088 | 1 |  | 50 |  |  | 4 | $$et{GotNAL06} = ($$et{GotNAL06} || 0) + 1; | 
| 1089 |  |  |  |  |  |  |  | 
| 1090 |  |  |  |  |  |  | } elsif ($nal_unit_type == 0x07) {  # sequence_parameter_set_rbsp | 
| 1091 |  |  |  |  |  |  |  | 
| 1092 |  |  |  |  |  |  | # process this NAL unit type only once | 
| 1093 | 1 | 50 |  |  |  | 3 | next if $$et{GotNAL07}; | 
| 1094 | 1 |  |  |  |  | 2 | $$et{GotNAL07} = 1; | 
| 1095 | 1 |  |  |  |  | 3 | ParseSeqParamSet($et, $tagTablePtr, \$buff); | 
| 1096 |  |  |  |  |  |  | } | 
| 1097 |  |  |  |  |  |  | # we were successful, so don't parse this NAL unit type again | 
| 1098 | 2 |  |  |  |  | 7 | delete $parseNalUnit{$nal_unit_type}; | 
| 1099 |  |  |  |  |  |  | } | 
| 1100 |  |  |  |  |  |  | # parse one extra H264 frame if we didn't find the user data in this one | 
| 1101 |  |  |  |  |  |  | # (Panasonic cameras don't put the SEI in the first frame) | 
| 1102 | 1 | 50 | 33 |  |  | 5 | return 0 if $foundUserData or $$et{ParsedH264}; | 
| 1103 | 0 |  |  |  |  |  | $$et{ParsedH264} = 1; | 
| 1104 | 0 |  |  |  |  |  | return 1; | 
| 1105 |  |  |  |  |  |  | } | 
| 1106 |  |  |  |  |  |  |  | 
| 1107 |  |  |  |  |  |  | 1;  # end | 
| 1108 |  |  |  |  |  |  |  | 
| 1109 |  |  |  |  |  |  | __END__ |