exiftool_vendored 13.12.0 → 13.14.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -12,7 +12,7 @@ meta information extracted from or written to a file.
12
12
  =head1 TAG TABLES
13
13
 
14
14
  The tables listed below give the names of all tags recognized by ExifTool.
15
- They contain a total of 28231 tags, with 17521 unique tag names.
15
+ They contain a total of 28258 tags, with 17524 unique tag names.
16
16
 
17
17
  B<Tag ID>, B<Index#> or B<Sequence> is given in the first column of each
18
18
  table. A B<Tag ID> is the computer-readable equivalent of a tag name, and
@@ -171,6 +171,7 @@ L<https://www.w3.org/Graphics/JPEG/jfif3.pdf> for the JPEG specification.
171
171
  PhotoMechanic PhotoMechanic
172
172
  MIE MIE
173
173
  Samsung Samsung Trailer
174
+ Vivo Vivo
174
175
  EmbeddedVideo no
175
176
  Insta360 no
176
177
  NikonApp no
@@ -12972,34 +12973,56 @@ Thermal parameters extracted from APP4 of some DJI RJPEG files.
12972
12973
 
12973
12974
  =head3 DJI Protobuf Tags
12974
12975
 
12975
- Tags found in protobuf-format DJI djmd and dbgi timed metadata. Only a few
12976
- tags are currently known, but unknown djmd tags may be extracted by setting
12977
- the Unknown option to 1 (or 2 to also extract unknown dbgi debug tags). Tag
12978
- ID's are composed of the corresponding .proto file name combined with the
12979
- hierarchical protobuf field numbers.
12976
+ Tags found in protobuf-format DJI djmd and dbgi timed metadata. The known
12977
+ tags listed below are extracted by default, but unknown djmd tags may be
12978
+ extracted as well by setting the Unknown option to 1, or 2 to also extract
12979
+ unknown dbgi debug tags. Tag ID's are composed of the corresponding .proto
12980
+ file name combined with the hierarchical protobuf field numbers.
12980
12981
 
12981
12982
  ExifTool currently extracts timed GPS plus a few other tags from DJI devices
12982
12983
  which use the following protocols: dvtm_AVATA2.proto (Avanta 2),
12983
- dvtm_ac203.proto (Osmo Action 4), and dvtm_ac204.proto (Osmo Action 5).
12984
+ dvtm_ac203.proto (Osmo Action 4), dvtm_ac204.proto (Osmo Action 5) and
12985
+ dvtm_wm265e.proto (Mavic 3).
12986
+
12987
+ Note that with the protobuf format, numerical tags missing from the output
12988
+ for a given protocol should be considered to have the default value of 0.
12984
12989
 
12985
12990
  Tag ID Tag Name Writable
12986
12991
  ------ -------- --------
12987
12992
  'Protocol' Protocol no
12993
+ 'dvtm_AVATA2_1-1-5' SerialNumber no
12988
12994
  'dvtm_AVATA2_1-1-10' Model no
12989
- 'dvtm_AVATA2_2-2-3-1' SerialNumber no
12995
+ 'dvtm_AVATA2_2-2-3-1' SerialNumber2 no
12990
12996
  'dvtm_AVATA2_2-3' FrameInfo DJI FrameInfo
12991
12997
  'dvtm_AVATA2_3-1-2' TimeStamp no
12992
12998
  'dvtm_AVATA2_3-4-4-1' GPSInfo DJI GPSInfo
12999
+ 'dvtm_ac203_1-1-5' SerialNumber no
12993
13000
  'dvtm_ac203_1-1-10' Model no
12994
13001
  'dvtm_ac203_2-3' FrameInfo DJI FrameInfo
12995
13002
  'dvtm_ac203_3-4-2-1' GPSInfo DJI GPSInfo
12996
13003
  'dvtm_ac203_3-4-2-2' GPSAltitude no
12997
13004
  'dvtm_ac203_3-4-2-6-1' GPSDateTime no
13005
+ 'dvtm_ac204_1-1-5' SerialNumber no
12998
13006
  'dvtm_ac204_1-1-10' Model no
12999
13007
  'dvtm_ac204_2-3' FrameInfo DJI FrameInfo
13000
13008
  'dvtm_ac204_3-4-2-1' GPSInfo DJI GPSInfo
13001
13009
  'dvtm_ac204_3-4-2-2' GPSAltitude no
13002
13010
  'dvtm_ac204_3-4-2-6-1' GPSDateTime no
