diff options
author | exiftool <phil@work3.phy.queensu.ca> | 2018-01-04 15:15:23 -0500 |
---|---|---|
committer | exiftool <phil@work3.phy.queensu.ca> | 2018-01-04 15:15:23 -0500 |
commit | a79f1c3fb465e3cdc205ba2e2798df739ccb7673 (patch) | |
tree | 7aa7e6d721699cd1fe0be735b4521129c8c53cf7 /lib/Image/ExifTool | |
parent | df996adf2033d036c0e683a66e284583c0737f05 (diff) |
Update to 10.72
Diffstat (limited to 'lib/Image/ExifTool')
-rw-r--r-- | lib/Image/ExifTool/Geotag.pm | 21 | ||||
-rw-r--r-- | lib/Image/ExifTool/H264.pm | 30 | ||||
-rwxr-xr-x[-rw-r--r--] | lib/Image/ExifTool/Lang/de.pm | 0 | ||||
-rw-r--r-- | lib/Image/ExifTool/QuickTime.pm | 55 | ||||
-rw-r--r-- | lib/Image/ExifTool/QuickTimeStream.pl | 473 | ||||
-rw-r--r-- | lib/Image/ExifTool/TagLookup.pm | 16 | ||||
-rw-r--r-- | lib/Image/ExifTool/TagNames.pod | 65 | ||||
-rw-r--r-- | lib/Image/ExifTool/Writer.pl | 9 | ||||
-rw-r--r-- | lib/Image/ExifTool/XMP.pm | 3 |
9 files changed, 654 insertions, 18 deletions
diff --git a/lib/Image/ExifTool/Geotag.pm b/lib/Image/ExifTool/Geotag.pm index 71183a40..0101874c 100644 --- a/lib/Image/ExifTool/Geotag.pm +++ b/lib/Image/ExifTool/Geotag.pm @@ -24,7 +24,7 @@ use strict; use vars qw($VERSION); use Image::ExifTool qw(:Public); -$VERSION = '1.52'; +$VERSION = '1.53'; sub JITTER() { return 2 } # maximum time jitter @@ -237,7 +237,13 @@ sub LoadTrackLog($$;$) my $tag = $xmlTag{lc $2}; if ($tag) { $$fix{$tag} = $4; - $$has{orient} = 1 if $isOrient{$tag}; + if ($isOrient{$tag}) { + $$has{orient} = 1; + } elsif ($tag eq 'alt') { + # validate altitude + undef $$fix{alt} if defined $$fix{alt} and $$fix{alt} !~ /^[+-]?\d+\.?\d*/; + $$has{alt} = 1 if $$fix{alt}; # set "has altitude" flag if appropriate + } } } # loop through XML elements @@ -270,7 +276,13 @@ sub LoadTrackLog($$;$) @$fix{'lon','lat','alt'} = split ',', $1; } else { $$fix{$tag} = $1; - $$has{orient} = 1 if $isOrient{$tag}; + if ($isOrient{$tag}) { + $$has{orient} = 1; + } elsif ($tag eq 'alt') { + # validate altitude + undef $$fix{alt} if defined $$fix{alt} and $$fix{alt} !~ /^[+-]?\d+\.?\d*/; + $$has{alt} = 1 if $$fix{alt}; # set "has altitude" flag if appropriate + } } } next; @@ -287,11 +299,8 @@ sub LoadTrackLog($$;$) $e1 or $et->VPrint(0, "Timestamp format error in $from\n"), $e1 = 1; next; } - # validate altitude - undef $$fix{alt} if defined $$fix{alt} and $$fix{alt} !~ /^[+-]?\d+\.?\d*/; $isDate = 1; $canCut= 1 if defined $$fix{pdop} or defined $$fix{hdop} or defined $$fix{nsats}; - $$has{alt} = 1 if $$fix{alt}; # set "has altitude" flag if appropriate # generate extra fixes assuming an equally spaced track if ($$fix{begin}) { my $begin = GetTime($$fix{begin}); diff --git a/lib/Image/ExifTool/H264.pm b/lib/Image/ExifTool/H264.pm index 42b58a87..4dab9229 100644 --- a/lib/Image/ExifTool/H264.pm +++ b/lib/Image/ExifTool/H264.pm @@ -25,7 +25,7 @@ use Image::ExifTool qw(:DataAccess :Utils); use Image::ExifTool::Exif; use Image::ExifTool::GPS; -$VERSION = '1.14'; +$VERSION = '1.15'; sub ProcessSEI($$); @@ -710,7 +710,8 @@ sub DecodeScalingMatrices($) #------------------------------------------------------------------------------ # Parse H.264 sequence parameter set RBSP (ref 1) -# Inputs) 0) ExifTool ref, 1) tag table ref, 2) data ref +# Inputs: 0) ExifTool ref, 1) tag table ref, 2) data ref +# Notes: All this just to get the image size! sub ParseSeqParamSet($$$) { my ($et, $tagTablePtr, $dataPt) = @_; @@ -832,7 +833,9 @@ sub ParseSeqParamSet($$$) #------------------------------------------------------------------------------ # Parse H.264 picture timing SEI message (payload type 1) (ref 1) -# Inputs) 0) ExifTool ref, 1) data ref +# Inputs: 0) ExifTool ref, 1) data ref +# Notes: this routine is for test purposes only, and not called unless the +# $parsePictureTiming flag is set sub ParsePictureTiming($$) { my ($et, $dataPt) = @_; @@ -889,6 +892,26 @@ sub ParsePictureTiming($$) # Process H.264 Supplementary Enhancement Information (ref 1/PH) # Inputs: 0) Exiftool ref, 1) dirInfo ref, 2) tag table ref # Returns: 1 if we processed payload type 5 +# Payload types: +# 0 - buffer period +# 1 - pic timing +# 2 - pan scan rect +# 3 - filler payload +# 4 - user data registered itu t t35 +# 5 - user data unregistered +# 6 - recovery point +# 7 - dec ref pic marking repetition +# 8 - spare pic +# 9 - sene info +# 10 - sub seq info +# 11 - sub seq layer characteristics +# 12 - sub seq characteristics +# 13 - full frame freeze +# 14 - full frame freeze release +# 15 - full frame snapshot +# 16 - progressive refinement segment start +# 17 - progressive refinement segment end +# 18 - motion constrained slice group set sub ProcessSEI($$) { my ($et, $dirInfo) = @_; @@ -915,6 +938,7 @@ sub ProcessSEI($$) last unless $t == 255; } return 0 if $pos + $size > $end; + $et->VPrint(1," (SEI type $type)\n"); if ($type == 1) { # picture timing information if ($parsePictureTiming) { my $buff = substr($$dataPt, $pos, $size); diff --git a/lib/Image/ExifTool/Lang/de.pm b/lib/Image/ExifTool/Lang/de.pm index d6d434fb..d6d434fb 100644..100755 --- a/lib/Image/ExifTool/Lang/de.pm +++ b/lib/Image/ExifTool/Lang/de.pm diff --git a/lib/Image/ExifTool/QuickTime.pm b/lib/Image/ExifTool/QuickTime.pm index 65df126d..8b1be25f 100644 --- a/lib/Image/ExifTool/QuickTime.pm +++ b/lib/Image/ExifTool/QuickTime.pm @@ -42,7 +42,7 @@ use Image::ExifTool qw(:DataAccess :Utils); use Image::ExifTool::Exif; use Image::ExifTool::GPS; -$VERSION = '2.05'; +$VERSION = '2.06'; sub FixWrongFormat($); sub ProcessMOV($$;$); @@ -296,6 +296,15 @@ my %graphicsMode = ( 0x110 => 'Component Alpha', ); +# boxes for the various handler types that we want to save when ExtractEmbedded is enabled +my %eeBox = ( + # (nothing useful found yet in video stream) + # vide => { stco => 1, co64 => 1, stsz => 1, stz2 => 1, avcC => 1 }, + text => { stco => 1, co64 => 1, stsz => 1, stz2 => 1 }, + meta => { stco => 1, co64 => 1, stsz => 1, stz2 => 1 }, + '' => { 'gps ' => 1 }, # (no handler -- top level box) +); + # QuickTime atoms %Image::ExifTool::QuickTime::Main = ( PROCESS_PROC => \&ProcessMOV, @@ -632,7 +641,12 @@ my %graphicsMode = ( Name => 'CleanAperture', SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::CleanAperture' }, }, - # avcC - AVC configuration (ref http://thompsonng.blogspot.ca/2010/11/mp4-file-format-part-2.html) + avcC => { + # (see http://thompsonng.blogspot.ca/2010/11/mp4-file-format-part-2.html) + Name => 'AVCConfiguration', + Unknown => 1, + Binary => 1, + }, # hvcC - HEVC configuration # svcC - 7 bytes: 00 00 00 00 ff e0 00 # esds - elementary stream descriptor @@ -830,6 +844,11 @@ my %graphicsMode = ( Name => 'HTCTrack', SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::Track' }, }, + 'gps ' => { # GPS data written by Novatek cameras + Name => 'GPSDataList', + Unknown => 1, + Binary => 1, + }, # prfl - Profile (ref 12) # clip - clipping --> contains crgn (clip region) (ref 12) # mvex - movie extends --> contains mehd (movie extends header), trex (track extends) (ref 14) @@ -5803,6 +5822,7 @@ my %graphicsMode = ( # (sometimes this is a Pascal string, and sometimes it is a C string) RawConv => q{ $val=substr($val,1,ord($1)) if $val=~/^([\0-\x1f])/ and ord($1)<length($val); + ($$self{HandlerDesc} = $val) =~ s/\s+$//; length $val ? $val : undef; }, }, @@ -6698,11 +6718,16 @@ sub ProcessMOV($$;$) my $verbose = $et->Options('Verbose'); my $dataPos = $$dirInfo{Base} || 0; my $charsetQuickTime = $et->Options('CharsetQuickTime'); - my ($buff, $tag, $size, $track, $isUserData, %triplet, $doDefaultLang, $index); + my ($buff, $tag, $size, $track, $isUserData, %triplet, $doDefaultLang, $index, $ee); my $topLevel = not $$et{InQuickTime}; $$et{InQuickTime} = 1; + $$et{HandlerType} = $$et{HandlerDesc} = '' unless defined $$et{HandlerType}; + if ($et->Options('ExtractEmbedded')) { + $ee = 1; + require 'Image/ExifTool/QuickTimeStream.pl'; + } unless (defined $$et{KeyCount}) { $$et{KeyCount} = 0; # initialize ItemList key directory count $doDefaultLang = 1; # flag to generate default language tags @@ -6805,6 +6830,12 @@ sub ProcessMOV($$;$) } } my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag); + # set flag to store additional information for ExtractEmbedded option + my $eeTag; + if ($ee and $eeBox{$$et{HandlerType}} and $eeBox{$$et{HandlerType}}{$tag}) { + $tagInfo or $tagInfo = $$tagTablePtr{$tag}; # extract even if Unknown + $tagInfo and $eeTag = 1; + } # allow numerical tag ID's unless ($tagInfo) { my $id = $$et{KeyCount} . '.' . unpack('N', $tag); @@ -6844,7 +6875,7 @@ sub ProcessMOV($$;$) my $ignore; if ($size > 0x2000000) { # start to get worried above 32 MB $ignore = 1; - if ($tagInfo and not $$tagInfo{Unknown}) { + if ($tagInfo and not $$tagInfo{Unknown} and not $eeTag) { my $t = $tag; $t =~ s/([\x00-\x1f\x7f-\xff])/'x'.unpack('H*',$1)/eg; if ($size > 0x8000000) { @@ -6895,6 +6926,9 @@ ItemID: foreach $id (keys %$items) { $et->Warn("Truncated '$tag' data (missing $missing bytes)"); last; } + # extract metadata from stream if ExtractEmbedded option is enabled + ParseTag($et, $tag, \$val, $$et{HandlerType}, $$et{HandlerDesc}) if $eeTag; + # use value to get tag info if necessary $tagInfo or $tagInfo = $et->GetTagInfo($tagTablePtr, $tag, \$val); my $hasData = ($$dirInfo{HasData} and $val =~ /\0...data\0/s); @@ -6989,6 +7023,14 @@ ItemID: foreach $id (keys %$items) { $et->ProcessDirectory(\%dirInfo, $subTable, $proc); } } + # reset HandlerType when exiting MediaInfo box + if ($tag eq 'minf') { + $$et{HandlerType} = $$et{HanderDesc} = ''; + # make sure we don't extract embedded with sizes from one + # MediaInfo box and offsets from another + delete $$et{eeStart}; + delete $$et{eeSize}; + } $$et{SET_GROUP1} = $oldGroup1; SetByteOrder('MM'); # handle metadata now if iwe just processed the 'meta' box @@ -7188,7 +7230,10 @@ QTLang: foreach $tag (@{$$et{QTLang}}) { } delete $$et{QTLang}; } - HandleItemInfo($et, $raf) if $topLevel; + if ($topLevel) { + HandleItemInfo($et, $raf); + ExtractEmbedded($et, $raf) if $ee; + } return 1; } diff --git a/lib/Image/ExifTool/QuickTimeStream.pl b/lib/Image/ExifTool/QuickTimeStream.pl new file mode 100644 index 00000000..210adb77 --- /dev/null +++ b/lib/Image/ExifTool/QuickTimeStream.pl @@ -0,0 +1,473 @@ +#------------------------------------------------------------------------------ +# File: QuickTimeStream.pl +# +# Description: Extract embedded information from QuickTime video data +# +# Revisions: 2018-01-03 - P. Harvey Created +# +# References: 1) https://github.com/stilldavid/gopro-utils +# 2) http://sergei.nz/files/nvtk_mp42gpx.py +#------------------------------------------------------------------------------ +package Image::ExifTool::QuickTime; + +use strict; + +%Image::ExifTool::QuickTime::Stream = ( + GROUPS => { 2 => 'Video' }, + NOTES => q{ + Tags extracted from QuickTime movie data when the ExtractEmbedded option is + used. + }, + GPSLatitude => { + Groups => { 2 => 'Location' }, + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")', + }, + GPSLongitude => { + Groups => { 2 => 'Location' }, + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")', + }, + GPSAltitude => { + Groups => { 2 => 'Location' }, + PrintConv => '"$val m"', + }, + GPSAltitudeRef => { + Groups => { 2 => 'Location' }, + PrintConv => { + 0 => 'Above Sea Level', + 1 => 'Below Sea Level', + }, + }, + GPSSpeed => { + Groups => { 2 => 'Location' }, + }, + GPSSpeedRef => { + Groups => { 2 => 'Location' }, + PrintConv => { + K => 'km/h', + M => 'mph', + N => 'knots', + }, + }, + GPSTrack => { + Groups => { 2 => 'Location' }, + }, + GPSTrackRef => { + Groups => { 2 => 'Location' }, + PrintConv => { + M => 'Magnetic North', + T => 'True North', + }, + }, + GPSDateTime => { Groups => { 2 => 'Time' } }, + TimeCode => { Groups => { 2 => 'Time' } }, + Accelerometer => { + Notes => 'right/up/backward acceleration in units of g', + Groups => { 2 => 'Location' }, + }, + Text => { + Notes => 'text captions extracted from some videos when -ee is used', + Binary => 1, + }, +); + +# GoPro META tags (ref 1) +%Image::ExifTool::QuickTime::GoPro = ( + GROUPS => { 1 => 'GoPro', 2 => 'Video' }, + NOTES => q{ + Tags extracted from compatible GoPro MP4 videos when the ExtractEmbedded + option is used. + }, + ACCL => { # accelerometer reading x/y/z + Name => 'Accelerometer', + ValueConv => q{ + my @a = split ' ', $val; + my $scl = $$self{ScaleFactor} ? $$self{ScaleFactor}[0] : 1; + $_ /= $scl foreach @a; + return \ join ' ', @a; + }, + }, + DEVC => 'Device', + DVID => { Name => 'DeviceID', Unknown => 1 }, # possibly hard-coded to 0x1 + DVNM => { # device name, string "Camera" + Name => 'Model', + Description => 'Camera Model Name', + }, + EMPT => { Name => 'Empty', Unknown => 1 }, # empty packet + GPS5 => { # GPS data (lat, lon, alt, speed, 3d speed) + Name => 'GPSInfo', + SubDirectory => { TagTable => 'Image::ExifTool::QuickTime::GoProGPS' }, + }, + GPSF => { # GPS fix (none, 2d, 3d) + Name => 'GPSMeasureMode', + PrintConv => { + 2 => '2-Dimensional Measurement', + 3 => '3-Dimensional Measurement', + }, + }, + GPSP => { # GPS positional accuracy in cm + Name => 'GPSHPositioningError', + Description => 'GPS Horizontal Positioning Error', + ValueConv => '$val / 100', # convert to m + }, + GPSU => { # GPS acquired timestamp; potentially different than "camera time" + Name => 'GPSDateTime', + Groups => { 2 => 'Time' }, + }, + GYRO => { # gryroscope reading x/y/z + Name => 'Gyroscope', + ValueConv => q{ + my @a = split ' ', $val; + my $scl = $$self{ScaleFactor} ? $$self{ScaleFactor}[0] : 1; + $_ /= $scl foreach @a; + return \ join ' ', @a; + }, + }, + SCAL => { # scale factor, a multiplier for subsequent data + Name => 'ScaleFactor', + Unknown => 1, # (not very useful to user) + }, + SIUN => { # SI units; strings (m/s2, rad/s) + Name => 'SIUnits', + }, + STRM => 'NestedSignalStream', + STNM => { Name => 'StreamName', Unknown => 1 }, + TMPC => { # temperature + Name => 'CameraTemperature', + PrintConv => '"$val C"', + }, + TSMP => { Name => 'TotalSamples', Unknown => 1 }, + UNIT => { # alternative units; strings (deg, m, m/s)); + Name => 'Units', + }, + SHUT => { + Name => 'ExposureTimes', + PrintConv => q{ + my @a = split ' ', $val; + $_ = Image::ExifTool::Exif::PrintExposureTime($_) foreach @a; + return join ' ', @a; + }, + }, + ISOG => 'ImageSensorGain', + TYPE => { Name => 'StructureType', Unknown => 1 }, + RMRK => 'Comments', + WRGB => { #PH + Name => 'WhiteBalanceRGB', + Binary => 1, + }, + WBAL => 'ColorTemperatures', #PH + FCNM => 'FaceNumbers', #PH + ISOE => 'ISOSpeeds', #PH + # ALLD => 'AutoLowLightDuration', #PH + # TICK => ? + # FACE => 'FaceDetected', #PH (need sample for testing) +); + +# GoPro GPS tags (ref 1) +%Image::ExifTool::QuickTime::GoProGPS = ( + GROUPS => { 1 => 'GoPro', 2 => 'Location' }, + PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, + FORMAT => 'int32s', + 0 => { + Name => 'GPSLatitude', + ValueConv => '$val / ($$self{ScaleFactor} ? $$self{ScaleFactor}[0] : 1)', + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")', + }, + 1 => { + Name => 'GPSLongitude', + ValueConv => '$val / ($$self{ScaleFactor} ? $$self{ScaleFactor}[1] : 1)', + PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")', + }, + 2 => { + Name => 'GPSAltitude', + ValueConv => '$val / ($$self{ScaleFactor} ? $$self{ScaleFactor}[2] : 1)', + PrintConv => '"$val m"', + }, + 3 => { + Name => 'GPSSpeed', + ValueConv => '$val / ($$self{ScaleFactor} ? $$self{ScaleFactor}[3] : 1)', + }, + 4 => { + Name => 'GPSSpeed3D', + ValueConv => '$val / ($$self{ScaleFactor} ? $$self{ScaleFactor}[4] : 1)', + }, +); + +#------------------------------------------------------------------------------ +# Process GoPro metadata +# Inputs: 0) ExifTool ref, 1) data ref +my %goProFmt = ( # format codes + 0x62 => 'int8s', # 'b' + 0x42 => 'int8u', # 'B' + 0x63 => 'undef', # 'c' (character) + 0x73 => 'int16s', # 's' + 0x53 => 'int16u', # 'S' + 0x6c => 'int32s', # 'l' + 0x4c => 'int32u', # 'L' + 0x66 => 'float', # 'f' + 0x64 => 'double', # 'd' + # 0x46 => 'undef[4]', # 'F' (4-char ID) + # 0x47 => 'undef[16]',# 'G' (uuid) + 0x6a => 'int64s', # 'j' + 0x4a => 'int64u', # 'J' + 0x71 => 'fixed32s', # 'q' + # 0x51 => 'fixed64s', # 'Q' + # 0x55 => 'date', # 'U' 16-byte + 0x3f => 'undef', # '?' (complex structure) +); +sub ProcessGoProMET($$) +{ + my ($et, $dataPt) = @_; + my $dataLen = length $$dataPt; + my $unk = ($et->Options('Unknown') || $et->Options('Verbose')); + my $pos = 0; + + my $tagTablePtr = GetTagTable('Image::ExifTool::QuickTime::GoPro'); + + while ($pos + 8 <= $dataLen) { + my $tag = substr($$dataPt, $pos, 4); + my ($fmt,$size,$count) = unpack("x${pos}x4CCn", $$dataPt); + $pos += 8; + next if $fmt == 0; + my $len = $size * $count; + last if $pos + $len > $dataLen; + next if $len == 0; # skip empty tags (for now) + my $format = $goProFmt{$fmt} || 'undef'; + $format = 'undef' if $tag eq 'GPS5'; # don't reformat GPSInfo + my $val = ReadValue($dataPt, $pos, $format, undef, $len); + # save scaling factor + if ($tag eq 'SCAL') { + $$et{ScaleFactor} = [ split ' ', $val ] if $tag eq 'SCAL'; + } elsif ($fmt == 0x55) { # date + $val =~ s/^(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/20$1:$2:$3 $4:$5:/; + } + $pos += (($len + 3) & 0xfffffffc); # round up to even 4-byte boundary + if (not $$tagTablePtr{$tag} and $unk) { + AddTagToTable($tagTablePtr, $tag, { Name => Image::ExifTool::MakeTagName("Unknown_$tag") }); + } + $et->HandleTag($tagTablePtr, $tag, $val); + } +} + +#------------------------------------------------------------------------------ +# Exract embedded metadata from media samples +# Inputs: 0) ExifTool ref, 1) tag table ptr, 2) RAF ref, 3) embedded info +sub ProcessSamples($$$$) +{ + my ($et, $tagTablePtr, $raf, $eeInfo) = @_; + my ($start, $size, $type, $desc) = @$eeInfo{qw(start size type desc)}; + my ($i, $pos, $buff, %parms, $hdrLen, $hdrFmt); + my $verbose = $et->Options('Verbose'); + + $parms{MaxLen} = $verbose == 3 ? 96 : 2048 if $verbose < 5; + + # get required information from avcC box if parsing video data + if ($type eq 'vide' and $$eeInfo{avcC}) { + $hdrLen = (Get8u(\$$eeInfo{avcC}, 4) & 0x03) + 1; + $hdrFmt = ($hdrLen == 4 ? 'N' : $hdrLen == 2 ? 'n' : 'C'); + require Image::ExifTool::H264; + } + # loop through all samples + for ($i=0; $i<@$start and $i<@$size; ++$i) { + next unless $raf->Seek($$start[$i], 0) and $raf->Read($buff, $$size[$i]) == $$size[$i]; + if ($type eq 'vide' and defined $hdrLen) { + next if length($buff) <= $hdrLen; + # scan through all NAL units and send them to ParseH264Video() + for ($pos=0; ; ) { + my $len = unpack("x$pos$hdrFmt", $buff); + last if $pos + $hdrLen + $len > length($buff); + my $tmp = "\0\0\0\x01" . substr($buff, $pos+$hdrLen, $len); + Image::ExifTool::H264::ParseH264Video($et, \$tmp); + $pos += $hdrLen + $len; + last if $pos + $hdrLen >= length($buff); + } + next; + } + if ($verbose > 2) { + $et->VPrint(2, "Type='$type' Desc='$desc', Sample ".($i+1).' of '.scalar(@$start)."\n"); + $parms{Addr} = $$start[$i]; + HexDump(\$buff, undef, %parms); + } + if ($type eq 'text') { + $$et{DOC_NUM} = ++$$et{DOC_COUNT}; + unless ($buff =~ /^\$BEGIN/) { + $et->HandleTag($tagTablePtr, Text => $buff); + next; + } + while ($buff =~ /\$(\w+)([^\$]*)/g) { + my ($tag, $dat) = ($1, $2); + if ($tag eq 'GPRMC' and $dat =~ /^,(\d{2})(\d{2})(\d+(\.\d*)?),A?,(\d*?)(\d{1,2}\.\d+),([NS]),(\d*?)(\d{1,2}\.\d+),([EW]),(\d*\.?\d*),(\d*\.?\d*),(\d{2})(\d{2})(\d+)/) { + $et->HandleTag($tagTablePtr, GPSLatitude => (($5 || 0) + $6/60) * ($7 eq 'N' ? 1 : -1)); + $et->HandleTag($tagTablePtr, GPSLongitude => (($8 || 0) + $9/60) * ($10 eq 'E' ? 1 : -1)); + if (length $11) { + $et->HandleTag($tagTablePtr, GPSSpeed => $11); + $et->HandleTag($tagTablePtr, GPSSpeedRef => 'N'); + } + if (length $12) { + $et->HandleTag($tagTablePtr, GPSTrack => $11); + $et->HandleTag($tagTablePtr, GPSTrackRef => 'T'); + } + my $year = $15 + ($15 >= 70 ? 1900 : 2000); + my $str = sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%.2d', $year, $14, $13, $1, $2, $3); + $et->HandleTag($tagTablePtr, GPSDateTime => $str); + } elsif ($tag eq 'BEGINGSENSOR' and $dat =~ /^:([-+]\d+\.\d+):([-+]\d+\.\d+):([-+]\d+\.\d+)/) { + $et->HandleTag($tagTablePtr, Accelerometer => "$1 $2 $3"); + } elsif ($tag eq 'TIME' and $dat =~ /^:(\d+)/) { + $et->HandleTag($tagTablePtr, TimeCode => $1 / 100000); + } elsif ($tag ne 'BEGIN' and $tag ne 'END') { + $et->HandleTag($tagTablePtr, Text => "\$$tag$dat"); + } + } + } elsif ($type eq 'meta' and $desc eq 'GoPro MET') { + $$et{DOC_NUM} = ++$$et{DOC_COUNT}; + ProcessGoProMET($et, \$buff); + } elsif ($type eq 'gps ') { + # decode Novatek GPS data (ref 2) + next unless $buff =~ /^....freeGPS /s and length $buff >= 92; + my ($hr,$min,$sec,$yr,$mon,$day,$active,$latRef,$lonRef) = unpack('x48V6a1a1a1', $buff); + my ($lat,$lon,$spd) = unpack('f3', pack('L3', unpack('x76V3', $buff))); + next unless $active eq 'A'; # ignore bad GPS fixes + $$et{DOC_NUM} = ++$$et{DOC_COUNT}; + # lat/long are in DDDmm.mmmm format + my $deg = int($lat / 100); + $lat = $deg + (($lat - $deg * 100) / 60) * ($latRef eq 'S' ? -1 : 1); + $deg = int($lon / 100); + $lon = $deg + (($lon - $deg * 100) / 60) * ($lonRef eq 'W' ? -1 : 1); + $et->HandleTag($tagTablePtr, GPSLatitude => $lat); + $et->HandleTag($tagTablePtr, GPSLongitude => $lon); + $et->HandleTag($tagTablePtr, GPSSpeed => $spd); + $et->HandleTag($tagTablePtr, GPSSpeedRef => 'N'); + $yr += $yr >= 70 ? 1900 : 2000; + $et->HandleTag($tagTablePtr, GPSDateTime => + sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%.2d',$yr,$mon,$day,$hr,$min,$sec)); + } + } + $$et{DOC_NUM} = 0; +} + +#------------------------------------------------------------------------------ +# Extract embedded information from movie data +# Inputs: 0) ExifTool ref, 1) RAF ref +sub ExtractEmbedded($$) +{ + local $_; + my ($et, $raf) = @_; + if ($$et{eeInfo}) { + $et->VPrint(0,"Extract Embedded:\n"); + my $tagTablePtr = GetTagTable('Image::ExifTool::QuickTime::Stream'); + ProcessSamples($et, $tagTablePtr, $raf, $_) foreach @{$$et{eeInfo}}; + delete $$et{eeInfo}; + } +} + +#------------------------------------------------------------------------------ +# Save details about embedded information +# Inputs: 0) ExifTool ref, 1) tag name, 2) data ref, 3) handler type, +# 4) handler description +sub ParseTag($$$$$) +{ + local $_; + my ($et, $tag, $dataPt, $type, $desc) = @_; + my ($i, $start, $size); + my $dataLen = length $$dataPt; + + if ($tag eq 'stco' or $tag eq 'co64' and $dataLen > 8) { + my $num = unpack('x4N', $$dataPt); + $start = $$et{eeStart} = [ ]; + $size = $$et{eeSize}; + @$start = ReadValue($dataPt, 8, $tag eq 'stco' ? 'int32u' : 'int64u', $num, $dataLen-8); + } elsif ($tag eq 'stsz' or $tag eq 'sts2' and $dataLen > 12) { + my ($sz, $num) = unpack('x4N2', $$dataPt); + $start = $$et{eeStart}; + $size = $$et{eeSize} = [ ]; + if ($tag eq 'stsz') { + if ($sz == 0) { + @$size = ReadValue($dataPt, 12, 'int32u', $num, $dataLen-12); + } else { + @$size = ($sz) x $num; + } + } else { + $sz &= 0xff; + if ($sz == 4) { + my @tmp = ReadValue($dataPt, 12, "int8u", int(($num+1)/2), $dataLen-12); + foreach (@tmp) { + push @$size, $_ >> 4; + push @$size, $_ & 0xff; + } + } elsif ($sz == 8 || $sz == 16) { + @$size = ReadValue($dataPt, 12, "int${sz}u", $num, $dataLen-12); + } + } + } elsif ($tag eq 'avcC') { + $$et{avcC} = $$dataPt if $dataLen >= 7; # (minimum length is 7) + } elsif ($tag eq 'gps ' and $dataLen > 8) { + # decode Novatek 'gps ' box (ref 2) + my $num = Get32u($dataPt, 4); + $num = int(($dataLen - 8) / 8) if $num * 8 + 8 > $dataLen; + $start = $$et{eeStart} = [ ]; + $size = $$et{eeSize} = [ ]; + for ($i=0; $i<$num; $i+=2) { + push @$start, Get32u($dataPt, 8 + $i * 8); + push @$size, Get32u($dataPt, 12 + $i * 8); + } + $type = $tag; # fake type + } + if ($start and $size) { + # save details now that we have both sample sizes and offsets + my $eeInfo = $$et{eeInfo}; + $eeInfo or $eeInfo = $$et{eeInfo} = [ ]; + push @$eeInfo, { + type => $type, + desc => $desc, + start => $start, + size => $size, + avcC => $$et{avcC}, + }; + delete $$et{eeStart}; + delete $$et{eeSize}; + delete $$et{avcC}; + } +} + +1; # end + +__END__ + +=head1 NAME + +Image::ExifTool::QuickTime - Extract embedded information from video data + +=head1 SYNOPSIS + +These routines are autoloaded by Image::ExifTool::QuickTime. + +=head1 DESCRIPTION + +This file contains routines used by Image::ExifTool to extract embedded +information like GPS tracks from QuickTime and MP4 videos. + +=head1 AUTHOR + +Copyright 2003-2018, Phil Harvey (phil at owl.phy.queensu.ca) + +This library is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=head1 REFERENCES + +=over 4 + +=item L<https://github.com/stilldavid/gopro-utils> + +=item L<http://sergei.nz/files/nvtk_mp42gpx.py> + +=back + +=head1 SEE ALSO + +L<Image::ExifTool::QuickTime(3pm)|Image::ExifTool::QuickTime>, +L<Image::ExifTool::TagNames/QuickTime Stream Tags>, +L<Image::ExifTool::TagNames/QuickTime GoPro Tags>, +L<Image::ExifTool(3pm)|Image::ExifTool> + +=cut + diff --git a/lib/Image/ExifTool/TagLookup.pm b/lib/Image/ExifTool/TagLookup.pm index 72139160..085b51ad 100644 --- a/lib/Image/ExifTool/TagLookup.pm +++ b/lib/Image/ExifTool/TagLookup.pm @@ -5688,6 +5688,7 @@ my %tagExists = ( 'autosummary' => 1, 'aux' => 1, 'auxiliaryimagetype' => 1, + 'avcconfiguration' => 1, 'averagebitrate' => 1, 'averagelevel' => 1, 'avgbitrate' => 1, @@ -6136,6 +6137,7 @@ my %tagExists = ( 'colorspecmethod' => 1, 'colorspecprecedence' => 1, 'colortable' => 1, + 'colortemperatures' => 1, 'colortransferfuncs' => 1, 'colortransform' => 1, 'colortwistmatrix' => 1, @@ -6410,6 +6412,7 @@ my %tagExists = ( 'detector' => 1, 'detectorboard' => 1, 'deviceattributes' => 1, + 'deviceid' => 1, 'devicemanufacturer' => 1, 'devicemfgdesc' => 1, 'devicemodel' => 1, @@ -6556,6 +6559,7 @@ my %tagExists = ( 'embeddedvideofile' => 1, 'embeddedvideotype' => 1, 'emphasis' => 1, + 'empty' => 1, 'encodedby' => 1, 'encodedpixelsdimensions' => 1, 'encodedusing' => 1, @@ -6614,6 +6618,7 @@ my %tagExists = ( 'exportimage' => 1, 'exposurebias' => 1, 'exposureinfo' => 1, + 'exposuretimes' => 1, 'exposureunknown' => 1, 'expressionmedia' => 1, 'exrversion' => 1, @@ -6674,6 +6679,7 @@ my %tagExists = ( 'faceinfoa' => 1, 'faceinfolength' => 1, 'faceinfooffset' => 1, + 'facenumbers' => 1, 'facepos' => 1, 'facerecinfo' => 1, 'facesize' => 1, @@ -6924,8 +6930,10 @@ my %tagExists = ( 'gpano' => 1, 'gps' => 1, 'gpscoordinates' => 1, + 'gpsdatalist' => 1, 'gpsinfo' => 1, 'gpsposition' => 1, + 'gpsspeed3d' => 1, 'grainybwfilter' => 1, 'graphicconverter' => 1, 'graphicsmode' => 1, @@ -6952,6 +6960,7 @@ my %tagExists = ( 'gtmodeltype' => 1, 'gtrastertype' => 1, 'guid' => 1, + 'gyroscope' => 1, 'handler' => 1, 'handlerclass' => 1, 'handlerdescription' => 1, @@ -7099,6 +7108,7 @@ my %tagExists = ( 'imagerotated' => 1, 'imagerotation' => 1, 'imagerotationstatus' => 1, + 'imagesensorgain' => 1, 'imagesourceek' => 1, 'imagespatialextent' => 1, 'imagestatus' => 1, @@ -7203,6 +7213,7 @@ my %tagExists = ( 'isnetworkfeed' => 1, 'isoinfo' => 1, 'isonline' => 1, + 'isospeeds' => 1, 'isotherm1color' => 1, 'isotherm2color' => 1, 'isprotected' => 1, @@ -7741,6 +7752,7 @@ my %tagExists = ( 'nativexresolution' => 1, 'nativeyresolution' => 1, 'ncc' => 1, + 'nestedsignalstream' => 1, 'nestlevel' => 1, 'netexposurecompensation' => 1, 'netname' => 1, @@ -8505,6 +8517,7 @@ my %tagExists = ( 'scalado' => 1, 'scale' => 1, 'scalecrop' => 1, + 'scalefactor' => 1, 'scalefactor35efl' => 1, 'scalingfactor' => 1, 'scandate' => 1, @@ -8655,6 +8668,7 @@ my %tagExists = ( 'site' => 1, 'siteenter' => 1, 'siteexit' => 1, + 'siunits' => 1, 'sixthlanguage' => 1, 'size' => 1, 'sizemode' => 1, @@ -8792,6 +8806,7 @@ my %tagExists = ( 'striprowcounts' => 1, 'strobe' => 1, 'strobetime' => 1, + 'structuretype' => 1, 'studio' => 1, 'sub-sampleinformation' => 1, 'subdir3000' => 1, @@ -9307,6 +9322,7 @@ my %tagExists = ( 'webpage' => 1, 'weight' => 1, 'whitebalancematching' => 1, + 'whitebalancergb' => 1, 'whitebalancetable' => 1, 'whiteluminance' => 1, 'whitepointx' => 1, diff --git a/lib/Image/ExifTool/TagNames.pod b/lib/Image/ExifTool/TagNames.pod index b5f24111..6d874673 100644 --- a/lib/Image/ExifTool/TagNames.pod +++ b/lib/Image/ExifTool/TagNames.pod @@ -22450,6 +22450,7 @@ ItemList tags written by the "mdta" handler. The prefix of Tag ID Tag Name Writable ------ -------- -------- 'cmov' CompressedMovie QuickTime CMovie + 'gps ' GPSDataList? no 'htka' HTCTrack QuickTime Track 'iods' InitialObjectDescriptor? no 'meta' Meta QuickTime Meta @@ -22700,6 +22701,7 @@ Child atoms found in "sinf" and/or "pinf" atoms. 20 YResolution no 25 CompressorName no 41 BitDepth no + 'avcC' AVCConfiguration? no 'btrt' BitrateInfo QuickTime Bitrate 'clap' CleanAperture QuickTime CleanAperture 'colr' ColorRepresentation no @@ -23155,6 +23157,69 @@ Tags used in QTIF QuickTime Image Files. 'idsc' ImageDescription QuickTime ImageDesc 'iicc' ICC_Profile ICC_Profile +=head3 QuickTime Stream Tags + +Tags extracted from QuickTime movie data when the ExtractEmbedded option is +used. + + Tag ID Tag Name Writable + ------ -------- -------- + 'Accelerometer' Accelerometer no + 'GPSAltitude' GPSAltitude no + 'GPSAltitudeRef' GPSAltitudeRef no + 'GPSDateTime' GPSDateTime no + 'GPSLatitude' GPSLatitude no + 'GPSLongitude' GPSLongitude no + 'GPSSpeed' GPSSpeed no + 'GPSSpeedRef' GPSSpeedRef no + 'GPSTrack' GPSTrack no + 'GPSTrackRef' GPSTrackRef no + 'Text' Text no + 'TimeCode' TimeCode no + +=head3 QuickTime GoPro Tags + +Tags extracted from compatible GoPro MP4 videos when the ExtractEmbedded +option is used. + + Tag ID Tag Name Writable + ------ -------- -------- + 'ACCL' Accelerometer no + 'DEVC' Device no + 'DVID' DeviceID? no + 'DVNM' Model no + 'EMPT' Empty? no + 'FCNM' FaceNumbers no + 'GPS5' GPSInfo QuickTime GoProGPS + 'GPSF' GPSMeasureMode no + 'GPSP' GPSHPositioningError no + 'GPSU' GPSDateTime no + 'GYRO' Gyroscope no + 'ISOE' ISOSpeeds no + 'ISOG' ImageSensorGain no + 'RMRK' Comments no + 'SCAL' ScaleFactor? no + 'SHUT' ExposureTimes no + 'SIUN' SIUnits no + 'STNM' StreamName? no + 'STRM' NestedSignalStream no + 'TMPC' CameraTemperature no + 'TSMP' TotalSamples? no + 'TYPE' StructureType? no + 'UNIT' Units no + 'WBAL' ColorTemperatures no + 'WRGB' WhiteBalanceRGB no + +=head3 QuickTime GoProGPS Tags + + Index4 Tag Name Writable + ------ -------- -------- + 0 GPSLatitude no + 1 GPSLongitude no + 2 GPSAltitude no + 3 GPSSpeed no + 4 GPSSpeed3D no + =head2 PLIST Tags Apple Property List tags. ExifTool reads both XML and binary-format PLIST diff --git a/lib/Image/ExifTool/Writer.pl b/lib/Image/ExifTool/Writer.pl index 4f121716..1bce4ed3 100644 --- a/lib/Image/ExifTool/Writer.pl +++ b/lib/Image/ExifTool/Writer.pl @@ -2792,6 +2792,7 @@ Conv: for (;;) { # 'Error' - issue minor error on missing tag (and return undef) # 'Warn' - issue minor warning on missing tag (and return undef) # Hash ref - hash for return of tag/value pairs +# 4) document group name if extracting from a specific document # Returns: string with embedded tag values (or '$info{TAGNAME}' entries with Hash ref option) # Notes: # - tag names are not case sensitive and may end with '#' for ValueConv value @@ -2800,10 +2801,10 @@ Conv: for (;;) { # - advanced feature allows Perl expressions inside braces (eg. '${model;tr/ //d}') # - an error/warning in an advanced expression ("${TAG;EXPR}") generates an error # if option set to 'Error', or a warning otherwise -sub InsertTagValues($$$;$) +sub InsertTagValues($$$;$$) { local $_; - my ($self, $foundTags, $line, $opt) = @_; + my ($self, $foundTags, $line, $opt, $docGrp) = @_; my $rtnStr = ''; while ($line =~ s/(.*?)\$(\{\s*)?([-\w]*\w|\$|\/)//s) { my ($pre, $bra, $var) = ($1, $2, $3); @@ -2823,7 +2824,7 @@ sub InsertTagValues($$$;$) } # allow trailing '#' to indicate ValueConv value $type = 'ValueConv' if $line =~ s/^#//; - # (undocumented feature to allow '@' to evaluate values separately, but only in braces) + # (undocumented feature to allow '@' to evaluate list values separately, but only in braces) if ($bra and $line =~ s/^\@(#)?//) { $asList = 1; $type = 'ValueConv' if $1; @@ -2844,6 +2845,8 @@ sub InsertTagValues($$$;$) # use default Windows filename filter if expression is empty $expr = 'tr(/\\\\?*:|"<>\\0)()d' unless length $expr; } + # add document number to tag if specified + $var = $docGrp . ':' . $var if $docGrp and $var !~ /^(main|doc\d+):/i; push @tags, $var; ExpandShortcuts(\@tags); @tags or $rtnStr .= $pre, next; diff --git a/lib/Image/ExifTool/XMP.pm b/lib/Image/ExifTool/XMP.pm index b59352c4..4f1bea39 100644 --- a/lib/Image/ExifTool/XMP.pm +++ b/lib/Image/ExifTool/XMP.pm @@ -48,7 +48,7 @@ use Image::ExifTool::Exif; use Image::ExifTool::GPS; require Exporter; -$VERSION = '3.07'; +$VERSION = '3.08'; @ISA = qw(Exporter); @EXPORT_OK = qw(EscapeXML UnescapeXML); @@ -3138,6 +3138,7 @@ NoLoop: # Returns: Number of contained XMP elements sub ParseXMPElement($$$;$$$$) { + local $_; my ($et, $tagTablePtr, $dataPt, $start, $end, $propListPt, $blankInfo) = @_; my ($count, $nItems) = (0, 0); my $isWriting = $$et{XMP_CAPTURE}; |