exiftool_vendored 12.18.0 → 12.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of exiftool_vendored might be problematic. Click here for more details.

Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/bin/Changes +54 -0
  3. data/bin/MANIFEST +1 -0
  4. data/bin/META.json +1 -1
  5. data/bin/META.yml +1 -1
  6. data/bin/README +2 -2
  7. data/bin/config_files/example.config +1 -8
  8. data/bin/exiftool +70 -32
  9. data/bin/fmt_files/gpx.fmt +1 -1
  10. data/bin/fmt_files/gpx_wpt.fmt +1 -1
  11. data/bin/fmt_files/kml.fmt +1 -1
  12. data/bin/fmt_files/kml_track.fmt +1 -1
  13. data/bin/lib/Image/ExifTool.pm +74 -24
  14. data/bin/lib/Image/ExifTool.pod +33 -25
  15. data/bin/lib/Image/ExifTool/Apple.pm +3 -2
  16. data/bin/lib/Image/ExifTool/BuildTagLookup.pm +20 -10
  17. data/bin/lib/Image/ExifTool/Canon.pm +6 -1
  18. data/bin/lib/Image/ExifTool/DJI.pm +6 -6
  19. data/bin/lib/Image/ExifTool/Exif.pm +5 -2
  20. data/bin/lib/Image/ExifTool/FITS.pm +13 -2
  21. data/bin/lib/Image/ExifTool/GPS.pm +22 -11
  22. data/bin/lib/Image/ExifTool/ICC_Profile.pm +2 -2
  23. data/bin/lib/Image/ExifTool/M2TS.pm +40 -4
  24. data/bin/lib/Image/ExifTool/MIE.pm +2 -2
  25. data/bin/lib/Image/ExifTool/Microsoft.pm +296 -82
  26. data/bin/lib/Image/ExifTool/Nikon.pm +2 -1
  27. data/bin/lib/Image/ExifTool/Olympus.pm +2 -2
  28. data/bin/lib/Image/ExifTool/QuickTime.pm +28 -12
  29. data/bin/lib/Image/ExifTool/QuickTimeStream.pl +6 -5
  30. data/bin/lib/Image/ExifTool/Shortcuts.pm +9 -0
  31. data/bin/lib/Image/ExifTool/Sony.pm +51 -17
  32. data/bin/lib/Image/ExifTool/TagInfoXML.pm +1 -0
  33. data/bin/lib/Image/ExifTool/TagLookup.pm +4031 -4022
  34. data/bin/lib/Image/ExifTool/TagNames.pod +130 -95
  35. data/bin/lib/Image/ExifTool/WriteExif.pl +1 -1
  36. data/bin/lib/Image/ExifTool/WriteQuickTime.pl +7 -5
  37. data/bin/lib/Image/ExifTool/Writer.pl +43 -10
  38. data/bin/lib/Image/ExifTool/XMP.pm +4 -4
  39. data/bin/perl-Image-ExifTool.spec +1 -1
  40. data/lib/exiftool_vendored/version.rb +1 -1
  41. metadata +20 -6
@@ -24,7 +24,7 @@ use strict;
24
24
  use vars qw($VERSION);
25
25
  use Image::ExifTool qw(:DataAccess :Utils);
26
26
 
27
- $VERSION = '1.35';
27
+ $VERSION = '1.36';
28
28
 
29
29
  sub ProcessICC($$);
30
30
  sub ProcessICC_Profile($$$);
@@ -1225,7 +1225,7 @@ sub ProcessICC_Profile($$$)
1225
1225
  my $type = substr($$dataPt, $valuePtr, 4);
1226
1226
  #### eval Validate ($type)
1227
1227
  if (defined $$subdir{Validate} and not eval $$subdir{Validate}) {
1228
- $et->Warn("Invalid $name data");
1228
+ $et->Warn("Invalid ICC $name data");
1229
1229
  } else {
1230
1230
  $et->ProcessDirectory(\%subdirInfo, $newTagTable, $$subdir{ProcessProc});
1231
1231
  }
@@ -31,7 +31,7 @@ use strict;
31
31
  use vars qw($VERSION);
32
32
  use Image::ExifTool qw(:DataAccess :Utils);
33
33
 
34
- $VERSION = '1.18';
34
+ $VERSION = '1.19';
35
35
 
36
36
  # program map table "stream_type" lookup (ref 6/1)