13011
+ 'dvtm_wm265e_1-1-5' SerialNumber no
13012
+ 'dvtm_wm265e_1-1-10' Model no
13013
+ 'dvtm_wm265e_2-2' FrameInfo DJI FrameInfo
13014
+ 'dvtm_wm265e_3-2-2-1' ISO no
13015
+ 'dvtm_wm265e_3-2-3-1' ShutterSpeed no
13016
+ 'dvtm_wm265e_3-2-6-1' DigitalZoom no
13017
+ 'dvtm_wm265e_3-3-3-1' DroneRoll no
13018
+ 'dvtm_wm265e_3-3-3-2' DronePitch no
13019
+ 'dvtm_wm265e_3-3-3-3' DroneYaw no
13020
+ 'dvtm_wm265e_3-3-4-1' GPSInfo DJI GPSInfo
13021
+ 'dvtm_wm265e_3-3-4-2' AbsoluteAltitude no
13022
+ 'dvtm_wm265e_3-3-5-1' RelativeAltitude no
13023
+ 'dvtm_wm265e_3-4-3-1' GimbalPitch no
13024
+ 'dvtm_wm265e_3-4-3-2' GimbalRoll no
13025
+ 'dvtm_wm265e_3-4-3-3' GimbalYaw no
13003
13026
 
13004
13027
  =head3 DJI FrameInfo Tags
13005
13028
 
@@ -23679,6 +23702,16 @@ Tags written by the Sony Image Data Converter utility in ARW images.
23679
23702
  0xd100 VersionCreateDate string
23680
23703
  0xd101 VersionModifyDate string
23681
23704
 
23705
+ =head2 Vivo Tags
23706
+
23707
+ Proprietary information written by some Vivo phones.
23708
+
23709
+ Tag Name Writable
23710
+ -------- --------
23711
+ HDRImage no
23712
+ HiddenData no
23713
+ JSONInfo no
23714
+
23682
23715
  =head2 Unknown Tags
23683
23716
 
23684
23717
  The following tags are decoded in unsupported maker notes. Use the Unknown
@@ -29967,15 +30000,22 @@ appropriate table in the config file (see
29967
30000
  example.config in the full distribution for an
29968
30001
  example). Note that some tags with the same name but different ID's may
29969
30002
  exist in the same location, but the family 7 group names may be used to
29970
- differentiate these. ExifTool currently writes only top-level metadata in
29971
- QuickTime-based files; it extracts other track-specific and timed metadata,
29972
- but can not yet edit tags in these locations (with the exception of
29973
- track-level date/time tags).
30003
+ differentiate these.
29974
30004
 
29975
- Beware that the Keys tags are actually stored inside the ItemList in the
29976
- file, so deleting the ItemList group as a block (ie. C<-ItemList:all=>) also
29977
- deletes Keys tags. Instead, to preserve Keys tags the ItemList tags may be
29978
- deleted individually with C<-QuickTime:ItemList:all=>.
30005
+ ExifTool currently writes
30006
+ L<ItemList|Image::ExifTool::TagNames/QuickTime ItemList Tags> and
30007
+ L<UserData|Image::ExifTool::TagNames/QuickTime UserData Tags> only as
30008
+ top-level metadata, but select Keys tags are may be written to the audio or
30009
+ video track. See the
30010
+ L<AudioKeys|Image::ExifTool::TagNames/QuickTime AudioKeys Tags> and
30011
+ L<VideoKeys|Image::ExifTool::TagNames/QuickTime VideoKeys Tags> tags for
30012
+ more information.
30013
+
30014
+ Beware that the values of the Keys tags are actually stored inside an
30015
+ ItemList atom in the file, so deleting the ItemList group as a block (ie.
30016
+ C<-ItemList:all=>) also deletes Keys tags. Instead, to preserve Keys tags
30017
+ the ItemList tags may be deleted individually with
30018
+ C<-QuickTime:ItemList:all=>.
29979
30019
 
29980
30020
  Alternate language tags may be accessed for
29981
30021
  L<ItemList|Image::ExifTool::TagNames/QuickTime ItemList Tags> and
@@ -30073,7 +30113,7 @@ for the official QuickTime specification.
30073
30113
  The tags below are extracted from timed metadata in QuickTime and other
30074
30114
  formats of video files when the ExtractEmbedded option is used. Although
30075
30115
  most of these tags are combined into the single table below, ExifTool
30076
- currently reads 98 different types of timed GPS metadata from video files.
30116
+ currently reads 100 different types of timed GPS metadata from video files.
30077
30117
 
30078
30118
  Tag Name Writable
30079
30119
  -------- --------
@@ -30276,15 +30316,11 @@ changed via the config file.
30276
30316
  'artist' Artist yes
30277
30317
  'artwork' Artwork yes
30278
30318
  'author' Author yes
30279
- 'camera.framereadouttimeinmicroseconds'
30280
- FrameReadoutTime yes
30281
- 'camera.identifier' CameraIdentifier yes
30282
30319
  'collection.user' UserCollection yes
30283
30320
  'com.android.capture.fps' AndroidCaptureFPS float
30284
30321
  'com.android.manufacturer' AndroidMake yes
30285
30322
  'com.android.model' AndroidModel yes
30286
30323
  'com.android.version' AndroidVersion yes
30287
- 'com.apple.photos.captureMode' CaptureMode yes
30288
30324
  'com.xiaomi.hdr10' XiaomiHDR10 int32s
