summaryrefslogtreecommitdiff
path: root/lib/Image/ExifTool
diff options
context:
space:
mode:
authorexiftool <phil@work3.phy.queensu.ca>2018-01-04 15:15:23 -0500
committerexiftool <phil@work3.phy.queensu.ca>2018-01-04 15:15:23 -0500
commita79f1c3fb465e3cdc205ba2e2798df739ccb7673 (patch)
tree7aa7e6d721699cd1fe0be735b4521129c8c53cf7 /lib/Image/ExifTool
parentdf996adf2033d036c0e683a66e284583c0737f05 (diff)
Update to 10.72
Diffstat (limited to 'lib/Image/ExifTool')
-rw-r--r--lib/Image/ExifTool/Geotag.pm21
-rw-r--r--lib/Image/ExifTool/H264.pm30
-rwxr-xr-x[-rw-r--r--]lib/Image/ExifTool/Lang/de.pm0
-rw-r--r--lib/Image/ExifTool/QuickTime.pm55
-rw-r--r--lib/Image/ExifTool/QuickTimeStream.pl473
-rw-r--r--lib/Image/ExifTool/TagLookup.pm16
-rw-r--r--lib/Image/ExifTool/TagNames.pod65
-rw-r--r--lib/Image/ExifTool/Writer.pl9
-rw-r--r--lib/Image/ExifTool/XMP.pm3
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};