summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorexiftool <exiftool@users.sourceforge.net>2018-10-09 12:24:48 -0400
committerexiftool <exiftool@users.sourceforge.net>2018-10-09 12:24:48 -0400
commit4d55678e0776c97648f15ca3635cd73ab2eb1924 (patch)
tree9c1edae34b34945898720eab78a181b75bf60142 /lib
parent5297201750bfe371010ee437f1d31aeec4ab49ab (diff)
Update to 11.13
Diffstat (limited to 'lib')
-rw-r--r--lib/File/RandomAccess.pm54
-rw-r--r--lib/File/RandomAccess.pod19
-rw-r--r--lib/Image/ExifTool.pm4
-rw-r--r--lib/Image/ExifTool.pod32
-rw-r--r--lib/Image/ExifTool/Canon.pm3
-rw-r--r--lib/Image/ExifTool/Exif.pm4
-rw-r--r--lib/Image/ExifTool/Nikon.pm37
-rw-r--r--lib/Image/ExifTool/PNG.pm54
-rw-r--r--lib/Image/ExifTool/QuickTime.pm4
-rw-r--r--lib/Image/ExifTool/QuickTimeStream.pl272
-rw-r--r--lib/Image/ExifTool/RIFF.pm3
-rw-r--r--lib/Image/ExifTool/TagNames.pod4
12 files changed, 340 insertions, 150 deletions
diff --git a/lib/File/RandomAccess.pm b/lib/File/RandomAccess.pm
index 42ea3b89..c689f56b 100644
--- a/lib/File/RandomAccess.pm
+++ b/lib/File/RandomAccess.pm
@@ -16,6 +16,7 @@
# 11/26/2008 - P. Harvey Fixed bug in ReadLine when reading from a
# scalar with a multi-character newline
# 01/24/2009 - PH Protect against reading too much at once
+# 10/04/2018 - PH Added NoBuffer option
#
# Notes: Calls the normal file i/o routines unless SeekTest() fails, in
# which case the file is buffered in memory to allow random access.
@@ -36,13 +37,14 @@ require 5.002;
require Exporter;
use vars qw($VERSION @ISA @EXPORT_OK);
-$VERSION = '1.10';
+$VERSION = '1.11';
@ISA = qw(Exporter);
sub Read($$$);
# constants
my $CHUNK_SIZE = 8192; # size of chunks to read from file (must be power of 2)
+my $SKIP_SIZE = 65536; # size to skip when fast-forwarding over sequential data
my $SLURP_CHUNKS = 16; # read this many chunks at a time when slurping
#------------------------------------------------------------------------------
@@ -60,6 +62,7 @@ sub new($$;$)
# string i/o
$self = {
BUFF_PT => $filePt,
+ BASE => 0,
POS => 0,
LEN => length($$filePt),
TESTED => -1,
@@ -71,8 +74,9 @@ sub new($$;$)
$self = {
FILE_PT => $filePt, # file pointer
BUFF_PT => \$buff, # reference to file data
- POS => 0, # current position in file
- LEN => 0, # data length
+ BASE => 0, # location of start of buffer in file
+ POS => 0, # current position in buffer
+ LEN => 0, # length of data in buffer
TESTED => 0, # 0=untested, 1=passed, -1=failed (requires buffering)
};
bless $self, $class;
@@ -118,7 +122,7 @@ sub Tell($)
my $self = shift;
my $rtnVal;
if ($self->{TESTED} < 0) {
- $rtnVal = $self->{POS};
+ $rtnVal = $self->{POS} + $self->{BASE};
} else {
$rtnVal = tell($self->{FILE_PT});
}
@@ -141,9 +145,11 @@ sub Seek($$;$)
if ($self->{TESTED} < 0) {
my $newPos;
if ($whence == 0) {
- $newPos = $num; # from start of file
+ $newPos = $num - $self->{BASE}; # from start of file
} elsif ($whence == 1) {
$newPos = $num + $self->{POS}; # relative to current position
+ } elsif ($self->{NoBuffer} and $self->{FILE_PT}) {
+ $newPos = -1; # (can't seek relative to end if no buffering)
} else {
$self->Slurp(); # read whole file into buffer
$newPos = $num + $self->{LEN}; # relative to end of file
@@ -192,6 +198,8 @@ sub Read($$$)
}
# read through our buffer if necessary
if ($self->{TESTED} < 0) {
+ # purge old data before reading in NoBuffer mode
+ $self->Purge() or return 0 if $self->{NoBuffer};
my $buff;
my $newPos = $self->{POS} + $len;
# number of bytes to read from file
@@ -244,6 +252,7 @@ sub ReadLine($$)
if ($self->{TESTED} < 0) {
my ($num, $buff);
+ $self->Purge() or return 0 if $self->{NoBuffer};
my $pos = $self->{POS};
if ($fp) {
# make sure we have some data after the current position
@@ -311,9 +320,39 @@ sub Slurp($)
}
}
+#------------------------------------------------------------------------------
+# Purge internal buffer [internal use only]
+# Inputs: 0) reference to RandomAccess object
+# Returns: 1 on success, or 0 if current buffer position is negative
+# Notes: This is called only in NoBuffer mode
+sub Purge($)
+{
+ my $self = shift;
+ return 1 unless $self->{FILE_PT};
+ return 0 if $self->{POS} < 0; # error if we can't read from here
+ if ($self->{POS} > $CHUNK_SIZE) {
+ my $purge = $self->{POS} - ($self->{POS} % $CHUNK_SIZE);
+ if ($purge >= $self->{LEN}) {
+ # read up to current position in 64k chunks, discarding as we go
+ while ($self->{POS} > $self->{LEN}) {
+ $self->{BASE} += $self->{LEN};
+ $self->{POS} -= $self->{LEN};
+ ${$self->{BUFF_PT}} = '';
+ $self->{LEN} = read($self->{FILE_PT}, ${$self->{BUFF_PT}}, $SKIP_SIZE);
+ last if $self->{LEN} < $SKIP_SIZE;
+ }
+ } elsif ($purge > 0) {
+ ${$self->{BUFF_PT}} = substr ${$self->{BUFF_PT}}, $purge;
+ $self->{BASE} += $purge;
+ $self->{POS} -= $purge;
+ $self->{LEN} -= $purge;
+ }
+ }
+ return 1;
+}
#------------------------------------------------------------------------------
-# set binary mode
+# Set binary mode
# Inputs: 0) reference to RandomAccess object
sub BinMode($)
{
@@ -322,7 +361,7 @@ sub BinMode($)
}
#------------------------------------------------------------------------------
-# close the file and free the buffer
+# Close the file and free the buffer
# Inputs: 0) reference to RandomAccess object
sub Close($)
{
@@ -370,6 +409,7 @@ sub Close($)
# reset the buffer
my $emptyBuff = '';
$self->{BUFF_PT} = \$emptyBuff;
+ $self->{BASE} = 0;
$self->{LEN} = 0;
$self->{POS} = 0;
}
diff --git a/lib/File/RandomAccess.pod b/lib/File/RandomAccess.pod
index 860f8a3a..d3a26f37 100644
--- a/lib/File/RandomAccess.pod
+++ b/lib/File/RandomAccess.pod
@@ -215,6 +215,25 @@ Nothing.
=back
+=head1 OPTIONS
+
+=over 4
+
+=item B<NoBuffer>
+
+Avoid buffering sequential files.
+
+ $raf->{NoBuffer} = 1;
+
+When this option is set, old data is purged from the internal buffer before
+a read operation on a sequential file. In this mode, memory requirements
+may be significantly reduced when reading sequential files, but seeking
+backward is limited to within the size of the internal buffer (which will be
+at least as large as the last returned data block), and seeking relative to
+the end of file is not allowed.
+
+=back
+
=head1 AUTHOR
Copyright 2003-2018 Phil Harvey (phil at owl.phy.queensu.ca)
diff --git a/lib/Image/ExifTool.pm b/lib/Image/ExifTool.pm
index 4b6990a6..df2f2e33 100644
--- a/lib/Image/ExifTool.pm
+++ b/lib/Image/ExifTool.pm
@@ -27,7 +27,7 @@ use vars qw($VERSION $RELEASE @ISA @EXPORT_OK %EXPORT_TAGS $AUTOLOAD @fileTypes
%mimeType $swapBytes $swapWords $currentByteOrder %unpackStd
%jpegMarker %specialTags %fileTypeLookup);
-$VERSION = '11.12';
+$VERSION = '11.13';
$RELEASE = '';
@ISA = qw(Exporter);
%EXPORT_TAGS = (
@@ -5726,6 +5726,8 @@ sub ProcessJPEG($$)
return 1 if $fast and $fast == 3; # don't process file when FastScan == 3
$$self{LOW_PRIORITY_DIR}{IFD1} = 1; # lower priority of IFD1 tags
}
+ $$raf{NoBuffer} = 1 if $self->Options('FastScan'); # disable buffering in FastScan mode
+
$dumpParms{MaxLen} = 128 if $verbose < 4;
if ($htmlDump) {
$dumpEnd = $raf->Tell();
diff --git a/lib/Image/ExifTool.pod b/lib/Image/ExifTool.pod
index 0636429d..98050c42 100644
--- a/lib/Image/ExifTool.pod
+++ b/lib/Image/ExifTool.pod
@@ -617,12 +617,13 @@ this option set to 1, ExifTool will not scan to the end of a JPEG image to
check for an AFCP, CanonVRD, FotoStation, PhotoMechanic, MIE or PreviewImage
trailer. This also stops the parsing after the first comment in GIF images,
and at the audio/video data with RIFF-format files (AVI, WAV, etc), so any
-trailing metadata (eg. XMP written by some utilities) may be missed. When
-combined with the ScanForXMP option, prevents scanning for XMP in recognized
-file types. With a value of 2, ExifTool will also avoid extracting any EXIF
-MakerNote information. When set to 3, the file is not actually parsed, and
-only an initial guess at FileType and some pseudo tags are returned.
-Default is undef.
+trailing metadata (eg. XMP written by some utilities) may be missed. Also
+disables input buffering for some types of files to reduce memory usage when
+reading from a non-seekable stream. When combined with the ScanForXMP
+option, prevents scanning for XMP in recognized file types. With a value of
+2, ExifTool will also avoid extracting any EXIF MakerNote information. When
+set to 3, the file is not actually parsed, and only an initial guess at
+FileType and some pseudo tags are returned. Default is undef.
=item Filter
@@ -805,10 +806,10 @@ Flag set to assume that QuickTime date/time values are stored as UTC,
causing conversion to local time when they are extracted and from local time
when written. According to the QuickTime specification date/time values
should be UTC, but many digital cameras store local time instead (presumably
-because they don't know the time zone), so the default is undef. This
-option also disables the autodetection of incorrect time-zero offsets in
-QuickTime date/time values, and enforces a time zero of 1904 as per the
-QuickTime specification.
+because they don't know the time zone), so the default is to not convert
+these times. This option also disables the autodetection of incorrect
+time-zero offsets in QuickTime date/time values, and enforces a time zero of
+1904 as per the QuickTime specification.
=item RequestAll
@@ -864,9 +865,14 @@ the DateFormat option is used. Default is undef.
0 - Return date/time value unchanged if it can't be converted
1 - Return undef if date/time value can't be converted
-When set to 1 while writing a PrintConv date/time value, the value is
-written only if POSIX::strptime or Time::Piece is available and can
-successfully convert the value.
+When set to 1 while writing a PrintConv date/time value with the DateFormat
+option set, the value is written only if POSIX::strptime or Time::Piece is
+available and can successfully convert the value.
+
+For PNG CreationTime, this option has the additional effect of causing the
+date/time to be reformatted according to PNG 1.2 recommendation (RFC-1123)
+when writing, and a warning to be issued for any non-standard value when
+reading.
=item Struct
diff --git a/lib/Image/ExifTool/Canon.pm b/lib/Image/ExifTool/Canon.pm
index fa789fef..8931cef5 100644
--- a/lib/Image/ExifTool/Canon.pm
+++ b/lib/Image/ExifTool/Canon.pm
@@ -87,7 +87,7 @@ sub ProcessCTMD($$$);
sub ProcessExifInfo($$$);
sub SwapWords($);
-$VERSION = '3.99';
+$VERSION = '4.00';
# Note: Removed 'USM' from 'L' lenses since it is redundant - PH
# (or is it? Ref 32 shows 5 non-USM L-type lenses)
@@ -432,6 +432,7 @@ $VERSION = '3.99';
368 => 'Sigma 14-24mm f/2.8 DG HSM | A or other Sigma Lens', #IB (A018)
368.1 => 'Sigma 20mm f/1.4 DG HSM | A', #50 (newer firmware)
368.2 => 'Sigma 50mm f/1.4 DG HSM | A', #50
+ 368.3 => 'Sigma 40mm f/1.4 DG HSM | A', #IB (018)
# Note: LensType 488 (0x1e8) is reported as 232 (0xe8) in 7D CameraSettings
488 => 'Canon EF-S 15-85mm f/3.5-5.6 IS USM', #PH
489 => 'Canon EF 70-300mm f/4-5.6L IS USM', #Gerald Kapounek
diff --git a/lib/Image/ExifTool/Exif.pm b/lib/Image/ExifTool/Exif.pm
index b2afe8ee..bd60d23a 100644
--- a/lib/Image/ExifTool/Exif.pm
+++ b/lib/Image/ExifTool/Exif.pm
@@ -55,7 +55,7 @@ use vars qw($VERSION $AUTOLOAD @formatSize @formatName %formatNumber %intFormat
use Image::ExifTool qw(:DataAccess :Utils);
use Image::ExifTool::MakerNotes;
-$VERSION = '4.05';
+$VERSION = '4.06';
sub ProcessExif($$$);
sub WriteExif($$$);
@@ -4149,7 +4149,7 @@ my %subSecConv = (
3 => 'FocusDistance', # focus distance in metres (0 is infinity)
4 => 'SubjectDistance',
5 => 'ObjectDistance',
- 6 => 'ApproximateFocusDistance ',
+ 6 => 'ApproximateFocusDistance',
7 => 'FocusDistanceLower',
8 => 'FocusDistanceUpper',
},
diff --git a/lib/Image/ExifTool/Nikon.pm b/lib/Image/ExifTool/Nikon.pm
index b3a0628d..a26f2f7f 100644
--- a/lib/Image/ExifTool/Nikon.pm
+++ b/lib/Image/ExifTool/Nikon.pm
@@ -59,7 +59,7 @@ use Image::ExifTool qw(:DataAccess :Utils);
use Image::ExifTool::Exif;
use Image::ExifTool::GPS;
-$VERSION = '3.51';
+$VERSION = '3.52';
sub LensIDConv($$$);
sub ProcessNikonAVI($$$);
@@ -930,6 +930,27 @@ my %afPoints153 = (
31 => 'B8', 62 => 'H13', 93 => 'C17', 124 => 'G4',
);
+my %cropHiSpeed = ( #IB
+ 0 => 'Off',
+ 1 => '1.3x Crop', # (1.3x Crop, Large)
+ 2 => 'DX Crop',
+ 3 => '5:4 Crop',
+ 4 => '3:2 Crop',
+ 6 => '16:9 Crop',
+ 9 => 'DX Movie Crop', # (DX during movie recording, Large)
+ 11 => 'FX Uncropped',
+ 12 => 'DX Uncropped',
+ 17 => '1:1 Crop',
+ OTHER => sub {
+ my ($val, $inv, $conv) = @_;
+ return undef if $inv;
+ my @a = split ' ', $val;
+ return "Unknown ($val)" unless @a == 7;
+ $a[0] = $$conv{$a[0]} || "Unknown ($a[0])";
+ return "$a[0] ($a[1]x$a[2] cropped to $a[3]x$a[4] at pixel $a[5],$a[6])";
+ },
+);
+
my %offOn = ( 0 => 'Off', 1 => 'On' );
# common attributes for writable BinaryData directories
@@ -1138,12 +1159,7 @@ my %binaryDataAttrs = (
Name => 'CropHiSpeed',
Writable => 'int16u',
Count => 7,
- PrintConv => q{
- my @a = split ' ', $val;
- return "Unknown ($val)" unless @a == 7;
- $a[0] = $a[0] ? "On" : "Off";
- return "$a[0] ($a[1]x$a[2] cropped to $a[3]x$a[4] at pixel $a[5],$a[6])";
- }
+ PrintConv => \%cropHiSpeed,
},
0x001c => { #28 (D3 "the application of CSb6 to the selected metering mode")
Name => 'ExposureTuning',
@@ -7981,12 +7997,7 @@ my %nikonFocalConversions = (
Name => 'CropHiSpeed',
Writable => 'int16u',
Count => 7,
- PrintConv => q{
- my @a = split ' ', $val;
- return "Unknown ($val)" unless @a == 7;
- $a[0] = $a[0] ? "On" : "Off";
- return "$a[0] ($a[1]x$a[2] cropped to $a[3]x$a[4] at pixel $a[5],$a[6])";
- }
+ PrintConv => \%cropHiSpeed,
},
0x200001e => {
Name => 'ColorSpace',
diff --git a/lib/Image/ExifTool/PNG.pm b/lib/Image/ExifTool/PNG.pm
index ec48baec..c25962ce 100644
--- a/lib/Image/ExifTool/PNG.pm
+++ b/lib/Image/ExifTool/PNG.pm
@@ -27,7 +27,7 @@ use strict;
use vars qw($VERSION $AUTOLOAD %stdCase);
use Image::ExifTool qw(:DataAccess :Utils);
-$VERSION = '1.45';
+$VERSION = '1.46';
sub ProcessPNG_tEXt($$$);
sub ProcessPNG_iTXt($$$);
@@ -452,7 +452,7 @@ my %unreg = ( Notes => 'unregistered' );
Groups => { 2 => 'Time' },
Shift => 'Time',
Notes => 'stored in RFC-1123 format and converted to/from EXIF format by ExifTool',
- ValueConv => \&ConvertPNGDate,
+ RawConv => \&ConvertPNGDate,
ValueConvInv => \&InversePNGDate,
PrintConv => '$self->ConvertDateTime($val)',
PrintConvInv => '$self->InverseDateTime($val,undef,1)',
@@ -651,22 +651,25 @@ my %tzConv = (
);
sub ConvertPNGDate($$)
{
- my $val = shift;
- # standard format is like "Mon, 1 Jan 2018 12:10:22 EST"
- if ($val =~ /(\d+)\s*(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s*(\d+)\s+(\d+):(\d{2})(:\d{2})?\s*(\S*)/i) {
+ my ($val, $et) = @_;
+ # standard format is like "Mon, 1 Jan 2018 12:10:22 EST" (RFC-1123 section 5.2.14)
+ while ($val =~ /(\d+)\s*(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s*(\d+)\s+(\d+):(\d{2})(:\d{2})?\s*(\S*)/i) {
my ($day,$mon,$yr,$hr,$min,$sec,$tz) = ($1,$2,$3,$4,$5,$6,$7);
$yr += $yr > 70 ? 1900 : 2000 if $yr < 100; # boost year to 4 digits if necessary
$mon = $monthNum{ucfirst lc $mon} or return $val;
if (not $tz) {
$tz = '';
- } elsif ($tzConv{$tz}) {
- $tz = $tzConv{$tz};
+ } elsif ($tzConv{uc $tz}) {
+ $tz = $tzConv{uc $tz};
} elsif ($tz =~ /^([-+]\d+):?(\d{2})/) {
$tz = $1 . ':' . $2;
} else {
- return $val; # (non-standard date)
+ last; # (non-standard date)
}
- $val = sprintf("%.4d:%.2d:%.2d %.2d:%.2d%s%s",$yr,$mon,$day,$hr,$min,$sec||':00',$tz);
+ return sprintf("%.4d:%.2d:%.2d %.2d:%.2d%s%s",$yr,$mon,$day,$hr,$min,$sec||':00',$tz);
+ }
+ if (($et->Options('StrictDate') and not $$et{TAGS_FROM_FILE}) or $et->Options('Validate')) {
+ $et->Warn('Non standard PNG date/time format', 1);
}
return $val;
}
@@ -678,19 +681,24 @@ sub ConvertPNGDate($$)
sub InversePNGDate($$)
{
my ($val, $et) = @_;
- my $err;
- if ($val =~ /^(\d{4}):(\d{2}):(\d{2}) (\d{2})(:\d{2})(:\d{2})?(?:\.\d*)?\s*(\S*)/) {
- my ($yr,$mon,$day,$hr,$min,$sec,$tz) = ($1,$2,$3,$4,$5,$6,$7);
- $sec or $sec = '';
- my %monName = map { $monthNum{$_} => $_ } keys %monthNum;
- $mon = $monName{$mon + 0} or $err = 1;
- $tz =~ /^(Z|[-+]\d{2}:?\d{2})/ or $err = 1 if length $tz;
- $tz =~ tr/://d;
- $val = "$day $mon $yr $hr$min$sec $tz" unless $err;
- }
- if ($err and $et->Options('StrictDate')) {
- warn "Invalid date/time (use YYYY:mm:dd HH:MM:SS[.ss][+/-HH:MM|Z])\n";
- undef $val;
+ if ($et->Options('StrictDate')) {
+ my $err;
+ if ($val =~ /^(\d{4}):(\d{2}):(\d{2}) (\d{2})(:\d{2})(:\d{2})?(?:\.\d*)?\s*(\S*)/) {
+ my ($yr,$mon,$day,$hr,$min,$sec,$tz) = ($1,$2,$3,$4,$5,$6,$7);
+ $sec or $sec = '';
+ my %monName = map { $monthNum{$_} => $_ } keys %monthNum;
+ $mon = $monName{$mon + 0} or $err = 1;
+ if (length $tz) {
+ $tz =~ /^(Z|[-+]\d{2}:?\d{2})/ or $err = 1;
+ $tz =~ tr/://d;
+ $tz = ' ' . $tz;
+ }
+ $val = "$day $mon $yr $hr$min$sec$tz" unless $err;
+ }
+ if ($err) {
+ warn "Invalid date/time (use YYYY:mm:dd HH:MM:SS[.ss][+/-HH:MM|Z])\n";
+ undef $val;
+ }
}
return $val;
}
@@ -1231,6 +1239,8 @@ sub ProcessPNG($$)
# check to be sure this is a valid PNG/MNG/JNG image
return 0 unless $raf->Read($sig,8) == 8 and $pngLookup{$sig};
+ $$raf{NoBuffer} = 1 if $et->Options('FastScan'); # disable buffering in FastScan mode
+
my $earlyXMP = $et->Options('PNGEarlyXMP');
if ($outfile) {
delete $$et{TextChunkType};
diff --git a/lib/Image/ExifTool/QuickTime.pm b/lib/Image/ExifTool/QuickTime.pm
index 410723ed..58d0d428 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.18';
+$VERSION = '2.19';
sub ProcessMOV($$;$);
sub ProcessKeys($$$);
@@ -7513,6 +7513,8 @@ sub ProcessMOV($$;$)
SetByteOrder('MM');
$$et{PRIORITY_DIR} = 'XMP'; # have XMP take priority
}
+ $$raf{NoBuffer} = 1 if $et->Options('FastScan'); # disable buffering in FastScan mode
+
if ($$et{OPTIONS}{ExtractEmbedded}) {
$ee = 1;
$unkOpt = $$et{OPTIONS}{Unknown};
diff --git a/lib/Image/ExifTool/QuickTimeStream.pl b/lib/Image/ExifTool/QuickTimeStream.pl
index 6a4eaa51..8de1b38e 100644
--- a/lib/Image/ExifTool/QuickTimeStream.pl
+++ b/lib/Image/ExifTool/QuickTimeStream.pl
@@ -225,7 +225,7 @@ my %processByMetaFormat = (
},
4 => {
Name => 'AngleAxis',
- Notes => 'angle axis orientation in radians',
+ Notes => 'angle axis orientation in radians in local coordinate system',
Format => 'float[3]',
},
);
@@ -280,6 +280,7 @@ my %processByMetaFormat = (
FIRST_ENTRY => 0,
4 => {
Name => 'Position',
+ Notes => 'X, Y, Z position in local coordinate system',
Format => 'float[3]',
},
);
@@ -684,6 +685,10 @@ sub ProcessSamples($)
# based on known value of 4th-last char = '*'
my $dif = ord('*') - ord(substr($buff, -4, 1));
my $tmp = pack 'C*',map { $_=($_+$dif)&0xff } unpack 'C*',substr $buff,1,-1;
+ if ($verbose > 2) {
+ $et->VPrint(0, "[decrypted text]\n");
+ $et->VerboseDump(\$tmp);
+ }
if ($tmp =~ /^(.*?)(\$[A-Z]{2}RMC.*)/s) {
($val, $buff) = ($1, $2);
$val =~ tr/\t/ /;
@@ -776,7 +781,7 @@ sub ProcessSamples($)
#------------------------------------------------------------------------------
# Process "freeGPS " data blocks referenced by a 'gps ' (GPSDataList) atom
-# Inputs: 0) ExifTool ref, 1) dirInfo ref (with SampleTime and SampleDuration), 2) tagTable ref
+# Inputs: 0) ExifTool ref, 1) dirInfo ref {DataPt,SampleTime,SampleDuration}, 2) tagTable ref
# Returns: 1 on success (or 0 on unrecognized or "measurement-void" GPS data)
# Notes:
# - also see ProcessFreeGPS2() below for processing of other types of freeGPS blocks
@@ -790,7 +795,12 @@ sub ProcessFreeGPS($$$)
return 0 unless length $$dataPt >= 92;
if (substr($$dataPt,12,1) eq "\x05") {
+
# decode encrypted ASCII-based GPS (DashCam Azdome GS63H, ref 5)
+ # header looks like this in my sample:
+ # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 05 01 00 00 [....freeGPS ....]
+ # 0010: 01 03 aa aa f2 e1 f0 ee 54 54 98 9a 9b 92 9a 93 [........TT......]
+ # 0020: 98 9e 98 98 9e 93 98 92 a6 9f 9f 9c 9d ed fa 8a [................]
my $n = length($$dataPt) - 18;
$n = 0x101 if $n > 0x101;
my $buf2 = pack 'C*', map { $_ ^ 0xaa } unpack 'C*', substr($$dataPt,18,$n);
@@ -806,8 +816,14 @@ sub ProcessFreeGPS($$$)
push @xtra, UserLabel => $lbl if length $lbl;
# extract accelerometer data (ref PH)
@acc = ($1/100,$2/100,$3/100) if $buf2 =~ /^.{173}([-+]\d{3})([-+]\d{3})([-+]\d{3})/s;
+
} elsif ($$dataPt =~ /^.{52}(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/) {
+
# decode NMEA-format GPS data (NextBase 512GW dashcam, ref PH)
+ # header looks like this in my sample:
+ # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 40 01 00 00 [....freeGPS @...]
+ # 0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
+ # 0020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
push @xtra, CameraDateTime => "$1:$2:$3 $4:$5:$6";
if ($$dataPt =~ /\$[A-Z]{2}RMC,(\d{2})(\d{2})(\d+(\.\d*)?),A?,(\d+\.\d+),([NS]),(\d+\.\d+),([EW]),(\d*\.?\d*),(\d*\.?\d*),(\d{2})(\d{2})(\d+)/s) {
($lat,$latRef,$lon,$lonRef) = ($5,$6,$7,$8);
@@ -828,9 +844,15 @@ sub ProcessFreeGPS($$$)
# change to signed integer and divide by 256
map { $_ = $_ - 4294967296 if $_ >= 2147483648; $_ /= 256 } @acc;
}
+
} else {
+
# decode binary GPS format (Viofo A119S, ref 2)
- # (see comments in ScanMovieData() below for structure details)
+ # header looks like this in my sample:
+ # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 4c 00 00 00 [....freeGPS L...]
+ # 0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
+ # 0020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
+ # (records are same structure as Type 3 Novatek GPS in ProcessFreeGPS2() below)
($hr,$min,$sec,$yr,$mon,$day,$stat,$latRef,$lonRef,$lat,$lon,$spd,$trk) =
unpack('x48V6a1a1a1x1V4', $$dataPt);
# ignore invalid fixes
@@ -841,6 +863,9 @@ sub ProcessFreeGPS($$$)
$spd *= $knotsToKph; # convert speed to km/h
# ($trk is not confirmed; may be GPSImageDirection, ref PH)
}
+#
+# save tag values extracted by above code
+#
FoundSomething($et, $tagTbl, $$dirInfo{SampleTime}, $$dirInfo{SampleDuration});
# lat/long are in DDDmm.mmmm format
my $deg = int($lat / 100);
@@ -876,7 +901,7 @@ sub ProcessFreeGPS($$$)
#------------------------------------------------------------------------------
# Process "freeGPS " data blocks _not_ referenced by a 'gps ' atom
-# Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tagTable ref
+# Inputs: 0) ExifTool ref, 1) dirInfo ref {DataPt,DataPos}, 2) tagTable ref
# Returns: 1 on success
# Notes:
# - also see ProcessFreeGPS() above
@@ -885,15 +910,25 @@ sub ProcessFreeGPS2($$$)
{
my ($et, $dirInfo, $tagTbl) = @_;
my $dataPt = $$dirInfo{DataPt};
+ my ($yr, $mon, $day, $hr, $min, $sec, $pos);
+ my ($lat, $latRef, $lon, $lonRef, $spd, $trk, $alt, $ddd, $unk);
if (substr($$dataPt,0x45,3) eq 'ATC') {
+ # header looks like this: (sample 1)
+ # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 38 06 00 00 [....freeGPS 8...]
+ # 0010: 49 51 53 32 30 31 33 30 33 30 36 42 00 00 00 00 [IQS20130306B....]
+ # 0020: 4d 61 79 20 31 35 20 32 30 31 35 2c 20 31 39 3a [May 15 2015, 19:]
+ # (sample 2)
+ # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 4c 06 00 00 [....freeGPS L...]
+ # 0010: 32 30 31 33 30 33 31 38 2e 30 31 00 00 00 00 00 [20130318.01.....]
+ # 0020: 4d 61 72 20 31 38 20 32 30 31 33 2c 20 31 34 3a [Mar 18 2013, 14:]
+
my ($recPos, $lastRecPos, $foundNew);
my $verbose = $et->Options('Verbose');
my $dataPos = $$dirInfo{DataPos};
my $then = $$et{FreeGPS2}{Then};
$then or $then = $$et{FreeGPS2}{Then} = [ (0) x 6 ];
-
# Loop through records in the ATC-type GPS block until we find the most recent.
# If we have already found one, then we only need to check the first record
# (in case the buffer wrapped around), and the record after the position of
@@ -932,12 +967,12 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $gpsBlockSize; $recPos += 52) {
next if $now[$i] == $$then[$i];
# we found a more recent record -- extract it and remember its location
if ($verbose) {
- $et->VPrint(2, " Encrypted GPS record:\n");
+ $et->VPrint(2, " + [encrypted GPS record]\n");
$et->VerboseDump(\$a, DataPos => $dataPos + $recPos);
- $et->VPrint(0, " Decrypted GPS record:\n");
+ $et->VPrint(2, " + [decrypted GPS record]\n");
$et->VerboseDump(\$b);
- my @v = unpack 'H8VVC4V!CA3V!CA3VvvV!vCCCCH4', $b;
- $et->VPrint(1, " Unpacked: @v\n");
+ #my @v = unpack 'H8VVC4V!CA3V!CA3VvvV!vCCCCH4', $b;
+ #$et->VPrint(2, " + [unpacked: @v]\n");
# values unpacked above (ref PH):
# 0) 0x00 4 bytes - byte 0=1, 1=counts to 255, 2=record index, 3=0 (ref 3)
# 1) 0x04 4 bytes - int32u: bits 0-4=day, 5-8=mon, 9-19=year (ref 3)
@@ -965,7 +1000,7 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $gpsBlockSize; $recPos += 52) {
}
@$then = @now;
$$et{DOC_NUM} = ++$$et{DOC_COUNT};
- my $trk = Get16s(\$b, 0x24) / 100;
+ $trk = Get16s(\$b, 0x24) / 100;
$trk += 360 if $trk < 0;
my $time = sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%.2dZ', @now[3..5, 0..2]);
$et->HandleTag($tagTbl, GPSDateTime => $time);
@@ -989,90 +1024,153 @@ ATCRec: for ($recPos = 0x30; $recPos + 52 < $gpsBlockSize; $recPos += 52) {
}
# save position of most recent record (needed when parsing the next freeGPS block)
$$et{FreeGPS2}{RecentRecPos} = $lastRecPos;
+ return 1;
+
+ } elsif ($$dataPt =~ /^.{60}A\0.{10}([NS])\0.{14}([EW])\0/s) {
+
+ # header looks like this in my sample:
+ # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 08 01 00 00 [....freeGPS ....]
+ # 0010: 32 30 31 33 30 38 31 35 2e 30 31 00 00 00 00 00 [20130815.01.....]
+ # 0020: 4a 75 6e 20 31 30 20 32 30 31 37 2c 20 31 34 3a [Jun 10 2017, 14:]
+
+ # Type 2 (ref PH):
+ # 0x30 - int32u hour
+ # 0x34 - int32u minute
+ # 0x38 - int32u second
+ # 0x3c - int32u GPS status ('A' or 'V')
+ # 0x40 - double latitude (DDMM.MMMMMM)
+ # 0x48 - int32u latitude ref ('N' or 'S')
+ # 0x50 - double longitude (DDMM.MMMMMM)
+ # 0x58 - int32u longitude ref ('E' or 'W')
+ # 0x60 - double speed (knots)
+ # 0x68 - double heading (deg)
+ # 0x70 - int32u year - 2000
+ # 0x74 - int32u month
+ # 0x78 - int32u day
+ ($latRef, $lonRef) = ($1, $2);
+ ($hr,$min,$sec,$yr,$mon,$day) = unpack('x48V3x52V3', $$dataPt);
+ $lat = GetDouble($dataPt, 0x40);
+ $lon = GetDouble($dataPt, 0x50);
+ $spd = GetDouble($dataPt, 0x60) * $knotsToKph;
+ $trk = GetDouble($dataPt, 0x68);
+
+ } elsif ($$dataPt =~ /^.{72}A([NS])([EW])/s) {
+
+ # Type 3 (Novatek GPS, ref 2): (in case it wasn't decoded via 'gps ' atom)
+ # 0x30 - int32u hour
+ # 0x34 - int32u minute
+ # 0x38 - int32u second
+ # 0x3c - int32u year - 2000
+ # 0x40 - int32u month
+ # 0x44 - int32u day
+ # 0x48 - int8u GPS status ('A' or 'V')
+ # 0x49 - int8u latitude ref ('N' or 'S')
+ # 0x4a - int8u longitude ref ('E' or 'W')
+ # 0x4b - 0
+ # 0x4c - float latitude (DDMM.MMMMMM)
+ # 0x50 - float longitude (DDMM.MMMMMM)
+ # 0x54 - float speed (knots)
+ # 0x58 - float heading (deg)
+ # Type 3b, same as above for 0x30-0x4a (ref PH)
+ # 0x4c - int32s latitude (decimal degrees * 1e7)
+ # 0x50 - int32s longitude (decimal degrees * 1e7)
+ # 0x54 - int32s speed (m/s * 100)
+ # 0x58 - float altitude (m * 1000, NC)
+ ($latRef, $lonRef) = ($1, $2);
+ ($hr,$min,$sec,$yr,$mon,$day) = unpack('x48V6', $$dataPt);
+ if (substr($$dataPt, 16, 3) eq 'IQS') {
+ # Type 3b (ref PH)
+ # header looks like this in my sample:
+ # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 4c 00 00 00 [....freeGPS L...]
+ # 0010: 49 51 53 5f 41 37 5f 32 30 31 35 30 34 31 37 00 [IQS_A7_20150417.]
+ # 0020: 4d 61 72 20 32 39 20 32 30 31 37 2c 20 31 36 3a [Mar 29 2017, 16:]
+ $ddd = 1;
+ $lat = abs Get32s($dataPt, 0x4c) / 1e7;
+ $lon = abs Get32s($dataPt, 0x50) / 1e7;
+ $spd = Get32s($dataPt, 0x54) / 100 * $mpsToKph;
+ $alt = GetFloat($dataPt, 0x58) / 1000; # (NC)
+ } else {
+ # Type 3 (ref 2)
+ $lat = GetFloat($dataPt, 0x4c);
+ $lon = GetFloat($dataPt, 0x50);
+ $spd = GetFloat($dataPt, 0x54) * $knotsToKph;
+ $trk = GetFloat($dataPt, 0x58);
+ }
} else {
- my ($yr, $mon, $day, $hr, $min, $sec);
- my ($lat, $latRef, $lon, $lonRef, $spd, $trk, $alt, $ddd);
- if ($$dataPt =~ /^.{60}A\0.{10}([NS])\0.{14}([EW])\0/s) {
- # Type 2 (ref PH):
- # 0x30 - int32u hour
- # 0x34 - int32u minute
- # 0x38 - int32u second
- # 0x3c - int32u GPS status ('A' or 'V')
- # 0x40 - double latitude (DDMM.MMMMMM)
- # 0x48 - int32u latitude ref ('N' or 'S')
- # 0x50 - double longitude (DDMM.MMMMMM)
- # 0x58 - int32u longitude ref ('E' or 'W')
- # 0x60 - double speed (knots)
- # 0x68 - double heading (deg)
- # 0x70 - int32u year - 2000
- # 0x74 - int32u month
- # 0x78 - int32u day
- ($latRef, $lonRef) = ($1, $2);
- ($hr,$min,$sec,$yr,$mon,$day) = unpack('x' . 0x30 . 'V3x52V3', $$dataPt);
- $lat = GetDouble($dataPt, 0x40);
- $lon = GetDouble($dataPt, 0x50);
- $spd = GetDouble($dataPt, 0x60) * $knotsToKph;
- $trk = GetDouble($dataPt, 0x68);
- } elsif ($$dataPt =~ /^.{72}A([NS])([EW])/s) {
- # Type 3 (Novatek GPS, ref 2): (in case it wasn't decoded via 'gps ' atom)
- # 0x30 - int32u hour
- # 0x34 - int32u minute
- # 0x38 - int32u second
- # 0x3c - int32u year - 2000
- # 0x40 - int32u month
- # 0x44 - int32u day
- # 0x48 - int8u GPS status ('A' or 'V')
- # 0x49 - int8u latitude ref ('N' or 'S')
- # 0x4a - int8u longitude ref ('E' or 'W')
- # 0x4b - 0
- # 0x4c - float latitude (DDMM.MMMMMM)
- # 0x50 - float longitude (DDMM.MMMMMM)
- # 0x54 - float speed (knots)
- # 0x58 - float heading (deg)
- ($latRef, $lonRef) = ($1, $2);
- ($hr,$min,$sec,$yr,$mon,$day) = unpack('x' . 0x30 . 'V6', $$dataPt);
- if (substr($$dataPt, 16, 3) eq 'IQS') {
- $ddd = 1;
- $lat = abs Get32s($dataPt, 0x4c) / 1e7;
- $lon = abs Get32s($dataPt, 0x50) / 1e7;
- $spd = Get32s($dataPt, 0x54) / 100 * $mpsToKph;
- $alt = GetFloat($dataPt, 0x58) / 1000; # (NC)
- } else {
- $lat = GetFloat($dataPt, 0x4c);
- $lon = GetFloat($dataPt, 0x50);
- $spd = GetFloat($dataPt, 0x54) * $knotsToKph;
- $trk = GetFloat($dataPt, 0x58);
- }
- } else {
- return 0; # not a recognized or valid freeGPS block
- }
- return 0 if $mon < 1 or $mon > 12; # quick sanity check
- $$et{DOC_NUM} = ++$$et{DOC_COUNT};
- $yr += 2000 if $yr < 2000;
- my $time = sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%.2dZ', $yr, $mon, $day, $hr, $min, $sec);
- # convert from DDMM.MMMMMM to DD.DDDDDD format if necessary
- unless ($ddd) {
- my $deg = int($lat / 100);
- $lat = $deg + ($lat - $deg * 100) / 60;
- $deg = int($lon / 100);
- $lon = $deg + ($lon - $deg * 100) / 60;
- }
- $et->HandleTag($tagTbl, GPSDateTime => $time);
- $et->HandleTag($tagTbl, GPSLatitude => $lat * ($latRef eq 'S' ? -1 : 1));
- $et->HandleTag($tagTbl, GPSLongitude => $lon * ($lonRef eq 'W' ? -1 : 1));
- $et->HandleTag($tagTbl, GPSSpeed => $spd); # (now in km/h)
- $et->HandleTag($tagTbl, GPSSpeedRef => 'K');
- if (defined $trk) {
+ # (look for binary GPS as stored by NextBase 512G, ref PH)
+ # header looks like this in my sample:
+ # 0000: 00 00 80 00 66 72 65 65 47 50 53 20 78 01 00 00 [....freeGPS x...]
+ # 0010: 78 2e 78 78 00 00 00 00 00 00 00 00 00 00 00 00 [x.xx............]
+ # 0020: 30 30 30 30 30 00 00 00 00 00 00 00 00 00 00 00 [00000...........]
+
+ # followed by a number of 32-byte records in this format (big endian!):
+ # 0x30 - int16u unknown (seen: 0x24 0x53 = "$S")
+ # 0x32 - int16u speed (m/s * 100)
+ # 0x34 - int16s heading (deg * 100) (or GPSImgDirection?)
+ # 0x36 - int16u year
+ # 0x38 - int8u month
+ # 0x39 - int8u day
+ # 0x3a - int8u hour
+ # 0x3b - int8u min
+ # 0x3c - int16u sec * 10
+ # 0x3e - int8u unknown (seen: 2)
+ # 0x3f - int32s latitude (decimal degrees * 1e7)
+ # 0x43 - int32s longitude (decimal degrees * 1e7)
+ # 0x47 - int8u unknown (seen: 16)
+ # 0x48-0x4f - all zero
+ for ($pos=0x32; ; ) {
+ ($spd,$trk,$yr,$mon,$day,$hr,$min,$sec,$unk,$lat,$lon) = unpack "x${pos}nnnCCCCnCNN", $$dataPt;
+ # validate record using date/time
+ last if $yr < 2000 or $yr > 2200 or
+ $mon < 1 or $mon > 12 or
+ $day < 1 or $day > 31 or
+ $hr > 59 or $min > 59 or $sec > 600;
+ # change lat/lon to signed integer and divide by 1e7
+ map { $_ = $_ - 4294967296 if $_ >= 2147483648; $_ /= 1e7 } $lat, $lon;
+ $trk -= 0x10000 if $trk >= 0x8000; # make it signed
+ $trk /= 100;
+ $trk += 360 if $trk < 0;
+ my $time = sprintf("%.4d:%.2d:%.2d %.2d:%.2d:%04.1fZ", $yr, $mon, $day, $hr, $min, $sec/10);
+ $$et{DOC_NUM} = ++$$et{DOC_COUNT};
+ $et->HandleTag($tagTbl, GPSDateTime => $time);
+ $et->HandleTag($tagTbl, GPSLatitude => $lat);
+ $et->HandleTag($tagTbl, GPSLongitude => $lon);
+ $et->HandleTag($tagTbl, GPSSpeed => $spd / 100 * $mpsToKph);
+ $et->HandleTag($tagTbl, GPSSpeedRef => 'K');
$et->HandleTag($tagTbl, GPSTrack => $trk);
$et->HandleTag($tagTbl, GPSTrackRef => 'T');
+ last if $pos += 0x20 > length($$dataPt) - 0x1e;
}
- if (defined $alt) {
- $et->HandleTag($tagTbl, GPSAltitude => $alt);
- }
+ return $$et{DOC_NUM} ? 1 : 0; # return 0 if nothing extracted
+ }
+#
+# save tag values extracted by above code
+#
+ return 0 if $mon < 1 or $mon > 12; # quick sanity check
+ $$et{DOC_NUM} = ++$$et{DOC_COUNT};
+ $yr += 2000 if $yr < 2000;
+ my $time = sprintf('%.4d:%.2d:%.2d %.2d:%.2d:%.2dZ', $yr, $mon, $day, $hr, $min, $sec);
+ # convert from DDMM.MMMMMM to DD.DDDDDD format if necessary
+ unless ($ddd) {
+ my $deg = int($lat / 100);
+ $lat = $deg + ($lat - $deg * 100) / 60;
+ $deg = int($lon / 100);
+ $lon = $deg + ($lon - $deg * 100) / 60;
+ }
+ $et->HandleTag($tagTbl, GPSDateTime => $time);
+ $et->HandleTag($tagTbl, GPSLatitude => $lat * ($latRef eq 'S' ? -1 : 1));
+ $et->HandleTag($tagTbl, GPSLongitude => $lon * ($lonRef eq 'W' ? -1 : 1));
+ $et->HandleTag($tagTbl, GPSSpeed => $spd); # (now in km/h)
+ $et->HandleTag($tagTbl, GPSSpeedRef => 'K');
+ if (defined $trk) {
+ $et->HandleTag($tagTbl, GPSTrack => $trk);
+ $et->HandleTag($tagTbl, GPSTrackRef => 'T');
+ }
+ if (defined $alt) {
+ $et->HandleTag($tagTbl, GPSAltitude => $alt);
}
-
return 1;
}
diff --git a/lib/Image/ExifTool/RIFF.pm b/lib/Image/ExifTool/RIFF.pm
index 11dc1f82..38a23666 100644
--- a/lib/Image/ExifTool/RIFF.pm
+++ b/lib/Image/ExifTool/RIFF.pm
@@ -29,7 +29,7 @@ use strict;
use vars qw($VERSION);
use Image::ExifTool qw(:DataAccess :Utils);
-$VERSION = '1.48';
+$VERSION = '1.49';
sub ConvertTimecode($);
sub ProcessSGLT($$$);
@@ -1607,6 +1607,7 @@ sub ProcessRIFF($$)
$buff .= $buf2;
return 0 unless $buff =~ /WAVE(.{4})?fmt /sg and $raf->Seek(pos($buff) - 4, 0);
}
+ $$raf{NoBuffer} = 1 if $et->Options('FastScan'); # disable buffering in FastScan mode
$mime = $riffMimeType{$type} if $type;
$et->SetFileType($type, $mime);
$$et{VALUE}{FileType} .= ' (RF64)' if $rf64;
diff --git a/lib/Image/ExifTool/TagNames.pod b/lib/Image/ExifTool/TagNames.pod
index 5f837868..9051c204 100644
--- a/lib/Image/ExifTool/TagNames.pod
+++ b/lib/Image/ExifTool/TagNames.pod
@@ -10655,7 +10655,7 @@ DiMAGE X and Xt.
0x0018 FlashExposureBracketValue undef[4]
0x0019 ExposureBracketValue rational64s
0x001a ImageProcessing string
- 0x001b CropHiSpeed int16u[7]~
+ 0x001b CropHiSpeed int16u[7]
0x001c ExposureTuning undef[3]
0x001d SerialNumber string!
0x001e ColorSpace int16u
@@ -11783,7 +11783,7 @@ videos from some Nikon cameras.
0x2000005 WhiteBalance no
0x2000007 FocusMode string
0x200000b WhiteBalanceFineTune no
- 0x200001b CropHiSpeed int16u[7]~
+ 0x200001b CropHiSpeed int16u[7]
0x200001e ColorSpace no
0x200001f VRInfo Nikon VRInfo
0x2000022 ActiveD-Lighting int16u