30289
30325
  'com.xiaomi.preview_video_cover'
30290
30326
  XiaomiPreviewVideoCover int32s
@@ -30353,6 +30389,35 @@ changed via the config file.
30353
30389
  'xiaomi.exifInfo.videoinfo' XiaomiExifInfo yes
30354
30390
  'year' Year yes
30355
30391
 
30392
+ =head3 QuickTime AudioKeys Tags
30393
+
30394
+ Keys tags written in the audio track by some Apple devices. These tags
30395
+ belong to the ExifTool AudioKeys family 1 gorup.
30396
+
30397
+ Tag ID Tag Name Writable
30398
+ ------ -------- --------
30399
+ 'player.movie.audio.balance' Balance yes
30400
+ 'player.movie.audio.bass' Bass yes
30401
+ 'player.movie.audio.gain' AudioGain yes
30402
+ 'player.movie.audio.mute' Mute int8u
30403
+ 'player.movie.audio.pitchshift' PitchShift yes
30404
+ 'player.movie.audio.treble' Treble yes
30405
+
30406
+ =head3 QuickTime VideoKeys Tags
30407
+
30408
+ Keys tags written in the video track. These tags belong to the ExifTool
30409
+ VideoKeys family 1 gorup.
30410
+
30411
+ Tag ID Tag Name Writable
30412
+ ------ -------- --------
30413
+ 'camera.focal_length.35mm_equivalent'
30414
+ FocalLengthIn35mmFormat yes
30415
+ 'camera.framereadouttimeinmicroseconds'
30416
+ FrameReadoutTime yes
30417
+ 'camera.identifier' CameraIdentifier yes
30418
+ 'camera.lens_model' LensModel yes
30419
+ 'com.apple.photos.captureMode' CaptureMode yes
30420
+
30356
30421
  =head3 QuickTime FaceInfo Tags
30357
30422
 
30358
30423
  Tag ID Tag Name Writable
@@ -30452,7 +30517,9 @@ Tags found in Pittasoft Blackvue dashcam "free" data.
30452
30517
  'ipro' ItemProtection? no
30453
30518
  'iprp' ItemProperties QuickTime ItemProp
30454
30519
  'iref' ItemReference QuickTime ItemRef
30455
- 'keys' Keys QuickTime Keys
30520
+ 'keys' AudioKeys QuickTime AudioKeys
30521
+ VideoKeys QuickTime VideoKeys
30522
+ Keys QuickTime Keys
30456
30523
  'pitm' PrimaryItemReference no
30457
30524
  'uuid' MetaVersion no
30458
30525
  UUID-Unknown? no
@@ -14,7 +14,7 @@ use strict;
14
14
  use vars qw($VERSION);
15
15
  use Image::ExifTool qw(:DataAccess :Utils);
16
16
 
17
- $VERSION = '1.06';
17
+ $VERSION = '1.07';
18
18
 
19
19
  sub ReadBencode($$$);
20
20
  sub ExtractTags($$$;$$@);
@@ -282,7 +282,7 @@ sub ProcessTorrent($$)
282
282
  my $dict = ReadBencode($et, $raf, \$buff);
283
283
  my $err = $$raf{BencodeError};
284
284
  $et->Warn("Bencode error: $err") if $err;
