#------------------------------------------------------------------------------ # File: Parrot.pm # # Description: Read timed metadata from Parrot drone videos # # Revisions: 2019-10-23 - P. Harvey Created # # References: 1) https://developer.parrot.com/docs/pdraw/metadata.html #------------------------------------------------------------------------------ package Image::ExifTool::Parrot; use strict; use vars qw($VERSION); $VERSION = '1.01'; sub Process_mett($$$); # tags found in Parrot 'mett' timed metadata (ref 1) %Image::ExifTool::Parrot::mett = ( PROCESS_PROC => \&Process_mett, # put the 'P' records first in the documentation VARS => { SORT_PROC => sub { my ($a,$b)=@_; $a=~s/P/A/; $b=~s/P/A/; $a cmp $b } }, NOTES => q{ Streaming metadata found in Parrot drone videos. See L for the specification. }, P1 => { Name => 'ParrotV1', SubDirectory => { TagTable => 'Image::ExifTool::Parrot::V1' }, }, P2 => { Name => 'ParrotV2', SubDirectory => { TagTable => 'Image::ExifTool::Parrot::V2' }, }, P3 => { Name => 'ParrotV3', SubDirectory => { TagTable => 'Image::ExifTool::Parrot::V3' }, }, E1 => { Name => 'ParrotTimeStamp', SubDirectory => { TagTable => 'Image::ExifTool::Parrot::TimeStamp' }, }, E2 => { Name => 'ParrotFollowMe', SubDirectory => { TagTable => 'Image::ExifTool::Parrot::FollowMe' }, }, E3 => { Name => 'ParrotAutomation', SubDirectory => { TagTable => 'Image::ExifTool::Parrot::Automation' }, }, ); # tags found in the Parrot 'mett' V1 timed metadata (ref 1) [untested] %Image::ExifTool::Parrot::V1 = ( PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, NOTES => 'Parrot version 1 streaming metadata.', GROUPS => { 2 => 'Location' }, 4 => { Name => 'DroneYaw', Format => 'int16s', ValueConv => '$val / 0x1000 * 180 / 3.14159', # convert from rad to deg }, 6 => { Name => 'DronePitch', Format => 'int16s', ValueConv => '$val / 0x1000 * 180 / 3.14159', }, 8 => { Name => 'DroneRoll', Format => 'int16s', ValueConv => '$val / 0x1000 * 180 / 3.14159', }, 10 => { Name => 'CameraPan', Format => 'int16s', ValueConv => '$val / 0x1000 * 180 / 3.14159', }, 12 => { Name => 'CameraTilt', Format => 'int16s', ValueConv => '$val / 0x1000 * 180 / 3.14159', }, 14 => { Name => 'FrameView', # (W,X,Y,Z) Format => 'int16s[4]', ValueConv => 'my @a = split " ",$val; $_ /= 0x1000 foreach @a; "@a"', }, 22 => { Name => 'ExposureTime', Groups => { 2 => 'Camera' }, Format => 'int16s', ValueConv => '$val / 0x100 / 1000', PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', }, 24 => { Name => 'ISO', Groups => { 2 => 'Camera' }, Format => 'int16s', }, 26 => { Name => 'WifiRSSI', Groups => { 2 => 'Device' }, Format => 'int8s', PrintConv => '"$val dBm"', }, 27 => { Name => 'Battery', Groups => { 2 => 'Device' }, PrintConv => '"$val %"', }, 28 => { Name => 'GPSLatitude', Format => 'int32s', ValueConv => '$val / 0x100000', PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")', }, 32 => { Name => 'GPSLongitude', Format => 'int32s', ValueConv => '$val / 0x100000', PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")', }, 36 => { Name => 'GPSAltitude', Format => 'int32s', Mask => 0xffffff00, ValueConv => '$val / 0x100', PrintConv => 'sprintf("%.3f m", $val)', }, 36.1 => { Name => 'GPSSatellites', # (SV count) Format => 'int32s', Mask => 0xff, }, 40 => { Name => 'AltitudeFromTakeOff', Format => 'int32s', ValueConv => '$val / 0x10000', PrintConv => 'sprintf("%.3f m", $val)', }, 44 => { Name => 'DistanceFromHome', Format => 'int32u', ValueConv => '$val / 0x10000', }, 48 => { Name => 'SpeedX', Format => 'int16s', ValueConv => '$val / 0x100', }, 50 => { Name => 'SpeedY', Format => 'int16s', ValueConv => '$val / 0x100', }, 52 => { Name => 'SpeedZ', Format => 'int16s', ValueConv => '$val / 0x100', }, 54 => { Name => 'Binning', Groups => { 2 => 'Device' }, Mask => 0x80, }, 54.1 => { Name => 'FlyingState', Groups => { 2 => 'Device' }, Mask => 0x7f, PrintConv => { 0 => 'Landed', 1 => 'Taking Off', 2 => 'Hovering', 3 => 'Flying', 4 => 'Landing', 5 => 'Emergency', }, }, 55 => { Name => 'Animation', Groups => { 2 => 'Device' }, Mask => 0x80, }, 55.1 => { Name => 'PilotingMode', Groups => { 2 => 'Device' }, Mask => 0x7f, PrintConv => { 0 => 'Manual', 1 => 'Return Home', 2 => 'Flight Plan', 3 => 'Follow Me', }, }, ); # tags found in the Parrot 'mett' V2 timed metadata (ref 1) [untested] %Image::ExifTool::Parrot::V2 = ( PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, Groups => { 2 => 'Location' }, NOTES => 'Parrot version 2 basic streaming metadata.', 4 => { Name => 'Elevation', Notes => 'estimated distance from ground', Format => 'int32s', ValueConv => '$val / 0x10000', PrintConv => 'sprintf("%.3f m", $val)', }, 8 => { Name => 'GPSLatitude', Format => 'int32s', ValueConv => '$val / 0x400000', PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")', }, 12 => { Name => 'GPSLongitude', Format => 'int32s', ValueConv => '$val / 0x400000', PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")', }, 16 => { Name => 'GPSAltitude', Format => 'int32s', Mask => 0xffffff00, ValueConv => '$val / 0x100', PrintConv => 'sprintf("%.3f m", $val)', }, 16.1 => { Name => 'GPSSatellites', # (SV count) Format => 'int32s', Mask => 0xff, }, 20 => { Name => 'GPSVelocityNorth', # (m/s) Format => 'int16s', ValueConv => '$val / 0x100', }, 22 => { Name => 'GPSVelocityEast', # (m/s) Format => 'int16s', ValueConv => '$val / 0x100', }, 24 => { Name => 'GPSVelocityDown', # (m/s) Format => 'int16s', ValueConv => '$val / 0x100', }, 26 => { Name => 'AirSpeed', # (m/s) Format => 'int16s', RawConv => '$val < 0 ? undef : $val', ValueConv => '$val / 0x100', }, 28 => { Name => 'DroneQuaternion', # (W,X,Y,Z) Format => 'int16s[4]', ValueConv => 'my @a = split " ",$val; $_ /= 0x4000 foreach @a; "@a"', }, 36 => { Name => 'FrameView', # (W,X,Y,Z) Format => 'int16s[4]', ValueConv => 'my @a = split " ",$val; $_ /= 0x4000 foreach @a; "@a"', }, 44 => { Name => 'CameraPan', Format => 'int16s', ValueConv => '$val / 0x1000 * 180 / 3.14159', # convert from rad to deg }, 46 => { Name => 'CameraTilt', Format => 'int16s', ValueConv => '$val / 0x1000 * 180 / 3.14159', }, 48 => { Name => 'ExposureTime', Groups => { 2 => 'Camera' }, Format => 'int16u', ValueConv => '$val / 0x100 / 1000', PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', }, 50 => { Name => 'ISO', Groups => { 2 => 'Camera' }, Format => 'int16u', }, 52 => { Name => 'Binning', Groups => { 2 => 'Device' }, Mask => 0x80, }, 52.1 => { Name => 'FlyingState', Groups => { 2 => 'Device' }, Mask => 0x7f, PrintConv => { 0 => 'Landed', 1 => 'Taking Off', 2 => 'Hovering', 3 => 'Flying', 4 => 'Landing', 5 => 'Emergency', 6 => 'User Takeoff', 7 => 'Motor Ramping', 8 => 'Emergency Landing', }, }, 53 => { Name => 'Animation', Groups => { 2 => 'Device' }, Mask => 0x80, }, 53.1 => { Name => 'PilotingMode', Groups => { 2 => 'Device' }, Mask => 0x7f, PrintConv => { 0 => 'Manual', 1 => 'Return Home', 2 => 'Flight Plan', 3 => 'Follow Me / Tracking', # (same as 'Tracking') 4 => 'Magic Carpet', 5 => 'Move To', }, }, 54 => { Name => 'WifiRSSI', Groups => { 2 => 'Device' }, Format => 'int8s', PrintConv => '"$val dBm"', }, 55 => { Name => 'Battery', Groups => { 2 => 'Device' }, PrintConv => '"$val %"', }, ); # tags found in the Parrot 'mett' V3 timed metadata (ref 1) %Image::ExifTool::Parrot::V3 = ( PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, GROUPS => { 2 => 'Location' }, NOTES => 'Parrot version 3 basic streaming metadata.', 4 => { Name => 'Elevation', Notes => 'estimated distance from ground', Format => 'int32s', ValueConv => '$val / 0x10000', PrintConv => 'sprintf("%.3f m", $val)', }, 8 => { Name => 'GPSLatitude', Format => 'int32s', ValueConv => '$val / 0x400000', PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "N")', }, 12 => { Name => 'GPSLongitude', Format => 'int32s', ValueConv => '$val / 0x400000', PrintConv => 'Image::ExifTool::GPS::ToDMS($self, $val, 1, "E")', }, 16 => { Name => 'GPSAltitude', Format => 'int32s', Mask => 0xffffff00, ValueConv => '$val / 0x100', PrintConv => 'sprintf("%.3f m", $val)', }, 16.1 => { Name => 'GPSSatellites', # (SV count) Format => 'int32s', Mask => 0xff, }, 20 => { Name => 'GPSVelocityNorth', # (m/s) Format => 'int16s', ValueConv => '$val / 0x100', }, 22 => { Name => 'GPSVelocityEast', # (m/s) Format => 'int16s', ValueConv => '$val / 0x100', }, 24 => { Name => 'GPSVelocityDown', # (m/s) Format => 'int16s', ValueConv => '$val / 0x100', }, 26 => { Name => 'AirSpeed', # (m/s) Format => 'int16s', RawConv => '$val < 0 ? undef : $val', ValueConv => '$val / 0x100', }, 28 => { Name => 'DroneQuaternion', # (W,X,Y,Z) Format => 'int16s[4]', ValueConv => 'my @a = split " ",$val; $_ /= 0x4000 foreach @a; "@a"', }, 36 => { Name => 'FrameBaseView', # (W,X,Y,Z without pan/tilt) Format => 'int16s[4]', ValueConv => 'my @a = split " ",$val; $_ /= 0x4000 foreach @a; "@a"', }, 44 => { Name => 'FrameView', # (W,X,Y,Z) Format => 'int16s[4]', ValueConv => 'my @a = split " ",$val; $_ /= 0x4000 foreach @a; "@a"', }, 52 => { Name => 'ExposureTime', Groups => { 2 => 'Camera' }, Format => 'int16u', ValueConv => '$val / 0x100 / 1000', PrintConv => 'Image::ExifTool::Exif::PrintExposureTime($val)', }, 54 => { Name => 'ISO', Groups => { 2 => 'Camera' }, Format => 'int16u', }, 56 => { Name => 'RedBalance', Groups => { 2 => 'Camera' }, Format => 'int16u', ValueConv => '$val / 0x4000', }, 58 => { Name => 'BlueBalance', Groups => { 2 => 'Camera' }, Format => 'int16u', ValueConv => '$val / 0x4000', }, 60 => { Name => 'FOV', # (degrees) Description => 'Field Of View', Notes => 'horizontal and vertical field of view in degrees', Groups => { 2 => 'Image' }, Format => 'int16u[2]', ValueConv => 'my @a = split " ",$val; $_ /= 0x100 foreach @a; "@a"', }, 64 => { Name => 'LinkGoodput', Groups => { 2 => 'Device' }, Format => 'int32u', Mask => 0xffffff00, PrintConv => '"$val kbit/s"', }, 64.1 => { Name => 'LinkQuality', Groups => { 2 => 'Device' }, Format => 'int32u', Notes => '0-5', Mask => 0xff, }, 68 => { Name => 'WifiRSSI', Groups => { 2 => 'Device' }, Format => 'int8s', PrintConv => '"$val dBm"', }, 69 => { Name => 'Battery', Groups => { 2 => 'Device' }, PrintConv => '"$val %"', }, 70 => { Name => 'Binning', Groups => { 2 => 'Device' }, Mask => 0x80, }, 70.1 => { Name => 'FlyingState', Groups => { 2 => 'Device' }, Mask => 0x7f, PrintConv => { 0 => 'Landed', 1 => 'Taking Off', 2 => 'Hovering', 3 => 'Flying', 4 => 'Landing', 5 => 'Emergency', 6 => 'User Takeoff', 7 => 'Motor Ramping', 8 => 'Emergency Landing', }, }, 71 => { Name => 'Animation', Groups => { 2 => 'Device' }, Mask => 0x80, }, 71.1 => { Name => 'PilotingMode', Groups => { 2 => 'Device' }, Mask => 0x7f, PrintConv => { 0 => 'Manual', 1 => 'Return Home', 2 => 'Flight Plan', 3 => 'Follow Me / Tracking', # (same as 'Tracking') 4 => 'Magic Carpet', 5 => 'Move To', }, }, ); # tags found in the Parrot 'mett' E1 timestamp timed metadata (ref 1) %Image::ExifTool::Parrot::TimeStamp = ( PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, NOTES => 'Parrot streaming metadata timestamp extension.', GROUPS => { 2 => 'Time' }, 4 => { Name => 'TimeStamp', Format => 'int64u', ValueConv => '$val / 1e6', }, ); # tags found in the Parrot 'mett' E2 follow-me timed metadata (ref 1) [untested] %Image::ExifTool::Parrot::FollowMe = ( PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, GROUPS => { 2 => 'Location' }, NOTES => 'Parrot streaming metadata follow-me extension.', 4 => { Name => 'GPSTargetLatitude', Format => 'int32s', ValueConv => '$val / 0x400000', }, 8 => { Name => 'GPSTargetLongitude', Format => 'int32s', ValueConv => '$val / 0x400000', }, 12 => { Name => 'GPSTargetAltitude', Format => 'int32s', ValueConv => '$val / 0x10000', }, 16 => { Name => 'Follow-meMode', Groups => { 2 => 'Device' }, PrintConv => { BITMASK => { 0 => 'Follow-me enabled', 1 => 'Follow-me', # (0=Look-at-me! auggh. see AutomationFlags below) 2 => 'Angle locked', }}, }, 17 => { Name => 'Follow-meAnimation', Groups => { 2 => 'Device' }, PrintConv => { 0 => 'None', 1 => 'Orbit', 2 => 'Boomerang', 3 => 'Parabola', 4 => 'Zenith', }, }, ); # tags found in the Parrot 'mett' E3 automation timed metadata (ref 1) %Image::ExifTool::Parrot::Automation = ( PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData, GROUPS => { 2 => 'Location' }, NOTES => 'Parrot streaming metadata automation extension.', 4 => { Name => 'GPSFramingLatitude', Format => 'int32s', ValueConv => '$val / 0x400000', }, 8 => { Name => 'GPSFramingLongitude', Format => 'int32s', ValueConv => '$val / 0x400000', }, 12 => { Name => 'GPSFramingAltitude', Format => 'int32s', ValueConv => '$val / 0x10000', }, 16 => { Name => 'GPSDestLatitude', Format => 'int32s', ValueConv => '$val / 0x400000', }, 20 => { Name => 'GPSDestLongitude', Format => 'int32s', ValueConv => '$val / 0x400000', }, 24 => { Name => 'GPSDestAltitude', Format => 'int32s', ValueConv => '$val / 0x10000', }, 28 => { Name => 'AutomationAnimation', Groups => { 2 => 'Device' }, PrintConv => { 0 => 'None', 1 => 'Orbit', 2 => 'Boomerang', 3 => 'Parabola', 4 => 'Dolly Slide', 5 => 'Dolly Zoom', 6 => 'Reveal Vertical', 7 => 'Reveal Horizontal', 8 => 'Candle', 9 => 'Flip Front', 10 => 'Flip Back', 11 => 'Flip Left', 12 => 'Flip Right', 13 => 'Twist Up', 14 => 'Position Twist Up', }, }, 29 => { Name => 'AutomationFlags', Groups => { 2 => 'Device' }, PrintConv => { BITMASK => { 0 => 'Follow-me enabled', 1 => 'Look-at-me enabled', # (really? opposite sense to Follow-meMode above!) 2 => 'Angle locked', }}, }, ); %Image::ExifTool::Parrot::Composite = ( GPSDateTime => { Description => 'GPS Date/Time', Groups => { 2 => 'Time' }, SubDoc => 1, Require => { 0 => 'Parrot:GPSLatitude', # (avoids creating this tag for other videos) 1 => 'Main:CreateDate', 2 => 'SampleTime', }, ValueConv => q{ my $time = $val[1]; my $diff = $val[2]; # handle time zone and shift to UTC if ($time =~ s/([-+])(\d{1,2}):?(\d{2})$//) { my $secs = (($2 * 60) + $3) * 60; $secs *= -1 if $1 eq '+'; $diff += $secs; } elsif ($time !~ s/Z$//) { # shift from local time $diff += GetUnixTime($time, 1) - GetUnixTime($time); } my $sign = ($diff =~ s/^-// ? '-' : ''); $time .= '.000'; # add decimal seconds ShiftTime($time, "${sign}0:0:$diff"); return $time . 'Z'; }, PrintConv => '$self->ConvertDateTime($val)', }, ); # add our composite tags Image::ExifTool::AddCompositeTags('Image::ExifTool::Parrot'); #------------------------------------------------------------------------------ # Parse Parrot 'mett' timed metadata # Inputs: 0) ExifTool ref, 1) dirInfo ref, 2) tag table ref # Returns: 1 on success # (this metadata design is really stupid -- you need to know the size of the base structures) sub Process_mett($$$) { my ($et, $dirInfo, $tagTbl) = @_; my $dataPt = $$dirInfo{DataPt}; my $dataPos = $$dirInfo{DataPos}; my $dirEnd = length $$dataPt; my $pos = $$dirInfo{DirStart} || 0; $et->VerboseDir('Parrot mett', undef, $dirEnd); while ($pos + 4 < $dirEnd) { my ($id, $nwords) = unpack("x${pos}a2n", $$dataPt); my $size; if ($id !~ /^[EP]\d/) { # no ID so this should be a 60-byte V1 recording record, otherwise give up last unless $dirEnd == 60; $id = 'P1'; # generate a fake ID # ignore the first 4 of the record so the fields will align with # the other V1 records (unfortunately, this means that we won't # decode the V1 recording frame timestamp, but oh well) $pos += 4; $size = $dirEnd - $pos; # must override size for P3 and P3 records since it includes the extensions (augh!) } elsif ($id eq 'P2') { $size = 56; } elsif ($id eq 'P3') { $size = 72; } else { $size = $nwords * 4 + 4; } last if $pos + $size > $dirEnd; $et->HandleTag($tagTbl, $id, undef, DataPt => $dataPt, DataPos => $dataPos, Base => $$dirInfo{Base}, Start => $pos, Size => $size, ); $pos += $size; } return 1; } 1; # end __END__ =head1 NAME Image::ExifTool::Parrot - Read timed metadata from Parrot drone videos =head1 SYNOPSIS This module is loaded automatically by Image::ExifTool when required. =head1 DESCRIPTION This module contains definitions required by Image::ExifTool to interpret timed metadata from the 'mett' frame found in Parrot drone MP4 videos. =head1 AUTHOR Copyright 2003-2021, Phil Harvey (philharvey66 at gmail.com) This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =head1 REFERENCES =over 4 =item L =back =head1 SEE ALSO L, L =cut