37
37
  my %streamType = (
@@ -57,7 +57,8 @@ my %streamType = (
57
57
  0x13 => 'ISO 14496-1 SL-packetized',
58
58
  0x14 => 'ISO 13818-6 Synchronized Download Protocol',
59
59
  # 0x15-0x7F => 'ISO 13818-1 Reserved',
60
- 0x1b => 'H.264 Video',
60
+ 0x1b => 'H.264 (AVC) Video',
61
+ 0x24 => 'H.265 (HEVC) Video', #PH
61
62
  0x80 => 'DigiCipher II Video',
62
63
  0x81 => 'A52/AC-3 Audio',
63
64
  0x82 => 'HDMV DTS Audio',
@@ -67,6 +68,7 @@ my %streamType = (
67
68
  0x86 => 'DTS-HD Audio',
68
69
  0x87 => 'E-AC-3 Audio',
69
70
  0x8a => 'DTS Audio',
71
+ 0x90 => 'PGS Audio', #https://www.avsforum.com/threads/bass-eq-for-filtered-movies.2995212/page-399
70
72
  0x91 => 'A52b/AC-3 Audio',
71
73
  0x92 => 'DVD_SPU vls Subtitle',
72
74
  0x94 => 'SDDS Audio',
@@ -299,6 +301,33 @@ sub ParsePID($$$$$)
299
301
  } elsif ($type == 0x81 or $type == 0x87 or $type == 0x91) {
300
302
  # AC-3 audio
301
303
  ParseAC3Audio($et, $dataPt);
304
+ } elsif ($type < 0) {
305
+ if ($$dataPt =~ /^(.{164})?(.{24})A[NS][EW]/s) {
306
+ # (Blueskysea B4K, Novatek NT96670)
307
+ # 0000: 01 00 ff 00 30 31 32 33 34 35 37 38 61 62 63 64 [....01234578abcd]
308
+ # 0010: 65 66 67 0a 00 00 00 00 00 00 00 00 00 00 00 00 [efg.............]
309
+ # 0020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
310
+ # 0030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
311
+ # 0040: 00 00 00 00 30 31 32 33 34 35 37 38 71 77 65 72 [....01234578qwer]
312
+ # 0050: 74 79 75 69 6f 70 0a 00 00 00 00 00 00 00 00 00 [tyuiop..........]
313
+ # 0060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
314
+ # 0070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [................]
315
+ # 0080: 00 00 00 00 63 38 61 61 32 35 63 66 34 35 65 65 [....c8aa25cf45ee]
316
+ # 0090: 61 39 65 32 34 34 32 66 61 65 62 35 65 30 39 39 [a9e2442faeb5e099]
317
+ # 00a0: 30 37 64 34 15 00 00 00 10 00 00 00 1b 00 00 00 [07d4............]
318
+ # 00b0: 15 00 00 00 01 00 00 00 09 00 00 00 41 4e 57 00 [............ANW.]
319
+ # 00c0: 82 9a 57 45 98 b2 00 46 66 66 e4 41 d7 e3 14 43 [..WE...Fff.A...C]
320
+ # 00d0: 01 00 02 00 03 00 04 00 05 00 06 00 [............]
321
+ # (Viofo A119V3)
322
+ # 0000: 08 00 00 00 07 00 00 00 18 00 00 00 15 00 00 00 [................]
323
+ # 0010: 03 00 00 00 0b 00 00 00 41 4e 45 00 01 f2 ac 45 [........ANE....E]
324
+ # 0020: 2d 7f 6e 45 b8 1e 97 41 d7 23 46 43 00 00 00 00 [-.nE...A.#FC....]
325
+ # pad with dummy header and parse with existing FreeGPS code (minimum 92 bytes)
326
+ my $dat = ("\0" x 16) . substr($$dataPt, length($1 || '')) . ("\0" x 20);
327
+ my $tbl = GetTagTable('Image::ExifTool::QuickTime::Stream');
328
+ Image::ExifTool::QuickTime::ProcessFreeGPS($et, { DataPt => \$dat }, $tbl);
329
+ $more = 1;
330
+ }
302
331
  }
303
332
  return $more;
304
333
  }
@@ -352,6 +381,14 @@ sub ProcessM2TS($$)
352
381
  my %needPID = ( 0 => 1 ); # lookup for stream PID's that we still need to parse
353
382
  my $pEnd = 0;
354
383
 
384
+ # scan entire file for GPS program 0x0300 if ExtractEmbedded option is 3 or higher
385
+ # (some dashcams write this program but don't include it in the PMT)
386
+ if (($et->Options('ExtractEmbedded') || 0) > 2) {
387
+ $needPID{0x0300} = 1;
388
+ $pidType{0x0300} = -1;
389
+ $pidName{0x0300} = 'unregistered dashcam GPS';
390
+ }
391
+
355
392
  # parse packets from MPEG-2 Transport Stream
356
393
  for (;;) {
357
394
 
@@ -421,12 +458,11 @@ sub ProcessM2TS($$)
421
458
  my $adaptation_field_exists = $prefix & 0x00000020;
422
459
  my $payload_data_exists = $prefix & 0x00000010;
423
460
  # my $continuity_counter = $prefix & 0x0000000f;
424
-
425
461
  if ($verbose > 1) {
426
462
  my $i = ($raf->Tell() - length($buff) + $pEnd) / $pLen - 1;
427
463
  print $out "Transport packet $i:\n";
428
464
  $et->VerboseDump(\$buff, Len => $pLen, Addr => $i * $pLen, Start => $pos - $prePos);
429
- my $str = $pidName{$pid} ? " ($pidName{$pid})" : '';
465
+ my $str = $pidName{$pid} ? " ($pidName{$pid})" : ' <not in Program Map Table!>';
430
466
  printf $out " Timecode: 0x%.4x\n", Get32u(\$buff, $pos - $prePos) if $pLen == 192;
431
467
  printf $out " Packet ID: 0x%.4x$str\n", $pid;
432
468
  printf $out " Start Flag: %s\n", $payload_unit_start_indicator ? 'Yes' : 'No';
@@ -393,7 +393,7 @@ my %offOn = ( 0 => 'Off', 1 => 'On' );
393
393
  ValueConv => 'Image::ExifTool::GPS::ToDegrees($val, 1)',
394
394
  ValueConvInv => 'Image::ExifTool::GPS::ToDMS($self, $val, 0)',
395
395
  PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")',
396
- PrintConvInv => 'Image::ExifTool::GPS::ToDegrees($val, 1)',
396
+ PrintConvInv => 'Image::ExifTool::GPS::ToDegrees($val, 1, "lat")',
397
397
  },
398
398
  Longitude => {
399
399
  Name => 'GPSLongitude',
@@ -406,7 +406,7 @@ my %offOn = ( 0 => 'Off', 1 => 'On' );
406
406
  ValueConv => 'Image::ExifTool::GPS::ToDegrees($val, 1)',
407
407
  ValueConvInv => 'Image::ExifTool::GPS::ToDMS($self, $val, 0)',
408
408
  PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")',
409
- PrintConvInv => 'Image::ExifTool::GPS::ToDegrees($val, 1)',
409
+ PrintConvInv => 'Image::ExifTool::GPS::ToDegrees($val, 1, "lon")',
410
410
  },
411
411
  MeasureMode => {
412
412
  Name => 'GPSMeasureMode',
@@ -5,6 +5,7 @@
5
5
  #
6
6
  # Revisions: 2010/10/01 - P. Harvey Created
7
7
  # 2011/10/05 - PH Added ProcessXtra()
8
+ # 2021/02/23 - PH Added abiltity to write Xtra tags
8
9
  #
9
10
  # References: 1) http://research.microsoft.com/en-us/um/redmond/groups/ivm/hdview/hdmetadataspec.htm
10
11
  #------------------------------------------------------------------------------
@@ -16,9 +17,11 @@ use vars qw($VERSION);
16
17
  use Image::ExifTool qw(:DataAccess :Utils);
17
18
  use Image::ExifTool::XMP;
18
19
 
19
- $VERSION = '1.20';
20
+ $VERSION = '1.22';
20
21
 
21
22
  sub ProcessXtra($$$);
23
+ sub WriteXtra($$$);
24
+ sub CheckXtra($$$);
22
25
 
23
26
  # tags written by Microsoft HDView (ref 1)
24
27
  %Image::ExifTool::Microsoft::Stitch = (
@@ -196,11 +199,20 @@ my %sRegions = (
196
199
  # and Image::ExifTool::WTV::Metadata
197
200
  %Image::ExifTool::Microsoft::Xtra = (
198
201
  PROCESS_PROC => \&ProcessXtra,
202
+ WRITE_PROC => \&WriteXtra,
203
+ CHECK_PROC => \&CheckXtra,
204
+ WRITE_GROUP => 'Microsoft',
205
+ AVOID => 1,
199
206
  GROUPS => { 0 => 'QuickTime', 2 => 'Video' },
200
207
  VARS => { NO_ID => 1 },
201
208
  NOTES => q{
202
- Tags extracted from the Microsoft "Xtra" atom of QuickTime videos. Tag ID's
203
- are not shown because some are unruly GUID's.
209
+ Tags found in the Microsoft "Xtra" atom of QuickTime videos. Tag ID's are
210
+ not shown because some are unruly GUID's. Currently most of these tags are
211
+ not writable because the Microsoft documentation is poor and samples were
212
+ not available, but more tags may be made writable in the future if samples
213
+ are provided. Note that writable tags in this table are are flagged to
214
+ "Avoid", which means that other more common tags will be written instead if
215
+ possible unless the Microsoft group is specified explicitly.
204
216
  },
205
217
  Abstract => { },
206
218
  AcquisitionTime => { Groups => { 2 => 'Time' } },
@@ -327,30 +339,34 @@ my %sRegions = (
327
339
  UserServiceRating => { },
328
340
  VideoBitrate => { },
329
341
  VideoFormat => { },
330
- 'WM/AlbumArtist' => 'AlbumArtist',
331
- 'WM/AlbumCoverURL' => 'AlbumCoverURL',
332
- 'WM/AlbumTitle' => 'AlbumTitle',
342
+ 'WM/AlbumArtist' => { Name => 'AlbumArtist', Writable => 'Unicode' }, # (NC)
343
+ 'WM/AlbumCoverURL' => { Name => 'AlbumCoverURL', Writable => 'Unicode' }, # (NC)
344
+ 'WM/AlbumTitle' => { Name => 'AlbumTitle', Writable => 'Unicode' }, # (NC)
333
345
  'WM/BeatsPerMinute' => 'BeatsPerMinute',
334
- 'WM/Category' => 'Category',
335
- 'WM/Composer' => 'Composer',
336
- 'WM/Conductor' => 'Conductor',
337
- 'WM/ContentDistributor' => 'ContentDistributor',
346
+ 'WM/Category' => { Name => 'Category', Writable => 'Unicode', List => 1 },
347
+ 'WM/Composer' => { Name => 'Composer', Writable => 'Unicode' }, # (NC)
348
+ 'WM/Conductor' => { Name => 'Conductor', Writable => 'Unicode', List => 1 },
349
+ 'WM/ContentDistributor' => { Name => 'ContentDistributor', Writable => 'Unicode' },
338
350
  'WM/ContentDistributorType' => 'ContentDistributorType',
339
351
  'WM/ContentGroupDescription'=> 'ContentGroupDescription',
340
- 'WM/Director' => 'Director',
352
+ 'WM/Director' => { Name => 'Director', Writable => 'Unicode', List => 1 },
341
353
  'WM/EncodingTime' => {
342
354
  Name => 'EncodingTime',
343
355
  Groups => { 2 => 'Time' },
356
+ Shift => 'Time',
357
+ Writable => 'date',
344
358
  PrintConv => '$self->ConvertDateTime($val)',
359
+ PrintConvInv => '$self->InverseDateTime($val)',
345
360
  },
346
361
  'WM/Genre' => 'Genre',
347
362
  'WM/GenreID' => 'GenreID',
348
- 'WM/InitialKey' => 'InitialKey',
363
+ 'WM/InitialKey' => { Name => 'InitialKey', Writable => 'Unicode' },
349
364
  'WM/Language' => 'Language',
350
365
  'WM/Lyrics' => 'Lyrics',
351
366
  'WM/MCDI' => 'MCDI',
352
367
  'WM/MediaClassPrimaryID' => {
353
368
  Name => 'MediaClassPrimaryID',
369
+ Writable => 'GUID',
354
370
  PrintConv => { #http://msdn.microsoft.com/en-us/library/windows/desktop/dd757960(v=vs.85).aspx
355
371
  'D1607DBC-E323-4BE2-86A1-48A42A28441E' => 'Music',
356
372
  'DB9830BD-3AB3-4FAB-8A37-1A995F7FF74B' => 'Video',
@@ -360,6 +376,7 @@ my %sRegions = (
360
376
  },
361
377
  'WM/MediaClassSecondaryID' => {
362
378
  Name => 'MediaClassSecondaryID',
379
+ Writable => 'GUID',
363
380
  PrintConv => { #http://msdn.microsoft.com/en-us/library/windows/desktop/dd757960(v=vs.85).aspx
364
381
  'E0236BEB-C281-4EDE-A36D-7AF76A3D45B5' => 'Audio Book',
365
382
  '3A172A13-2BD9-4831-835B-114F6A95943F' => 'Spoken Word',
@@ -385,22 +402,22 @@ my %sRegions = (
385
402
  },
386
403
  'WM/MediaOriginalChannel' => 'MediaOriginalChannel',
387
404
  'WM/MediaStationName' => 'MediaStationName',
388
- 'WM/Mood' => 'Mood',
389
- 'WM/OriginalAlbumTitle' => 'OriginalAlbumTitle',
390
- 'WM/OriginalArtist' => 'OriginalArtist',
391
- 'WM/OriginalLyricist' => 'OriginalLyricist',
392
- 'WM/ParentalRating' => 'ParentalRating',
405
+ 'WM/Mood' => { Name => 'Mood', Writable => 'Unicode' },
406
+ 'WM/OriginalAlbumTitle' => { Name => 'OriginalAlbumTitle', Writable => 'Unicode' }, # (NC)
407
+ 'WM/OriginalArtist' => { Name => 'OriginalArtist', Writable => 'Unicode' }, # (NC)
408
+ 'WM/OriginalLyricist' => { Name => 'OriginalLyricist', Writable => 'Unicode' }, # (NC)
409
+ 'WM/ParentalRating' => { Name => 'ParentalRating', Writable => 'Unicode' },
393
410
  'WM/PartOfSet' => 'PartOfSet',
394
- 'WM/Period' => 'Period',
395
- 'WM/Producer' => 'Producer',
411
+ 'WM/Period' => { Name => 'Period', Writable => 'Unicode' },
412
+ 'WM/Producer' => { Name => 'Producer', Writable => 'Unicode', List => 1 },
396
413
  'WM/ProtectionType' => 'ProtectionType',
397
- 'WM/Provider' => 'Provider',
414
+ 'WM/Provider' => { Name => 'Provider', Writable => 'Unicode' }, # (NC)
398
415
  'WM/ProviderRating' => 'ProviderRating',
399
416
  'WM/ProviderStyle' => 'ProviderStyle',
400
- 'WM/Publisher' => 'Publisher',
401
- 'WM/SharedUserRating' => 'SharedUserRating',
417
+ 'WM/Publisher' => { Name => 'Publisher', Writable => 'Unicode' }, # (multiple entries separated by semicolon)
418
+ 'WM/SharedUserRating' => { Name => 'SharedUserRating', Writable => 'int64u' },
402
419
  'WM/SubscriptionContentID' => 'SubscriptionContentID',
403
- 'WM/SubTitle' => 'Subtitle',
420
+ 'WM/SubTitle' => { Name => 'Subtitle', Writable => 'Unicode' },
404
421
  'WM/SubTitleDescription' => 'SubtitleDescription',
405
422
  'WM/TrackNumber' => 'TrackNumber',
406
423
  'WM/UniqueFileIdentifier' => 'UniqueFileIdentifier',
@@ -412,8 +429,11 @@ my %sRegions = (
412
429
  'WM/WMContentID' => 'WMContentID',
413
430
  'WM/WMShadowFileSourceDRMType' => 'WMShadowFileSourceDRMType',
414
431
  'WM/WMShadowFileSourceFileType' => 'WMShadowFileSourceFileType',
415
- 'WM/Writer' => 'Writer',
416
- 'WM/Year' => { Name => 'Year', Groups => { 2 => 'Time' } },
432
+ 'WM/Writer' => { Name => 'Writer', Groups => { 2 => 'Author' }, Writable => 'Unicode' }, # (NC)
433
+ 'WM/Year' => { Name => 'Year', Groups => { 2 => 'Time' } },
434
+ 'WM/PromotionURL' => { Name => 'PromotionURL',Writable => 'Unicode' },
435
+ 'WM/AuthorURL' => { Name => 'AuthorURL', Groups => { 2 => 'Author' }, Writable => 'Unicode' },
436
+ 'WM/EncodedBy', => { Name => 'EncodedBy', Writable => 'Unicode' },
417
437
 
418
438
  # I can't find documentation for the following tags in videos,
419
439
  # but the tag ID's correspond to Microsoft property GUID+ID's
@@ -422,9 +442,12 @@ my %sRegions = (
422
442
  # http://multi-rename-script.googlecode.com/svn-history/r4/trunk/plugins/ShellDetails/ShellDetails.ini
423
443
  # I have observed only 1 so far:
424
444
  '{2CBAA8F5-D81F-47CA-B17A-F8D822300131} 100' => {
425
- Name => 'DateAcquired',
445
+ Name => 'DateAcquired', # (seems to be when videos are downloaded from the camera)
426
446
  Groups => { 2 => 'Time' },
447
+ Shift => 'Time',
448
+ Writable => 'vt_filetime',
427
449
  PrintConv => '$self->ConvertDateTime($val)',
450
+ PrintConvInv => '$self->InverseDateTime($val,undef)',
428
451
  },
429
452
  # the following have not yet been observed...
430
453
  '{B725F130-47EF-101A-A5F1-02608C9EEBAC} 10' => 'Name',
@@ -764,6 +787,243 @@ my %sRegions = (
764
787
  '{64440491-4C8B-11D1-8B70-080036B11A03} 43' => 'TotalBitrate',
765
788
  );
766
789
 
790
+ #------------------------------------------------------------------------------
791
+ # check new value for Xtra tag
792
+ # Inputs: 0) ExifTool object ref, 1) tagInfo hash ref, 2) raw value ref
793
+ # Returns: error string, or undef on success
794
+ sub CheckXtra($$$)
795
+ {
796
+ my ($et, $tagInfo, $valPt) = @_;
797
+ my $format = $$tagInfo{Writable};
798
+ return 'Unknown format' unless $format;
799
+ if ($format =~ /^int/) {
800
+ return 'Not an integer' unless Image::ExifTool::IsInt($$valPt);
801
+ } elsif ($format ne 'Unicode') {
802
+ my @vals = ($$valPt);
803
+ return 'Invalid format' unless WriteXtraValue($et, $tagInfo, \@vals);
804
+ }
805
+ return undef;
806
+ }
807
+
808
+ #------------------------------------------------------------------------------
809
+ # Decode value(s) in Microsoft Xtra tag
810
+ # Inputs: 0) ExifTool object ref, 1) value data
811
+ # Returns: Scalar context: decoded value, List context: 0) decoded value, 1) format string
812
+ sub ReadXtraValue($$)
813
+ {
814
+ my ($et, $data) = @_;
815
+ my ($format, $i, @vals);
816
+
817
+ return undef if length($data) < 10;
818
+
819
+ # (version flags according to the reference, but looks more like a count - PH)
820
+ my $count = Get32u(\$data, 0);
821
+ # point to start of first value (after 4-byte count, 4-byte length and 2-byte type)
822
+ my $valPos = 10;
823
+ for ($i=0; ;) {
824
+ # (stored value includes size of $valLen and $valType, so subtract 6)
825
+ my $valLen = Get32u(\$data, $valPos - 6) - 6;
826
+ last if $valPos + $valLen > length($data);
827
+ my $valType = Get16u(\$data, $valPos - 2);
828
+ my $val = substr($data, $valPos, $valLen);
829
+ # Note: all dumb Microsoft values are little-endian inside a big-endian-format file
830
+ SetByteOrder('II');
831
+ if ($valType == 8) {
832
+ $format = 'Unicode';
833
+ $val = $et->Decode($val, 'UCS2');
834
+ } elsif ($valType == 19 and $valLen == 8) {
835
+ $format = 'int64u';
836
+ $val = Get64u(\$val, 0);
837
+ } elsif ($valType == 21 and $valLen == 8) {
838
+ $format = 'date';
839
+ $val = Get64u(\$val, 0);
840
+ # convert time from 100 ns intervals since Jan 1, 1601
841
+ $val = $val * 1e-7 - 11644473600 if $val;
842
+ # (the Nikon S100 uses UTC timezone, same as ASF - PH)
843
+ $val = Image::ExifTool::ConvertUnixTime($val, 1);
844
+ } elsif ($valType == 72 and $valLen == 16) {
845
+ $format = 'GUID';
846
+ $val = uc unpack('H*',pack('NnnNN',unpack('VvvNN',$val)));
847
+ $val =~ s/(.{8})(.{4})(.{4})(.{4})/$1-$2-$3-$4-/;
848
+ } elsif ($valType == 65 and $valLen > 4) { #PH (empirical)
849
+ $format = 'variant';
850
+ require Image::ExifTool::FlashPix;
851
+ my $vPos = 0; # (necessary because ReadFPXValue updates this)
852
+ # read entry as a VT_VARIANT (use FlashPix module for this)
853
+ $val = Image::ExifTool::FlashPix::ReadFPXValue($et, \$val, $vPos,
854
+ Image::ExifTool::FlashPix::VT_VARIANT(), $valLen, 1);
855
+ } else {
856
+ $format = "Unknown($valType)";
857
+ }
858
+ SetByteOrder('MM'); # back to native QuickTime byte ordering
859
+ push @vals, $val;
860
+ last if ++$i >= $count;
861
+ $valPos += $valLen + 6; # step to next value
862
+ last if $valPos > length($data);
863
+ }
864
+ return wantarray ? (\@vals, $format) : \@vals;
865
+ }
866
+
867
+ #------------------------------------------------------------------------------
868
+ # Write a Microsoft Xtra value
869
+ # Inputs: 0) ExifTool object ref, 1) tagInfo ref, 2) reference to list of values
870
+ # Returns: new value binary data (or empty string)
871
+ sub WriteXtraValue($$$)
872
+ {
873
+ my ($et, $tagInfo, $vals) = @_;
874
+ my $format = $$tagInfo{Writable};
875
+ my $buff = '';
876
+ my $count = 0;
877
+ my $val;
878
+ foreach $val (@$vals) {
879
+ SetByteOrder('II');
880
+ my ($type, $dat);
881
+ if ($format eq 'Unicode') {
882
+ $dat = $et->Encode($val,'UCS2','II') . "\0\0"; # (must be null terminated)
883
+ $type = 8;
884
+ } elsif ($format eq 'int64u') {
885
+ if (Image::ExifTool::IsInt($val)) {
886
+ $dat = Set64u($val);
887
+ $type = 19;
888
+ }
889
+ } elsif ($format eq 'date') {
890
+ $dat = Image::ExifTool::GetUnixTime($val, 1); # (convert to UTC, NC)
891
+ if ($dat) {
892
+ $dat = Set64u(($dat + 11644473600) * 1e7);
893
+ $type = 21;
894
+ }
895
+ } elsif ($format eq 'vt_filetime') {
896
+ $dat = Image::ExifTool::GetUnixTime($val); # (leave as local time, NC)
897
+ if ($dat) {
898
+ $dat = Set32u(64) . Set64u(($dat + 11644473600) * 1e7);
899
+ $type = 65;
900
+ }
901
+ } elsif ($format eq 'GUID') {
902
+ ($dat = $val) =~ tr/-//d;
903
+ if (length($dat) == 32) {
904
+ $dat = pack('VvvNN',unpack('NnnNN',pack('H*', $dat)));
905
+ $type = 72;
906
+ }
907
+ } else {
908
+ $et->WarnOnce("Error converting value for Microsoft:$$tagInfo{Name}");
909
+ }
910
+ SetByteOrder('MM');
911
+ if (defined $type) {
912
+ ++$count;
913
+ $buff .= Set32u(length($dat)+6) . Set16u($type) . $dat;
914
+ }
915
+ }
916
+ return $count ? Set32u($count) . $buff : '';
917
+ }
918
+
919
+ #------------------------------------------------------------------------------
920
+ # Add new values to list
921
+ # Inputs: 0) ExifTool ref, 1) new value list ref, 2) nvHash ref
922
+ # Returns: true if something was added
923
+ sub AddNewValues($$$)
924
+ {
925
+ my ($et, $vals, $nvHash) = @_;
926
+ my @newVals = $et->GetNewValue($nvHash) or return undef;
927
+ if ($$et{OPTIONS}{Verbose} > 1) {
928
+ $et->VPrint(1, " + Microsoft:$$nvHash{TagInfo}{Name} = $_\n") foreach @newVals;
929
+ }
930
+ push @$vals, @newVals;
931
+ return 1;
932
+ }
933
+
934
+ #------------------------------------------------------------------------------
935
+ # Write tags to a Microsoft Xtra MP4 atom
936
+ # Inputs: 0) ExifTool object ref, 1) source dirInfo ref, 2) tag table ref
937
+ # Returns: Microsoft Xtra data block (may be empty if no Xtra data) or undef on error
938
+ sub WriteXtra($$$)
939
+ {
940
+ my ($et, $dirInfo, $tagTablePtr) = @_;
941
+ $et or return 1; # allow dummy access
942
+
943
+ my $delGroup = ($$et{DEL_GROUP} and $$et{DEL_GROUP}{Microsoft});
944
+ my $newTags = $et->GetNewTagInfoHash($tagTablePtr);
945
+
946
+ return undef unless $delGroup or %$newTags; # don't rewrite if nothing to do
947
+
948
+ my $dataPt = $$dirInfo{DataPt};
949
+ my $dataLen = length $$dataPt;
950
+ my $newData = '';
951
+ my $pos = 0;
952
+ my ($err, %done, $changed, $tag);
953
+
954
+ if ($delGroup) {
955
+ $changed = 1 if $dataLen;
956
+ my $empty = '';
957
+ $dataPt = $empty;
958
+ $dataLen = 0;
959
+ }
960
+ for (;;) {
961
+ last if $pos + 4 > $dataLen;
962
+ my $size = Get32u($dataPt, $pos); # (includes $size word)
963
+ ($size < 8 or $pos + $size > $dataLen) and $err=1, last;
964
+ my $tagLen = Get32u($dataPt, $pos + 4);
965
+ $tagLen + 18 > $size and $err=1, last;
966
+ $tag = substr($$dataPt, $pos + 8, $tagLen);
967
+ my @newVals;
968
+ while ($$newTags{$tag}) {
969
+ my $nvHash = $et->GetNewValueHash($$newTags{$tag});
970
+ $$nvHash{CreateOnly} and delete($$newTags{$tag}), last; # don't edit this tag
971
+ my $valPos = $pos + 8 + $tagLen;
972
+ my $valLen = $size - 8 - $tagLen;
973
+ my $val = ReadXtraValue($et, substr($$dataPt, $valPos, $valLen));
974
+ foreach $val (@$val) {
975
+ my $overwrite = $et->IsOverwriting($nvHash, $val);
976
+ $overwrite or push(@newVals, $val), next;
977
+ $et->VPrint(1, " - Microsoft:$$newTags{$tag}{Name} = $val\n");
978
+ next if $done{$tag};
979
+ $done{$tag} = 1;
980
+ AddNewValues($et, \@newVals, $nvHash);
981
+ }
982
+ # add to the end of the list if this was a List-type tag and we didn't delete anything
983
+ if (not $done{$tag} and $$newTags{$tag}{List}) {
984
+ AddNewValues($et, \@newVals, $nvHash) or last;
985
+ $done{$tag} = 1;
986
+ }
987
+ last; # (it was a cheap goto)
988
+ }
989
+ if ($done{$tag}) {
990
+ # write changed values
991
+ my $buff = WriteXtraValue($et, $$newTags{$tag}, \@newVals);
992
+ if (length $buff) {
993
+ $newData .= Set32u(8+length($tag)+length($buff)) . Set32u(length($tag)) . $tag . $buff;
994
+ $changed = 1;
995
+ }
996
+ } else {
997
+ # nothing changed; just copy over
998
+ $newData .= substr($$dataPt, $pos, $size);
999
+ }
1000
+ $pos += $size; # step to next entry
1001
+ }
1002
+ if ($err) {
1003
+ $et->Warn('Microsoft Xtra format error');
1004
+ return undef;
1005
+ }
1006
+ # add any new tags
1007
+ foreach $tag (sort keys %$newTags) {
1008
+ next if $done{$tag};
1009
+ my $nvHash = $et->GetNewValueHash($$newTags{$tag});
1010
+ next unless $$nvHash{IsCreating} and not $$nvHash{EditOnly};
1011
+ my @newVals;
1012
+ AddNewValues($et, \@newVals, $nvHash) or next;
1013
+ my $buff = WriteXtraValue($et, $$newTags{$tag}, \@newVals);
1014
+ if (length $buff) {
1015
+ $newData .= Set32u(8+length($tag)+length($buff)) . Set32u(length($tag)) . $tag . $buff;
1016
+ $changed = 1;
1017
+ }
1018
+ }
1019
+ if ($changed) {
1020
+ ++$$et{CHANGED};
1021
+ } else {
1022
+ undef $newData;
1023
+ }
1024
+ return $newData;
1025
+ }
1026
+
767
1027
  #------------------------------------------------------------------------------
768
1028
  # Extract information from Xtra MP4 atom
769
1029
  # Inputs: 0) ExifTool object ref, 1) dirInfo ref, 2) tag table ref
@@ -783,61 +1043,13 @@ sub ProcessXtra($$$)
783
1043
  last if $size < 8 or $pos + $size > $dataLen;
784
1044
  my $tagLen = Get32u($dataPt, $pos + 4);
785
1045
  last if $tagLen + 18 > $size;
786
- my $tag = substr($$dataPt, $pos + 8, $tagLen);
787
- # (version flags according to the reference, but looks more like a count - PH)
788
- my $count = Get32u($dataPt, $pos + $tagLen + 8);
789
- my ($i, $valPos, $valLen, $valType, $val, $format, @vals);
790
- # point to start of first value (after 4-byte length and 2-byte type)
791
- $valPos = $pos + $tagLen + 18;
792
- for ($i=0; ;) {
793
- # (stored value includes size of $valLen and $valType, so subtract 6)
794
- $valLen = Get32u($dataPt, $valPos - 6) - 6;
795
- my $more = $pos + $size - $valPos - $valLen;
796
- last if $more < 0;
797
- $valType = Get16u($dataPt, $valPos - 2);
798
- $val = substr($$dataPt, $valPos, $valLen);
799
- # Note: all dumb Microsoft values are little-endian inside a big-endian-format file
800
- SetByteOrder('II');
801
- if ($valType == 8) {
802
- $format = 'Unicode';
803
- $val = $et->Decode($val, 'UCS2');
804
- } elsif ($valType == 19 and $valLen == 8) {
805
- $format = 'int64u';
806
- $val = Get64u(\$val, 0);
807
- } elsif ($valType == 21 and $valLen == 8) {
808
- $format = 'date';
809
- $val = Get64u(\$val, 0);
810
- # convert time from 100 ns intervals since Jan 1, 1601
811
- $val = $val * 1e-7 - 11644473600 if $val;
812
- # (the Nikon S100 uses UTC timezone, same as ASF - PH)
813
- $val = Image::ExifTool::ConvertUnixTime($val) . 'Z';
814
- } elsif ($valType == 72 and $valLen == 16) {
815
- $format = 'GUID';
816
- $val = uc unpack('H*',pack('NnnNN',unpack('VvvNN',$val)));
817
- $val =~ s/(.{8})(.{4})(.{4})(.{4})/$1-$2-$3-$4-/;
818
- } elsif ($valType == 65 && $valLen > 4) { #PH (empirical)
819
- $format = 'variant';
820
- require Image::ExifTool::FlashPix;
821
- my $vPos = $valPos; # (necessary because ReadFPXValue updates this)
822
- # read entry as a VT_VARIANT (use FlashPix module for this)
823
- $val = Image::ExifTool::FlashPix::ReadFPXValue($et, $dataPt, $vPos,
824
- Image::ExifTool::FlashPix::VT_VARIANT(), $valPos+$valLen, 1);
825
- } else {
826
- $format = "Unknown($valType)";
827
- }
828
- SetByteOrder('MM'); # back to native QuickTime byte ordering
829
- last if ++$i >= $count or $more < 6;
830
- push @vals, $val;
831
- undef $val;
832
- $valPos += $valLen + 6; # step to next value
833
- }
834
- if (@vals) {
835
- push @vals, $val if defined $val;
836
- $val = \@vals;
837
- $valPos = $pos + $tagLen + 18;
838
- $valLen = $size - 18 - $tagLen;
839
- }
1046
+ my $valLen = $size - 8 - $tagLen;
840
1047
  if ($tagLen > 0 and $valLen > 0) {
1048
+ my $tag = substr($$dataPt, $pos + 8, $tagLen);
1049
+ my $valPos = $pos + 8 + $tagLen;
1050
+ my ($val, $format) = ReadXtraValue($et, substr($$dataPt, $valPos, $valLen));
1051
+ last unless defined $val;
1052
+ $val = $$val[0] if @$val == 1;
841
1053
  my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag);
842
1054
  unless ($tagInfo) {
843
1055
  # generate tag information for unrecognized tags
@@ -850,6 +1062,7 @@ sub ProcessXtra($$$)
850
1062
  $et->VPrint(0, $$et{INDENT}, "[adding Microsoft:$tag]\n");
851
1063
  }
852
1064
  }
1065
+ my $count = ref $val ? scalar @$val : 1;
853
1066
  $et->HandleTag($tagTablePtr, $tag, $val,
854
1067
  TagInfo => $tagInfo,
855
1068
  DataPt => $dataPt,
@@ -857,7 +1070,7 @@ sub ProcessXtra($$$)
857
1070
  Start => $valPos,
858
1071
  Size => $valLen,
859
1072
  Format => $format,
860
- Extra => " count=$count type=$valType",
1073
+ Extra => " count=$count",
861
1074
  );
862
1075
  }
863
1076
  $pos += $size; # step to next entry
@@ -880,7 +1093,8 @@ This module is used by Image::ExifTool
880
1093
  =head1 DESCRIPTION
881
1094
 
882
1095
  This module contains definitions required by Image::ExifTool to interpret
883
- Microsoft-specific EXIF and XMP tags.
1096
+ Microsoft-specific EXIF and XMP tags, and routines to read/write Microsoft
1097
+ Xtra tags in videos.
884
1098
 
885
1099
  =head1 AUTHOR
886
1100