285
- if (ref $dict eq 'HASH' and ($$dict{announce} or $$dict{'created by'})) {
285
+ if (ref $dict eq 'HASH' and ($$dict{announce} or $$dict{'created by'} or $$dict{info})) {
286
286
  $et->SetFileType();
287
287
  my $tagTablePtr = GetTagTable('Image::ExifTool::Torrent::Main');
288
288
  ExtractTags($et, $dict, $tagTablePtr) and $success = 1;
@@ -0,0 +1,124 @@
1
+ #------------------------------------------------------------------------------
2
+ # File: Vivo.pm
3
+ #
4
+ # Description: Read trailer written by Vivo phones
5
+ #
6
+ # Revisions: 2025-01-13 - P. Harvey Created
7
+ #------------------------------------------------------------------------------
8
+
9
+ package Image::ExifTool::Vivo;
10
+
11
+ use strict;
12
+ use vars qw($VERSION);
13
+ use Image::ExifTool qw(:DataAccess :Utils);
14
+ use Image::ExifTool::XMP;
15
+
16
+ $VERSION = '1.00';
17
+
18
+ %Image::ExifTool::Vivo::Main = (
19
+ GROUPS => { 0 => 'Trailer', 1 => 'Vivo', 2 => 'Image' },
20
+ VARS => { NO_ID => 1 },
21
+ NOTES => 'Proprietary information written by some Vivo phones.',
22
+ # (don't know for sure what type of image this is, but it is in JPEG format)
23
+ HDRImage => {
24
+ Notes => 'highlights of HDR image',
25
+ Groups => { 2 => 'Preview' },
26
+ Binary => 1,
27
+ },
28
+ JSONInfo => { },
29
+ HiddenData => {
30
+ Notes => 'hidden in EXIF, not in trailer. This data is lost if the file is edited',
31
+ Groups => { 0 => 'EXIF' },
32
+ },
33
+ );
34
+
35
+ #------------------------------------------------------------------------------
36
+ # Process Vivo trailer
37
+ # Inputs: 0) ExifTool object reference, 1) dirInfo reference
38
+ # Returns: 1 on success, -1 if we must scan for the start of the trailer
39
+ # - takes Offset as positive offset from end of trailer to end of file,
40
+ # and returns DataPos and DirLen, and updates OutFile when writing
41
+ sub ProcessVivo($$)
42
+ {
43
+ my ($et, $dirInfo) = @_;
44
+ my $raf = $$dirInfo{RAF};
45
+ my $buff;
46
+
47
+ # return now unless we are at a position to scan for the trailer
48
+ # (must scan because the trailer footer doesn't indicate the trailer length)
49
+ return -1 unless $$dirInfo{ScanForTrailer};
50
+
51
+ my $pos = $raf->Tell();
52
+ $raf->Seek(0, 2) or return 0;
53
+ my $len = $raf->Tell() - $pos - $$dirInfo{Offset};
54
+ $raf->Seek($pos, 0) or return 0;
55
+ return 0 unless $len > 0 and $len < 1e7 and $raf->Read($buff, $len) == $len and
56
+ $buff =~ /\xff{4}\x1b\*9HWfu\x84\x93\xa2\xb1$/ and # validate footer
57
+ $buff =~ /(streamdata|vivo\{")/g; # find start
58
+ my $start = pos($buff) - length($1);
59
+ if ($start) {
60
+ $pos += $start;
61
+ $len -= $start;
62
+ $buff = substr($buff, $start);
63
+ }
64
+ # set trailer position and length
65
+ @$dirInfo{'DataPos','DirLen'} = ($pos, $len);
66
+
67
+ if ($$dirInfo{OutFile}) {
68
+ if ($$et{DEL_GROUP}{Vivo}) {
69
+ $et->VPrint(0, " Deleting Vivo trailer ($len bytes)\n");
70
+ ++$$et{CHANGED};
71
+ } else {
72
+ $et->VPrint(0, " Copying Vivo trailer ($len bytes)\n");
73
+ Write($$dirInfo{OutFile}, $buff);
74
+ }
75
+ } else {
76
+ $et->DumpTrailer($dirInfo) if $$et{OPTIONS}{Verbose} or $$et{HTML_DUMP};
77
+ my $tbl = GetTagTable('Image::ExifTool::Vivo::Main');
78
+ pos($buff) = 0; # rewind search to start of buffer
79
+ if ($buff =~ /^streamdata\xff\xd8\xff/ and $buff =~ /\xff\xd9stream(info|coun)/g) {
80
+ $et->HandleTag($tbl, HDRImage => substr($buff, 10, pos($buff)-20));
81
+ }
82
+ # continue looking for Vivo JSON data
83
+ if ($buff =~ /vivo\{"/g) {
84
+ my $jsonStart = pos($buff) - 2;
85
+ if ($buff =~ /\}\0/g) {
86
+ my $jsonLen = pos($buff) - 1 - $jsonStart;
87
+ $et->HandleTag($tbl, JSONInfo => substr($buff, $jsonStart, $jsonLen));
88
+ }
89
+ }
90
+ }
91
+ return 1;
92
+ }
93
+
94
+ 1; # end
95
+
96
+ __END__
97
+
98
+ =head1 NAME
99
+
100
+ Image::ExifTool::Vivo - Read trailer written by Vivo phones
101
+
102
+ =head1 SYNOPSIS
103
+
104
+ This module is used by Image::ExifTool
105
+
106
+ =head1 DESCRIPTION
107
+
108
+ This module contains definitions required by Image::ExifTool to read
109
+ metadata the trailer written by some Vivo phones.
110
+
111
+ =head1 AUTHOR
112
+
113
+ Copyright 2003-2025, Phil Harvey (philharvey66 at gmail.com)
114
+
115
+ This library is free software; you can redistribute it and/or modify it
116
+ under the same terms as Perl itself.
117
+
118
+ =head1 SEE ALSO
119
+
120
+ L<Image::ExifTool::TagNames/Vivo Tags>,
121
+ L<Image::ExifTool(3pm)|Image::ExifTool>
122
+
123
+ =cut
124
+
@@ -15,6 +15,8 @@ my %movMap = (
15
15
  QuickTime => 'ItemList', # (default location for QuickTime tags)
16
16
  ItemList => 'Meta', # MOV-Movie-UserData-Meta-ItemList
17
17
  Keys => 'Movie', # MOV-Movie-Meta-Keys !! (hack due to different Meta location)
18
+ AudioKeys => 'Track', # MOV-Movie-Track-Meta-Keys !!
19
+ VideoKeys => 'Track', # MOV-Movie-Track-Meta-Keys !!
18
20
  Meta => 'UserData',
19
21
  XMP => 'UserData', # MOV-Movie-UserData-XMP
20
22
  Microsoft => 'UserData', # MOV-Movie-UserData-Microsoft
@@ -29,6 +31,8 @@ my %mp4Map = (
29
31
  QuickTime => 'ItemList', # (default location for QuickTime tags)
30
32
  ItemList => 'Meta', # MOV-Movie-UserData-Meta-ItemList
31
33
  Keys => 'Movie', # MOV-Movie-Meta-Keys !! (hack due to different Meta location)
34
+ AudioKeys => 'Track', # MOV-Movie-Track-Meta-Keys !!
35
+ VideoKeys => 'Track', # MOV-Movie-Track-Meta-Keys !!
32
36
  Meta => 'UserData',
33
37
  UserData => 'Movie', # MOV-Movie-UserData
34
38
  Microsoft => 'UserData', # MOV-Movie-UserData-Microsoft
@@ -383,13 +387,14 @@ sub WriteKeys($$$)
383
387
  my $outfile = $$dirInfo{OutFile};
384
388
  my ($tag, %done, %remap, %info, %add, $i);
385
389
 
390
+ my $keysGrp = $avType{$$et{MediaType}} ? "$avType{$$et{MediaType}}Keys" : 'Keys';
386
391
  $dirLen < 8 and $et->Warn('Short Keys box'), $dirLen = 8, $$dataPt = "\0" x 8;
387
- if ($$et{DEL_GROUP}{Keys}) {
392
+ if ($$et{DEL_GROUP}{$keysGrp}) {
388
393
  $dirLen = 8; # delete all existing keys
389
394
  # deleted keys are identified by a zero entry in the Remap lookup
390
395
  my $n = Get32u($dataPt, 4);
391
396
  for ($i=1; $i<=$n; ++$i) { $remap{$i} = 0; }
392
- $et->VPrint(0, " [deleting $n Keys entr".($n==1 ? 'y' : 'ies')."]\n");
397
+ $et->VPrint(0, " [deleting $n $keysGrp entr".($n==1 ? 'y' : 'ies')."]\n");
393
398
  ++$$et{CHANGED};
394
399
  }
395
400
  my $pos = 8;
@@ -425,7 +430,7 @@ sub WriteKeys($$$)
425
430
  }
426
431
  unless ($dontDelete) {
427
432
  # delete this key
428
- $et->VPrint(1, "$$et{INDENT}\[deleting Keys entry $index '${tag}']\n");
433
+ $et->VPrint(1, "$$et{INDENT}\[deleting $keysGrp entry $index '${tag}']\n");
429
434
  $pos += $len;
430
435
  $remap{$index++} = 0;
431
436
  ++$$et{CHANGED};
@@ -455,7 +460,7 @@ sub WriteKeys($$$)
455
460
  # add new entry to 'keys' data
456
461
  my $val = $id =~ /^com\./ ? $id : "com.apple.quicktime.$id";
457
462
  $newData .= Set32u(8 + length($val)) . 'mdta' . $val;
458
- $et->VPrint(1, "$$et{INDENT}\[adding Keys entry $newIndex '${id}']\n");
463
+ $et->VPrint(1, "$$et{INDENT}\[adding $keysGrp entry $newIndex '${id}']\n");
459
464
  $add{$newIndex++} = $tagInfo;
460
465
  ++$$et{CHANGED};
461
466
  }
@@ -470,7 +475,7 @@ sub WriteKeys($$$)
470
475
  # Info - Keys tag information, based on old index value
471
476
  # Add - Keys items deleted, based on old index value
472
477
  # Num - Number of items in edited Keys box
473
- $$et{Keys} = { Remap => \%remap, Info => \%info, Add => \%add, Num => $num };
478
+ $$et{$keysGrp} = { Remap => \%remap, Info => \%info, Add => \%add, Num => $num };
474
479
 
475
480
  return $newData; # return updated Keys box
476
481
  }
@@ -883,7 +888,7 @@ sub WriteQuickTime($$$)
883
888
  $et or return 1; # allow dummy access to autoload this package
884
889
  my ($mdat, @mdat, @mdatEdit, $edit, $track, $outBuff, $co, $term, $delCount);
885
890
  my (%langTags, $canCreate, $delGrp, %boxPos, %didDir, $writeLast, $err, $atomCount);
886
- my ($tag, $lastTag, $lastPos, $errStr, $trailer, $buf2);
891
+ my ($tag, $lastTag, $lastPos, $errStr, $trailer, $buf2, $keysGrp, $keysPath);
887
892
  my $outfile = $$dirInfo{OutFile} || return 0;
888
893
  my $raf = $$dirInfo{RAF}; # (will be null for lower-level atoms)
889
894
  my $dataPt = $$dirInfo{DataPt}; # (will be null for top-level atoms)
@@ -917,15 +922,26 @@ sub WriteQuickTime($$$)
917
922
 
918
923
  $raf->Seek($dirStart, 1) if $dirStart; # skip header if it exists
919
924
 
925
+ if ($avType{$$et{MediaType}}) {
926
+ # (note: these won't be correct now if we haven't yet processed the Media box,
927
+ # but in this case they won't be needed until after we set them properly below)
928
+ ($keysGrp, $keysPath) = ("$avType{$$et{MediaType}}Keys", 'MOV-Movie-Track');
929
+ } else {
930
+ ($keysGrp, $keysPath) = ('Keys', 'MOV-Movie');
931
+ }
920
932
  my $curPath = join '-', @{$$et{PATH}};
921
933
  my ($dir, $writePath) = ($dirName, $dirName);
922
934
  $writePath = "$dir-$writePath" while defined($dir = $$et{DirMap}{$dir});
923
935
  # hack to create Keys directories if necessary (its containing Meta is in a different location)
924
- if ($$addDirs{Keys} and $curPath =~ /^MOV-Movie(-Meta)?$/) {
936
+ if (($$addDirs{Keys} and $curPath =~ /^MOV-Movie(-Meta)?$/)) {
925
937
  $createKeys = 1; # create new Keys directories
926
- } elsif ($curPath eq 'MOV-Movie-Meta-ItemList') {
938
+ } elsif (($$addDirs{AudioKeys} or $$addDirs{VideoKeys}) and $curPath =~ /^MOV-Movie-Track(-Meta)?$/) {
939
+ $createKeys = -1; # (must wait until MediaType is known)
940
+ } elsif (($curPath eq 'MOV-Movie-Meta-ItemList') or
941
+ ($curPath eq 'MOV-Movie-Track-Meta-ItemList' and $avType{$$et{MediaType}}))
942
+ {
927
943
  $createKeys = 2; # create new Keys tags
928
- my $keys = $$et{Keys};
944
+ my $keys = $$et{$keysGrp};
929
945
  if ($keys) {
930
946
  # add new tag entries for existing Keys tags, now that we know their ID's
931
947
  # - first make lookup to convert Keys tagInfo ref to index number
@@ -933,7 +949,7 @@ sub WriteQuickTime($$$)
933
949
  foreach $index (keys %{$$keys{Info}}) {
934
950
  $keysInfo{$$keys{Info}{$index}} = $index if $$keys{Remap}{$index};
935
951
  }
936
- my $keysTable = GetTagTable('Image::ExifTool::QuickTime::Keys');
952
+ my $keysTable = GetTagTable("Image::ExifTool::QuickTime::$keysGrp");
937
953
  my $newKeysTags = $et->GetNewTagInfoHash($keysTable);
938
954
  foreach (keys %$newKeysTags) {
939
955
  my $tagInfo = $$newKeysTags{$_};
@@ -962,7 +978,8 @@ sub WriteQuickTime($$$)
962
978
  }
963
979
  if ($curPath eq $writePath or $createKeys) {
964
980
  $canCreate = 1;
965
- $delGrp = $$et{DEL_GROUP}{$dirName};
981
+ # (must check the appropriate Keys delete flag if this is a Keys ItemList)
982
+ $delGrp = $$et{DEL_GROUP}{$createKeys ? $keysGrp : $dirName};
966
983
  }
967
984
  $atomCount = $$tagTablePtr{VARS}{ATOM_COUNT} if $$tagTablePtr{VARS};
968
985
 
@@ -1080,12 +1097,12 @@ sub WriteQuickTime($$$)
1080
1097
  last;
1081
1098
  }
1082
1099
  }
1083
- # save the handler type for this track
1084
- if ($tag eq 'hdlr' and length $buff >= 12) {
1085
- my $hdlr = substr($buff,8,4);
1086
- $$et{HandlerType} = $hdlr if $hdlr =~ /^(vide|soun)$/;
1100
+ # save the handler type of the track media
1101
+ if ($tag eq 'hdlr' and length $buff >= 12 and
1102
+ @{$$et{PATH}} and $$et{PATH}[-1] eq 'Media')
1103
+ {
1104
+ $$et{MediaType} = substr($buff,8,4);
1087
1105
  }
1088
-
1089
1106
  # if this atom stores offsets, save its location so we can fix up offsets later
1090
1107
  # (are there any other atoms that may store absolute file offsets?)
1091
1108
  if ($tag =~ /^(stco|co64|iloc|mfra|moof|sidx|saio|gps |CTBO|uuid)$/) {
@@ -1130,11 +1147,11 @@ sub WriteQuickTime($$$)
1130
1147
  &{$$tagInfo{WriteHook}}($buff,$et) if $tagInfo and $$tagInfo{WriteHook};
1131
1148
 
1132
1149
  # allow numerical tag ID's (ItemList entries defined by Keys)
1133
- if (not $tagInfo and $dirName eq 'ItemList' and $$et{Keys}) {
1150
+ if (not $tagInfo and $dirName eq 'ItemList' and $$et{$keysGrp}) {
1134
1151
  $keysIndex = unpack('N', $tag);
1135
- my $newIndex = $$et{Keys}{Remap}{$keysIndex};
1152
+ my $newIndex = $$et{$keysGrp}{Remap}{$keysIndex};
1136
1153
  if (defined $newIndex) {
1137
- $tagInfo = $$et{Keys}{Info}{$keysIndex};
1154
+ $tagInfo = $$et{$keysGrp}{Info}{$keysIndex};
1138
1155
  unless ($newIndex) {
1139
1156
  if ($tagInfo) {
1140
1157
  $et->VPrint(1," - Keys:$$tagInfo{Name}");
@@ -1174,7 +1191,7 @@ sub WriteQuickTime($$$)
1174
1191
  if ($subdir) { # process atoms in this container from a buffer in memory
1175
1192
 
1176
1193
  if ($tag eq 'trak') {
1177
- undef $$et{HandlerType}; # init handler type for this track
1194
+ $$et{MediaType} = ''; # init media type for this track
1178
1195
  delete $$et{AssumedDataRef};
1179
1196
  }
1180
1197
  my $subName = $$subdir{DirName} || $$tagInfo{Name};
@@ -1243,10 +1260,13 @@ sub WriteQuickTime($$$)
1243
1260
  $$et{CHANGED} = $oldChanged;
1244
1261
  undef $newData;
1245
1262
  }
1246
- if ($tag eq 'trak' and $$et{AssumedDataRef}) {
1247
- my $grp = $$et{CUR_WRITE_GROUP} || $dirName;
1248
- $et->Error("Can't locate data reference to update offsets for $grp");
1249
- delete $$et{AssumedDataRef};
1263
+ if ($tag eq 'trak') {
1264
+ $$et{MediaType} = ''; # reset media type at end of track
1265
+ if ($$et{AssumedDataRef}) {
1266
+ my $grp = $$et{CUR_WRITE_GROUP} || $dirName;
1267
+ $et->Error("Can't locate data reference to update offsets for $grp");
1268
+ delete $$et{AssumedDataRef};
1269
+ }
1250
1270
  }
1251
1271
  $$et{CUR_WRITE_GROUP} = $oldWriteGroup;
1252
1272
  SetByteOrder('MM');
@@ -1542,7 +1562,7 @@ sub WriteQuickTime($$$)
1542
1562
  }
1543
1563
  if ($msg) {
1544
1564
  # (allow empty sample description for non-audio/video handler types, eg. 'url ', 'meta')
1545
- if ($$et{HandlerType}) {
1565
+ if ($$et{MediaType}) {
1546
1566
  my $grp = $$et{CUR_WRITE_GROUP} || $parent;
1547
1567
  $et->Error("$msg for $grp");
1548
1568
  return $rtnErr;
@@ -1595,7 +1615,16 @@ sub WriteQuickTime($$$)
1595
1615
  }
1596
1616
  $et->VPrint(0, " [deleting $delCount $dirName tag".($delCount==1 ? '' : 's')."]\n") if $delCount;
1597
1617
 
1598
- $createKeys &= ~0x01 unless $$addDirs{Keys}; # (Keys may have been written)
1618
+ # can finally set necessary variables for creating Video/AudioKeys tags
1619
+ if ($createKeys < 0) {
1620
+ if ($avType{$$et{MediaType}}) {
1621
+ $createKeys = 1;
1622
+ ($keysGrp, $keysPath) = ("$avType{$$et{MediaType}}Keys", 'MOV-Movie-Track');
1623
+ } else {
1624
+ $canCreate = 0;
1625
+ }
1626
+ }
1627
+ $createKeys &= ~0x01 unless $$addDirs{$keysGrp}; # (Keys may have been written)
1599
1628
 
1600
1629
  # add new directories/tags at this level if necessary
1601
1630
  if ($canCreate and (exists $$et{EDIT_DIRS}{$dirName} or $createKeys)) {
@@ -1606,13 +1635,13 @@ sub WriteQuickTime($$$)
1606
1635
  my ($tag, $index);
1607
1636
  # add Keys tags if necessary
1608
1637
  if ($createKeys) {
1609
- if ($curPath eq 'MOV-Movie') {
1638
+ if ($curPath eq $keysPath) {
1610
1639
  # add Meta for Keys if necessary
1611
1640
  unless ($didDir{meta}) {
1612
1641
  $$dirs{meta} = $Image::ExifTool::QuickTime::Movie{meta};
1613
1642
  push @addTags, 'meta';
1614
1643
  }
1615
- } elsif ($curPath eq 'MOV-Movie-Meta') {
1644
+ } elsif ($curPath eq "$keysPath-Meta") {
1616
1645
  # special case for Keys Meta -- reset directories and start again
1617
1646
  undef @addTags;
1618
1647
  $dirs = { };
@@ -1621,10 +1650,10 @@ sub WriteQuickTime($$$)
1621
1650
  $$dirs{$_} = $Image::ExifTool::QuickTime::Meta{$_};
1622
1651
  push @addTags, $_;
1623
1652
  }
1624
- } elsif ($curPath eq 'MOV-Movie-Meta-ItemList' and $$et{Keys}) {
1625
- foreach $index (sort { $a <=> $b } keys %{$$et{Keys}{Add}}) {
1653
+ } elsif ($curPath eq "$keysPath-Meta-ItemList" and $$et{$keysGrp}) {
1654
+ foreach $index (sort { $a <=> $b } keys %{$$et{$keysGrp}{Add}}) {
1626
1655
  my $id = Set32u($index);
1627
- $$newTags{$id} = $$et{Keys}{Add}{$index};
1656
+ $$newTags{$id} = $$et{$keysGrp}{Add}{$index};
1628
1657
  push @addTags, $id;
1629
1658
  }
1630
1659
  } else {
@@ -1636,8 +1665,7 @@ sub WriteQuickTime($$$)
1636
1665
  foreach $tag (@addTags) {
1637
1666
  my $tagInfo = $$dirs{$tag} || $$newTags{$tag};
1638
1667
  next if defined $$tagInfo{CanCreate} and not $$tagInfo{CanCreate};
1639
- next if defined $$tagInfo{HandlerType} and
1640
- (not $$et{HandlerType} or $$et{HandlerType} ne $$tagInfo{HandlerType});
1668
+ next if defined $$tagInfo{MediaType} and $$et{MediaType} ne $$tagInfo{MediaType};
1641
1669
  my $subdir = $$tagInfo{SubDirectory};
1642
1670
  unless ($subdir) {
1643
1671
  my $nvHash = $et->GetNewValueHash($tagInfo);
@@ -1699,13 +1727,13 @@ sub WriteQuickTime($$$)
1699
1727
  }
1700
1728
  my $subName = $$subdir{DirName} || $$tagInfo{Name};
1701
1729
  # QuickTime hierarchy is complex, so check full directory path before adding
1702
- if ($createKeys and $curPath eq 'MOV-Movie' and $subName eq 'Meta') {
1730
+ if ($createKeys and $curPath eq $keysPath and $subName eq 'Meta') {
1703
1731
  $et->VPrint(0, " Creating Meta with mdta Handler and Keys\n");
1704
1732
  # init Meta box for Keys tags with mdta Handler and empty Keys+ItemList
1705
1733
  $buf2 = "\0\0\0\x20hdlr\0\0\0\0\0\0\0\0mdta\0\0\0\0\0\0\0\0\0\0\0\0" .
1706
1734
  "\0\0\0\x10keys\0\0\0\0\0\0\0\0" .
1707
1735
  "\0\0\0\x08ilst";
1708
- } elsif ($createKeys and $curPath eq 'MOV-Movie-Meta') {
1736
+ } elsif ($createKeys and $curPath eq "$keysPath-Meta") {
1709
1737
  $buf2 = ($subName eq 'Keys' ? "\0\0\0\0\0\0\0\0" : '');
1710
1738
  } elsif ($subName eq 'Meta' and $$et{OPTIONS}{QuickTimeHandler}) {
1711
1739
  $et->VPrint(0, " Creating Meta with mdir Handler\n");
@@ -1754,8 +1782,8 @@ sub WriteQuickTime($$$)
1754
1782
  }
1755
1783
  }
1756
1784
  # add only once (must delete _after_ call to WriteDirectory())
1757
- # (Keys is a special case, and will be removed after Meta is processed)
1758
- delete $$addDirs{$subName} unless $subName eq 'Keys';
1785
+ # (Keys tags are a special case, and are handled separately)
1786
+ delete $$addDirs{$subName} unless $createKeys;
1759
1787
  }
1760
1788
  }
1761
1789
  # write HEIC metadata after top-level 'meta' box has been processed if editing this information
@@ -1781,9 +1809,9 @@ sub WriteQuickTime($$$)
1781
1809
  # (could report a file if editing nothing when it contained an empty Meta atom)
1782
1810
  # ++$$et{CHANGED};
1783
1811
  }
1784
- if ($curPath eq 'MOV-Movie-Meta') {
1785
- delete $$addDirs{Keys}; # prevent creation of another Meta for Keys tags
1786
- delete $$et{Keys};
1812
+ if ($curPath eq "$keysPath-Meta") {
1813
+ delete $$addDirs{$keysGrp}; # prevent creation of another Meta for Keys tags
1814
+ delete $$et{$keysGrp};
1787
1815
  }
1788
1816
  }
1789
1817
 
@@ -2111,6 +2139,7 @@ sub WriteMOV($$)
2111
2139
  $raf->Seek(0,0);
2112
2140
 
2113
2141
  # write the file
2142
+ $$et{MediaType} = '';
2114
2143
  $$dirInfo{Parent} = '';
2115
2144
  $$dirInfo{DirName} = 'MOV';
2116
2145
  $$dirInfo{ChunkOffset} = [ ]; # (just to be safe)