diff --git a/.gitignore b/.gitignore
index 878da69..9a51739 100755
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,4 @@ phpunit.xml
 tests/log
 vendor
 composer.phar
+.DS_Store
diff --git a/.travis.yml b/.travis.yml
index b098a73..9d6527d 100755
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,12 +1,9 @@
 language: php
 
 php:
-  - 5.4
-  - 5.5
-  - 5.6
-  - 7.0
-  - 7.1
   - 7.2
+  - 7.3
+  - 7.4snapshot
   - nightly
 
 env:
@@ -17,6 +14,7 @@ env:
 matrix:
   allow_failures:
     - php: nightly
+    - php: 7.4snapshot
     - env: DEPENDENCIES=beta
     - env: DEPENDENCIES=low
 
@@ -27,21 +25,21 @@ script:
   - composer run-tests
 
 before_script:
-  - if [ "$DEPENDENCIES" = "low" ]; then composer --prefer-lowest --prefer-stable update; else composer update; fi;
+  - if [ "$DEPENDENCIES" = "low" ]; then composer -vvv --prefer-lowest --prefer-stable update; else composer update; fi;
 
 after_script:
-  - php vendor/bin/coveralls -v 
+  - php vendor/bin/php-coveralls -v
 
 before_install:
+  - sudo apt-get update
+  - sudo apt-get install ffmpeg
   - composer self-update
-  - wget http://www.sno.phy.queensu.ca/~phil/exiftool/Image-ExifTool-9.90.tar.gz
-  - tar -zxvf Image-ExifTool-9.90.tar.gz
-  - cd Image-ExifTool-9.90 && perl Makefile.PL && make test && sudo make install
-  - cd .. && rm -rf Image-ExifTool-9.90
+  - wget http://www.sno.phy.queensu.ca/~phil/exiftool/Image-ExifTool-11.77.tar.gz
+  - tar -zxvf Image-ExifTool-11.77.tar.gz
+  - cd Image-ExifTool-11.77 && perl Makefile.PL && make test && sudo make install
+  - cd .. && rm -rf Image-ExifTool-11.77
   # Set composer minimum-stability configuration filter to beta versions
   - if [ "$DEPENDENCIES" = "beta" ]; then perl -pi -e 's/^}$/,"minimum-stability":"beta"}/' composer.json; fi;
-  # Disable xdebug, there is no use of it being enabled
-  - mv ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini{,.disabled} || echo "xdebug not available"
   # Prevent Travis throwing an out of memory error
   - echo "memory_limit=-1" >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini
   # Test if we have a token for github in case the project hits the 60 rph limit
diff --git a/composer.json b/composer.json
index a10b837..16eb9cc 100755
--- a/composer.json
+++ b/composer.json
@@ -10,21 +10,24 @@
             "role": "Developer"
         }
     ],
-    "keywords": ["EXIF", "IPTC", "jpeg", "tiff", "exiftool"],
+    "keywords": ["EXIF", "IPTC", "jpeg", "tiff", "exiftool", "FFmpeg", "FFprobe"],
     "require": {
-        "php": ">=5.4"
+        "php": ">=7.1",
+        "php-ffmpeg/php-ffmpeg": "^0.14.0"
     },
     "require-dev": {
         "jakub-onderka/php-parallel-lint": "^1.0",
-        "phpmd/phpmd": "~2.2",
-        "phpunit/phpunit": ">=4.0 <6.0",
-        "satooshi/php-coveralls": "~0.6",
-        "sebastian/phpcpd": "1.4.*@stable",
-        "squizlabs/php_codesniffer": "1.4.*@stable"
+        "php-coveralls/php-coveralls": "^2.2",
+        "phpmd/phpmd": "^2.7",
+        "phpunit/phpunit": ">=8.4",
+        "sebastian/phpcpd": "^4.1",
+        "friendsofphp/php-cs-fixer": "^2.16",
+	"squizlabs/php_codesniffer": "^3.5"
     },
     "suggest": {
         "lib-exiftool": "Use perl lib exiftool as adapter",
-        "ext-exif": "Use exif PHP extension as adapter"
+        "ext-exif": "Use exif PHP extension as adapter",
+	"FFmpeg": "Use FFmpeg/FFprobe as adapter"
     },
     "autoload": {
         "psr-0": {
diff --git a/lib/PHPExif/Adapter/FFprobe.php b/lib/PHPExif/Adapter/FFprobe.php
new file mode 100644
index 0000000..246ac84
--- /dev/null
+++ b/lib/PHPExif/Adapter/FFprobe.php
@@ -0,0 +1,123 @@
+<?php
+/**
+ * PHP Exif Native Reader Adapter
+ *
+ * @link        http://github.com/miljar/PHPExif for the canonical source repository
+ * @copyright   Copyright (c) 2013 Tom Van Herreweghe <tom@theanalogguy.be>
+ * @license     http://github.com/miljar/PHPExif/blob/master/LICENSE MIT License
+ * @category    PHPExif
+ * @package     Reader
+ */
+
+namespace PHPExif\Adapter;
+
+use PHPExif\Exif;
+use InvalidArgumentException;
+use FFMpeg;
+
+/**
+ * PHP Exif Native Reader Adapter
+ *
+ * Uses native PHP functionality to read data from a file
+ *
+ * @category    PHPExif
+ * @package     Reader
+ */
+class FFprobe extends AdapterAbstract
+{
+
+    const TOOL_NAME = 'ffprobe';
+
+    /**
+     * Path to the exiftool binary
+     *
+     * @var string
+     */
+    protected $toolPath;
+
+    /**
+     * @var string
+     */
+    protected $mapperClass = '\\PHPExif\\Mapper\\FFprobe';
+
+
+    /**
+     * Setter for the exiftool binary path
+     *
+     * @param string $path The path to the exiftool binary
+     * @return \PHPExif\Adapter\FFprobe Current instance
+     * @throws \InvalidArgumentException When path is invalid
+     */
+    public function setToolPath($path)
+    {
+        if (!file_exists($path)) {
+            throw new InvalidArgumentException(
+                sprintf(
+                    'Given path (%1$s) to the ffprobe binary is invalid',
+                    $path
+                )
+            );
+        }
+
+        $this->toolPath = $path;
+
+        return $this;
+    }
+
+
+
+    /**
+     * Getter for the ffprobe binary path
+     * Lazy loads the "default" path
+     *
+     * @return string
+     */
+    public function getToolPath()
+    {
+        if (empty($this->toolPath)) {
+            $path = exec('which ' . self::TOOL_NAME);
+            $this->setToolPath($path);
+        }
+
+        return $this->toolPath;
+    }
+
+    /**
+     * Reads & parses the EXIF data from given file
+     *
+     * @param string $file
+     * @return \PHPExif\Exif|boolean Instance of Exif object with data
+     */
+    public function getExifFromFile($file)
+    {
+        $mimeType = mime_content_type($file);
+
+        // file is not a video -> wrong adapter
+        if (strpos($mimeType, 'video') !== 0) {
+            return false;
+        }
+
+        $ffprobe = FFMpeg\FFProbe::create(array(
+                 'ffprobe.binaries' => $this->getToolPath(),
+             ));
+
+
+        $stream = $ffprobe->streams($file)->videos()->first()->all();
+        $format = $ffprobe->format($file)->all();
+
+        $data = array_merge($stream, $format, array('MimeType' => $mimeType, 'filesize' => filesize($file)));
+
+
+        // map the data:
+        $mapper = $this->getMapper();
+        $mappedData = $mapper->mapRawData($data);
+
+        // hydrate a new Exif object
+        $exif = new Exif();
+        $hydrator = $this->getHydrator();
+        $hydrator->hydrate($exif, $mappedData);
+        $exif->setRawData($data);
+
+        return $exif;
+    }
+}
diff --git a/lib/PHPExif/Adapter/Native.php b/lib/PHPExif/Adapter/Native.php
index 1bf3185..076049d 100644
--- a/lib/PHPExif/Adapter/Native.php
+++ b/lib/PHPExif/Adapter/Native.php
@@ -12,6 +12,7 @@
 namespace PHPExif\Adapter;
 
 use PHPExif\Exif;
+use FFMpeg;
 
 /**
  * PHP Exif Native Reader Adapter
@@ -70,14 +71,18 @@ class Native extends AdapterAbstract
      * @var array
      */
     protected $iptcMapping = array(
-        'title'     => '2#005',
-        'keywords'  => '2#025',
-        'copyright' => '2#116',
-        'caption'   => '2#120',
-        'headline'  => '2#105',
-        'credit'    => '2#110',
-        'source'    => '2#115',
-        'jobtitle'  => '2#085'
+        'title'       => '2#005',
+        'keywords'    => '2#025',
+        'copyright'   => '2#116',
+        'caption'     => '2#120',
+        'headline'    => '2#105',
+        'credit'      => '2#110',
+        'source'      => '2#115',
+        'jobtitle'    => '2#085',
+        'city'        => '2#090',
+        'sublocation' => '2#092',
+        'state'       => '2#095',
+        'country'     => '2#101'
     );
 
 
@@ -173,6 +178,11 @@ public function getSectionsAsArrays()
      */
     public function getExifFromFile($file)
     {
+        $mimeType = mime_content_type($file);
+
+
+
+        // Photo
         $sections   = $this->getRequiredSections();
         $sections   = implode(',', $sections);
         $sections   = (empty($sections)) ? null : $sections;
@@ -191,6 +201,7 @@ public function getExifFromFile($file)
         $xmpData = $this->getIptcData($file);
         $data = array_merge($data, array(self::SECTION_IPTC => $xmpData));
 
+
         // map the data:
         $mapper = $this->getMapper();
         $mappedData = $mapper->mapRawData($data);
@@ -204,6 +215,7 @@ public function getExifFromFile($file)
         return $exif;
     }
 
+
     /**
      * Returns an array of IPTC data
      *
diff --git a/lib/PHPExif/Exif.php b/lib/PHPExif/Exif.php
index e6614e4..bf4469c 100755
--- a/lib/PHPExif/Exif.php
+++ b/lib/PHPExif/Exif.php
@@ -22,32 +22,50 @@
  */
 class Exif
 {
+    const ALTITUDE              = 'altitude';
     const APERTURE              = 'aperture';
     const AUTHOR                = 'author';
     const CAMERA                = 'camera';
     const CAPTION               = 'caption';
+    const CITY                  = 'city';
     const COLORSPACE            = 'ColorSpace';
+    const CONTENTIDENTIFIER     = 'contentIdentifier';
     const COPYRIGHT             = 'copyright';
+    const COUNTRY               = 'country';
     const CREATION_DATE         = 'creationdate';
     const CREDIT                = 'credit';
+    const DESCRIPTION           = 'description';
+    const DURATION              = 'duration';
     const EXPOSURE              = 'exposure';
     const FILESIZE              = 'FileSize';
+    const FILENAME              = 'FileName';
     const FOCAL_LENGTH          = 'focalLength';
     const FOCAL_DISTANCE        = 'focalDistance';
+    const FRAMERATE             = 'framerate';
+    const GPS                   = 'gps';
     const HEADLINE              = 'headline';
     const HEIGHT                = 'height';
     const HORIZONTAL_RESOLUTION = 'horizontalResolution';
+    const IMGDIRECTION          = 'imgDirection';
     const ISO                   = 'iso';
     const JOB_TITLE             = 'jobTitle';
     const KEYWORDS              = 'keywords';
+    const LATITUDE              = 'latitude';
+    const LONGITUDE             = 'longitude';
+    const LENS                  = 'lens';
+    const MAKE                  = 'make';
+    const MICROVIDEOOFFSET      = 'MicroVideoOffset';
     const MIMETYPE              = 'MimeType';
     const ORIENTATION           = 'Orientation';
     const SOFTWARE              = 'software';
     const SOURCE                = 'source';
+    const STATE                 = 'state';
+    const SUBLOCATION           = 'Sublocation';
     const TITLE                 = 'title';
     const VERTICAL_RESOLUTION   = 'verticalResolution';
     const WIDTH                 = 'width';
-    const GPS                   = 'gps';
+
+
 
     /**
      * The mapped EXIF data
@@ -705,7 +723,7 @@ public function setCreationDate(\DateTime $value)
 
         return $this;
     }
-    
+
     /**
      * Returns the colorspace, if it exists
      *
@@ -716,7 +734,7 @@ public function getColorSpace()
         if (!isset($this->data[self::COLORSPACE])) {
             return false;
         }
-        
+
         return $this->data[self::COLORSPACE];
     }
 
@@ -732,7 +750,7 @@ public function setColorSpace($value)
 
         return $this;
     }
-    
+
     /**
      * Returns the mimetype, if it exists
      *
@@ -743,7 +761,7 @@ public function getMimeType()
         if (!isset($this->data[self::MIMETYPE])) {
             return false;
         }
-        
+
         return $this->data[self::MIMETYPE];
     }
 
@@ -759,10 +777,10 @@ public function setMimeType($value)
 
         return $this;
     }
-    
+
     /**
      * Returns the filesize, if it exists
-     * 
+     *
      * @return int|boolean
      */
     public function getFileSize()
@@ -770,7 +788,7 @@ public function getFileSize()
         if (!isset($this->data[self::FILESIZE])) {
             return false;
         }
-        
+
         return $this->data[self::FILESIZE];
     }
 
@@ -787,6 +805,33 @@ public function setFileSize($value)
         return $this;
     }
 
+    /**
+     * Returns the filename, if it exists
+     *
+     * @return string|boolean
+     */
+    public function getFileName()
+    {
+        if (!isset($this->data[self::FILENAME])) {
+            return false;
+        }
+
+        return $this->data[self::FILENAME];
+    }
+
+    /**
+     * Sets the filename
+     *
+     * @param string $value
+     * @return \PHPExif\Exif
+     */
+    public function setFileName($value)
+    {
+        $this->data[self::FILENAME] = $value;
+
+        return $this;
+    }
+
     /**
      * Returns the orientation, if it exists
      *
@@ -840,4 +885,412 @@ public function setGPS($value)
 
         return $this;
     }
+
+    /**
+     * Sets the description value
+     *
+     * @param string $value
+     * @return \PHPExif\Exif
+     */
+    public function setDescription($value)
+    {
+        $this->data[self::DESCRIPTION] = $value;
+
+        return $this;
+    }
+
+    /**
+     * Returns description, if it exists
+     *
+     * @return string|boolean
+     */
+    public function getDescription()
+    {
+        if (!isset($this->data[self::DESCRIPTION])) {
+            return false;
+        }
+
+        return $this->data[self::DESCRIPTION];
+    }
+
+
+    /**
+     * Sets the Make value
+     *
+     * @param string $value
+     * @return \PHPExif\Exif
+     */
+    public function setMake($value)
+    {
+        $this->data[self::MAKE] = $value;
+
+        return $this;
+    }
+
+    /**
+     * Returns make, if it exists
+     *
+     * @return string|boolean
+     */
+    public function getMake()
+    {
+        if (!isset($this->data[self::MAKE])) {
+            return false;
+        }
+
+        return $this->data[self::MAKE];
+    }
+
+    /**
+     * Sets the altitude value
+     *
+     * @param string $value
+     * @return \PHPExif\Exif
+     */
+    public function setAltitude($value)
+    {
+        $this->data[self::ALTITUDE] = $value;
+
+        return $this;
+    }
+
+    /**
+     * Returns altitude, if it exists
+     *
+     * @return float|boolean
+     */
+    public function getAltitude()
+    {
+        if (!isset($this->data[self::ALTITUDE])) {
+            return false;
+        }
+
+        return $this->data[self::ALTITUDE];
+    }
+
+    /**
+     * Sets the altitude value
+     *
+     * @param string $value
+     * @return \PHPExif\Exif
+     */
+    public function setLongitude($value)
+    {
+        $this->data[self::LONGITUDE] = $value;
+
+        return $this;
+    }
+
+    /**
+     * Returns altitude, if it exists
+     *
+     * @return float|boolean
+     */
+    public function getLongitude()
+    {
+        if (!isset($this->data[self::LONGITUDE])) {
+            return false;
+        }
+
+        return $this->data[self::LONGITUDE];
+    }
+
+    /**
+     * Sets the latitude value
+     *
+     * @param string $value
+     * @return \PHPExif\Exif
+     */
+    public function setLatitude($value)
+    {
+        $this->data[self::LATITUDE] = $value;
+
+        return $this;
+    }
+
+    /**
+     * Returns latitude, if it exists
+     *
+     * @return float|boolean
+     */
+    public function getLatitude()
+    {
+        if (!isset($this->data[self::LATITUDE])) {
+            return false;
+        }
+
+        return $this->data[self::LATITUDE];
+    }
+
+    /**
+     * Sets the imgDirection value
+     *
+     * @param string $value
+     * @return \PHPExif\Exif
+     */
+    public function setImgDirection($value)
+    {
+        $this->data[self::IMGDIRECTION] = $value;
+
+        return $this;
+    }
+
+    /**
+     * Returns imgDirection, if it exists
+     *
+     * @return float|boolean
+     */
+    public function getImgDirection()
+    {
+        if (!isset($this->data[self::IMGDIRECTION])) {
+            return false;
+        }
+
+        return $this->data[self::IMGDIRECTION];
+    }
+
+
+    /**
+     * Sets the Make value
+     *
+     * @param string $value
+     * @return \PHPExif\Exif
+     */
+    public function setLens($value)
+    {
+        $this->data[self::LENS] = $value;
+
+        return $this;
+    }
+
+    /**
+     * Returns make, if it exists
+     *
+     * @return string|boolean
+     */
+    public function getLens()
+    {
+        if (!isset($this->data[self::LENS])) {
+            return false;
+        }
+
+        return $this->data[self::LENS];
+    }
+
+    /**
+     * Sets the content identifier value
+     *
+     * @param string $value
+     * @return \PHPExif\Exif
+     */
+    public function setContentIdentifier($value)
+    {
+        $this->data[self::CONTENTIDENTIFIER] = $value;
+
+        return $this;
+    }
+
+    /**
+     * Returns content identifier, if it exists
+     *
+     * @return string|boolean
+     */
+    public function getContentIdentifier()
+    {
+        if (!isset($this->data[self::CONTENTIDENTIFIER])) {
+            return false;
+        }
+
+        return $this->data[self::CONTENTIDENTIFIER];
+    }
+
+
+    /**
+     * Sets the framerate value
+     *
+     * @param string $value
+     * @return \PHPExif\Exif
+     */
+    public function setFramerate($value)
+    {
+        $this->data[self::FRAMERATE] = $value;
+
+        return $this;
+    }
+
+    /**
+     * Returns content identifier, if it exists
+     *
+     * @return string|boolean
+     */
+    public function getFramerate()
+    {
+        if (!isset($this->data[self::FRAMERATE])) {
+            return false;
+        }
+
+        return $this->data[self::FRAMERATE];
+    }
+
+
+    /**
+     * Sets the duration value
+     *
+     * @param string $value
+     * @return \PHPExif\Exif
+     */
+    public function setDuration($value)
+    {
+        $this->data[self::DURATION] = $value;
+
+        return $this;
+    }
+
+    /**
+     * Returns duration, if it exists
+     *
+     * @return string|boolean
+     */
+    public function getDuration()
+    {
+        if (!isset($this->data[self::DURATION])) {
+            return false;
+        }
+        return $this->data[self::DURATION];
+    }
+
+    /**
+     * Sets the duration value
+     *
+     * @param string $value
+     * @return \PHPExif\Exif
+     */
+    public function setMicroVideoOffset($value)
+    {
+        $this->data[self::MICROVIDEOOFFSET] = $value;
+
+        return $this;
+    }
+
+    /**
+     * Returns duration, if it exists
+     *
+     * @return string|boolean
+     */
+    public function getMicroVideoOffset()
+    {
+        if (!isset($this->data[self::MICROVIDEOOFFSET])) {
+            return false;
+        }
+
+        return $this->data[self::MICROVIDEOOFFSET];
+    }
+
+    /**
+     * Sets the sublocation value
+     *
+     * @param string $value
+     * @return \PHPExif\Exif
+     */
+    public function setSublocation($value)
+    {
+        $this->data[self::SUBLOCATION] = $value;
+
+        return $this;
+    }
+
+    /**
+     * Returns sublocation, if it exists
+     *
+     * @return string|boolean
+     */
+    public function getSublocation()
+    {
+        if (!isset($this->data[self::SUBLOCATION])) {
+            return false;
+        }
+
+        return $this->data[self::SUBLOCATION];
+    }
+
+    /**
+     * Sets the city value
+     *
+     * @param string $value
+     * @return \PHPExif\Exif
+     */
+    public function setCity($value)
+    {
+        $this->data[self::CITY] = $value;
+
+        return $this;
+    }
+
+    /**
+     * Returns city, if it exists
+     *
+     * @return string|boolean
+     */
+    public function getCity()
+    {
+        if (!isset($this->data[self::CITY])) {
+            return false;
+        }
+
+        return $this->data[self::CITY];
+    }
+
+    /**
+     * Sets the state value
+     *
+     * @param string $value
+     * @return \PHPExif\Exif
+     */
+    public function setState($value)
+    {
+        $this->data[self::STATE] = $value;
+
+        return $this;
+    }
+
+    /**
+     * Returns state, if it exists
+     *
+     * @return string|boolean
+     */
+    public function getState()
+    {
+        if (!isset($this->data[self::STATE])) {
+            return false;
+        }
+
+        return $this->data[self::STATE];
+    }
+
+    /**
+     * Sets the country value
+     *
+     * @param string $value
+     * @return \PHPExif\Exif
+     */
+    public function setCountry($value)
+    {
+        $this->data[self::COUNTRY] = $value;
+
+        return $this;
+    }
+
+    /**
+     * Returns country, if it exists
+     *
+     * @return string|boolean
+     */
+    public function getCountry()
+    {
+        if (!isset($this->data[self::COUNTRY])) {
+            return false;
+        }
+
+        return $this->data[self::COUNTRY];
+    }
 }
diff --git a/lib/PHPExif/Hydrator/Mutator.php b/lib/PHPExif/Hydrator/Mutator.php
index 9c8a0d6..164d57c 100644
--- a/lib/PHPExif/Hydrator/Mutator.php
+++ b/lib/PHPExif/Hydrator/Mutator.php
@@ -33,7 +33,6 @@ class Mutator implements HydratorInterface
     public function hydrate($object, array $data)
     {
         foreach ($data as $property => $value) {
-
             $mutator = $this->determineMutator($property);
 
             if (method_exists($object, $mutator)) {
diff --git a/lib/PHPExif/Mapper/Exiftool.php b/lib/PHPExif/Mapper/Exiftool.php
index b272f0a..b1f4b61 100644
--- a/lib/PHPExif/Mapper/Exiftool.php
+++ b/lib/PHPExif/Mapper/Exiftool.php
@@ -35,6 +35,7 @@ class Exiftool implements MapperInterface
     const CREDIT                   = 'IPTC:Credit';
     const EXPOSURETIME             = 'ExifIFD:ExposureTime';
     const FILESIZE                 = 'System:FileSize';
+    const FILENAME                 = 'System:FileName';
     const FOCALLENGTH              = 'ExifIFD:FocalLength';
     const HEADLINE                 = 'IPTC:Headline';
     const IMAGEHEIGHT              = 'File:ImageHeight';
@@ -52,6 +53,34 @@ class Exiftool implements MapperInterface
     const YRESOLUTION              = 'IFD0:YResolution';
     const GPSLATITUDE              = 'GPS:GPSLatitude';
     const GPSLONGITUDE             = 'GPS:GPSLongitude';
+    const GPSALTITUDE              = 'GPS:GPSAltitude';
+    const IMGDIRECTION             = 'GPS:GPSImgDirection';
+    const DESCRIPTION              = 'ExifIFD:ImageDescription ';
+    const MAKE                     = 'IFD0:Make';
+    const LENS                     = 'ExifIFD:LensModel';
+    const SUBJECT                  = 'XMP-dc:Subject';
+    const CONTENTIDENTIFIER        = 'Apple:ContentIdentifier';
+    const MICROVIDEOOFFSET         = 'XMP-GCamera:MicroVideoOffset';
+    const SUBLOCATION              = 'IPTC2:Sublocation';
+    const CITY                     = 'IPTC2:City';
+    const STATE                    = 'IPTC2:Province-State';
+    const COUNTRY                  = 'IPTC2:Country-PrimaryLocationName';
+
+    const DATETIMEORIGINAL_QUICKTIME  = 'QuickTime:CreationDate';
+    const IMAGEHEIGHT_VIDEO           = 'Composite:ImageSize';
+    const IMAGEWIDTH_VIDEO            = 'Composite:ImageSize';
+    const MAKE_QUICKTIME              = 'QuickTime:Make';
+    const MODEL_QUICKTIME             = 'QuickTime:Model';
+    const CONTENTIDENTIFIER_QUICKTIME = 'QuickTime:ContentIdentifier';
+    const GPSLATITUDE_QUICKTIME       = 'Composite:GPSLatitude';
+    const GPSLONGITUDE_QUICKTIME      = 'Composite:GPSLongitude';
+    const GPSALTITUDE_QUICKTIME       = 'Composite:GPSAltitude';
+    const FRAMERATE                   = 'MPEG:FrameRate';
+    const FRAMERATE_QUICKTIME_1       = 'Track1:VideoFrameRate';
+    const FRAMERATE_QUICKTIME_2       = 'Track2:VideoFrameRate';
+    const FRAMERATE_QUICKTIME_3       = 'Track3:VideoFrameRate';
+    const DURATION                    = 'Composite:Duration';
+    const DURATION_QUICKTIME          = 'QuickTime:Duration';
 
     /**
      * Maps the ExifTool fields to the fields of
@@ -70,6 +99,7 @@ class Exiftool implements MapperInterface
         self::CREDIT                   => Exif::CREDIT,
         self::EXPOSURETIME             => Exif::EXPOSURE,
         self::FILESIZE                 => Exif::FILESIZE,
+        self::FILENAME                 => Exif::FILENAME,
         self::FOCALLENGTH              => Exif::FOCAL_LENGTH,
         self::APPROXIMATEFOCUSDISTANCE => Exif::FOCAL_DISTANCE,
         self::HEADLINE                 => Exif::HEADLINE,
@@ -86,8 +116,35 @@ class Exiftool implements MapperInterface
         self::YRESOLUTION              => Exif::VERTICAL_RESOLUTION,
         self::IMAGEWIDTH               => Exif::WIDTH,
         self::CAPTIONABSTRACT          => Exif::CAPTION,
-        self::GPSLATITUDE              => Exif::GPS,
-        self::GPSLONGITUDE             => Exif::GPS,
+        self::GPSLATITUDE              => Exif::LATITUDE,
+        self::GPSLONGITUDE             => Exif::LONGITUDE,
+        self::GPSALTITUDE              => Exif::ALTITUDE,
+        self::MAKE                     => Exif::MAKE,
+        self::IMGDIRECTION             => Exif::IMGDIRECTION,
+        self::LENS                     => Exif::LENS,
+        self::DESCRIPTION              => Exif::DESCRIPTION,
+        self::SUBJECT                  => Exif::KEYWORDS,
+        self::CONTENTIDENTIFIER        => Exif::CONTENTIDENTIFIER,
+        self::DATETIMEORIGINAL_QUICKTIME  => Exif::CREATION_DATE,
+        self::MAKE_QUICKTIME              => Exif::MAKE,
+        self::MODEL_QUICKTIME             => Exif::CAMERA,
+        self::CONTENTIDENTIFIER_QUICKTIME => Exif::CONTENTIDENTIFIER,
+        self::GPSLATITUDE_QUICKTIME       => Exif::LATITUDE,
+        self::GPSLONGITUDE_QUICKTIME      => Exif::LONGITUDE,
+        self::GPSALTITUDE_QUICKTIME       => Exif::ALTITUDE,
+        self::IMAGEHEIGHT_VIDEO           => Exif::HEIGHT,
+        self::IMAGEWIDTH_VIDEO            => Exif::WIDTH,
+        self::FRAMERATE                   => Exif::FRAMERATE,
+        self::FRAMERATE_QUICKTIME_1       => Exif::FRAMERATE,
+        self::FRAMERATE_QUICKTIME_2       => Exif::FRAMERATE,
+        self::FRAMERATE_QUICKTIME_3       => Exif::FRAMERATE,
+        self::DURATION                    => Exif::DURATION,
+        self::DURATION_QUICKTIME          => Exif::DURATION,
+        self::MICROVIDEOOFFSET            => Exif::MICROVIDEOOFFSET,
+        self::SUBLOCATION                 => Exif::SUBLOCATION,
+        self::CITY                        => Exif::CITY,
+        self::STATE                       => Exif::STATE,
+        self::COUNTRY                     => Exif::COUNTRY
     );
 
     /**
@@ -136,11 +193,31 @@ public function mapRawData(array $data)
                     $value = sprintf('%1$sm', $value);
                     break;
                 case self::DATETIMEORIGINAL:
+                    // QUICKTIME_DATE contains data on timezone
+                    // only set value if QUICKTIME_DATE has not been used
+                    if (!isset($mappedData[Exif::CREATION_DATE])) {
+                        try {
+                            if (isset($data['ExifIFD:OffsetTimeOriginal'])) {
+                                $timezone = new \DateTimeZone($data['ExifIFD:OffsetTimeOriginal']);
+                                $value = new \DateTime($value, $timezone);
+                            } else {
+                                $value = new \DateTime($value);
+                            }
+                        } catch (\Exception $e) {
+                            continue 2;
+                        }
+                    } else {
+                        continue 2;
+                    }
+
+                    break;
+                case self::DATETIMEORIGINAL_QUICKTIME:
                     try {
                         $value = new DateTime($value);
-                    } catch (\Exception $exception) {
+                    } catch (\Exception $e) {
                         continue 2;
                     }
+
                     break;
                 case self::EXPOSURETIME:
                     // Based on the source code of Exiftool (PrintExposureTime subroutine):
@@ -158,30 +235,84 @@ public function mapRawData(array $data)
                         $value = reset($focalLengthParts);
                     }
                     break;
+                case self::ISO:
+                    $value = explode(" ", $value)[0];
+                    break;
+                case self::GPSLATITUDE_QUICKTIME:
+                    $value  = $this->extractGPSCoordinates($value);
+                    break;
                 case self::GPSLATITUDE:
-                    $gpsData['lat']  = $this->extractGPSCoordinates($value);
+                    $latitudeRef = empty($data['GPS:GPSLatitudeRef']) ? 'N' : $data['GPS:GPSLatitudeRef'][0];
+                    $value = $this->extractGPSCoordinates($value);
+                    if ($value !== false) {
+                        $value = (strtoupper($latitudeRef) === 'S' ? -1.0 : 1.0) * $value;
+                    } else {
+                        $value = false;
+                    }
+
+                    break;
+                case self::GPSLONGITUDE_QUICKTIME:
+                    $value  = $this->extractGPSCoordinates($value);
                     break;
                 case self::GPSLONGITUDE:
-                    $gpsData['lon']  = $this->extractGPSCoordinates($value);
+                    $longitudeRef = empty($data['GPS:GPSLongitudeRef']) ? 'E' : $data['GPS:GPSLongitudeRef'][0];
+                    $value  = $this->extractGPSCoordinates($value);
+                    if ($value !== false) {
+                        $value  = (strtoupper($longitudeRef) === 'W' ? -1 : 1) * $value;
+                    }
+
+                    break;
+                case self::GPSALTITUDE:
+                    $flip = 1;
+                    if (!(empty($data['GPS:GPSAltitudeRef']))) {
+                        $flip = ($data['GPS:GPSAltitudeRef'] == '1') ? -1 : 1;
+                    }
+                        $value = $flip * (float) $value;
+                    break;
+                case self::GPSALTITUDE_QUICKTIME:
+                    $flip = 1;
+                    if (!(empty($data['Composite:GPSAltitudeRef']))) {
+                        $flip = ($data['Composite:GPSAltitudeRef'] == '1') ? -1 : 1;
+                    }
+                    $value = $flip * (float) $value;
+                    break;
+                case self::IMAGEHEIGHT_VIDEO:
+                case self::IMAGEWIDTH_VIDEO:
+                    $value_splitted = explode("x", $value);
+                    $rotate = false;
+                    if (!(empty($data['Composite:Rotation']))) {
+                        if ($data['Composite:Rotation']=='90' || $data['Composite:Rotation']=='270') {
+                            $rotate = true;
+                        }
+                    }
+                    if (empty($mappedData[Exif::WIDTH])) {
+                        if (!($rotate)) {
+                            $mappedData[Exif::WIDTH]  = intval($value_splitted[0]);
+                        } else {
+                            $mappedData[Exif::WIDTH]  = intval($value_splitted[1]);
+                        }
+                    }
+                    if (empty($mappedData[Exif::HEIGHT])) {
+                        if (!($rotate)) {
+                            $mappedData[Exif::HEIGHT] = intval($value_splitted[1]);
+                        } else {
+                            $mappedData[Exif::HEIGHT] = intval($value_splitted[0]);
+                        }
+                    }
+                    continue 2;
                     break;
             }
-
             // set end result
             $mappedData[$key] = $value;
         }
 
         // add GPS coordinates, if available
-        if (count($gpsData) === 2 && $gpsData['lat'] !== false && $gpsData['lon'] !== false) {
-            $latitudeRef = empty($data['GPS:GPSLatitudeRef'][0]) ? 'N' : $data['GPS:GPSLatitudeRef'][0];
-            $longitudeRef = empty($data['GPS:GPSLongitudeRef'][0]) ? 'E' : $data['GPS:GPSLongitudeRef'][0];
-
-            $gpsLocation = sprintf(
-                '%s,%s',
-                (strtoupper($latitudeRef) === 'S' ? -1 : 1) * $gpsData['lat'],
-                (strtoupper($longitudeRef) === 'W' ? -1 : 1) * $gpsData['lon']
-            );
-
-            $mappedData[Exif::GPS] = $gpsLocation;
+        if ((isset($mappedData[Exif::LATITUDE])) && (isset($mappedData[Exif::LONGITUDE]))) {
+            if (($mappedData[Exif::LATITUDE]!==false) && $mappedData[Exif::LONGITUDE]!==false) {
+                $mappedData[Exif::GPS] = sprintf('%s,%s', $mappedData[Exif::LATITUDE], $mappedData[Exif::LONGITUDE]);
+            } else {
+                $mappedData[Exif::GPS] = false;
+            }
         } else {
             unset($mappedData[Exif::GPS]);
         }
@@ -197,8 +328,8 @@ public function mapRawData(array $data)
      */
     protected function extractGPSCoordinates($coordinates)
     {
-        if ($this->numeric === true) {
-            return abs((float) $coordinates);
+        if (is_numeric($coordinates) === true || $this->numeric === true) {
+            return ((float) $coordinates);
         } else {
             if (!preg_match('!^([0-9.]+) deg ([0-9.]+)\' ([0-9.]+)"!', $coordinates, $matches)) {
                 return false;
diff --git a/lib/PHPExif/Mapper/FFprobe.php b/lib/PHPExif/Mapper/FFprobe.php
new file mode 100644
index 0000000..75972bd
--- /dev/null
+++ b/lib/PHPExif/Mapper/FFprobe.php
@@ -0,0 +1,342 @@
+<?php
+/**
+ * PHP Exif Native Mapper
+ *
+ * @link        http://github.com/miljar/PHPExif for the canonical source repository
+ * @copyright   Copyright (c) 2015 Tom Van Herreweghe <tom@theanalogguy.be>
+ * @license     http://github.com/miljar/PHPExif/blob/master/LICENSE MIT License
+ * @category    PHPExif
+ * @package     Mapper
+ */
+
+namespace PHPExif\Mapper;
+
+use PHPExif\Exif;
+use DateTime;
+use Exception;
+
+/**
+ * PHP Exif Native Mapper
+ *
+ * Maps native raw data to valid data for the \PHPExif\Exif class
+ *
+ * @category    PHPExif
+ * @package     Mapper
+ */
+class FFprobe implements MapperInterface
+{
+    const HEIGHT           = 'height';
+    const WIDTH            = 'width';
+    const FILESIZE         = 'size';
+    const FILENAME         = 'filename';
+    const FRAMERATE        = 'avg_frame_rate';
+    const DURATION         = 'duration';
+    const DATETIMEORIGINAL = 'creation_time';
+    const GPSLATITUDE      = 'location';
+    const GPSLONGITUDE     = 'location';
+    const MIMETYPE         = 'MimeType';
+
+    const QUICKTIME_GPSLATITUDE       = 'com.apple.quicktime.location.ISO6709';
+    const QUICKTIME_GPSLONGITUDE      = 'com.apple.quicktime.location.ISO6709';
+    const QUICKTIME_GPSALTITUDE       = 'com.apple.quicktime.location.ISO6709';
+    const QUICKTIME_DATE              = 'com.apple.quicktime.creationdate';
+    const QUICKTIME_DESCRIPTION       = 'com.apple.quicktime.description';
+    const QUICKTIME_TITLE             = 'com.apple.quicktime.title';
+    const QUICKTIME_KEYWORDS          = 'com.apple.quicktime.keywords';
+    const QUICKTIME_MAKE              = 'com.apple.quicktime.make';
+    const QUICKTIME_MODEL             = 'com.apple.quicktime.model';
+    const QUICKTIME_CONTENTIDENTIFIER = 'com.apple.quicktime.content.identifier';
+
+
+    /**
+     * Maps the ExifTool fields to the fields of
+     * the \PHPExif\Exif class
+     *
+     * @var array
+     */
+    protected $map = array(
+        self::HEIGHT           => Exif::HEIGHT,
+        self::WIDTH            => Exif::WIDTH,
+        self::DATETIMEORIGINAL => Exif::CREATION_DATE,
+        self::FILESIZE         => Exif::FILESIZE,
+        self::FILENAME         => Exif::FILENAME,
+        self::MIMETYPE         => Exif::MIMETYPE,
+        self::GPSLATITUDE      => Exif::LATITUDE,
+        self::GPSLONGITUDE     => Exif::LONGITUDE,
+        self::FRAMERATE        => Exif::FRAMERATE,
+        self::DURATION         => Exif::DURATION,
+
+        self::QUICKTIME_DATE      => Exif::CREATION_DATE,
+        self::QUICKTIME_DESCRIPTION       => Exif::DESCRIPTION,
+        self::QUICKTIME_MAKE              => Exif::MAKE,
+        self::QUICKTIME_TITLE             => Exif::TITLE,
+        self::QUICKTIME_MODEL             => Exif::CAMERA,
+        self::QUICKTIME_KEYWORDS          => Exif::KEYWORDS,
+        self::QUICKTIME_GPSLATITUDE       => Exif::LATITUDE,
+        self::QUICKTIME_GPSLONGITUDE      => Exif::LONGITUDE,
+        self::QUICKTIME_GPSALTITUDE       => Exif::ALTITUDE,
+        self::QUICKTIME_CONTENTIDENTIFIER => Exif::CONTENTIDENTIFIER,
+    );
+
+    const SECTION_TAGS      = 'tags';
+
+    /**
+     * A list of section names
+     *
+     * @var array
+     */
+    protected $sections = array(
+        self::SECTION_TAGS
+    );
+
+    /**
+     * Maps the array of raw source data to the correct
+     * fields for the \PHPExif\Exif class
+     *
+     * @param array $data
+     * @return array
+     */
+    public function mapRawData(array $data)
+    {
+        $mappedData = array();
+        $gpsData = array();
+
+        foreach ($data as $field => $value) {
+            if ($this->isSection($field) && is_array($value)) {
+                $subData = $this->mapRawData($value);
+
+                $mappedData = array_merge($mappedData, $subData);
+                continue;
+            }
+            if (!$this->isFieldKnown($field)) {
+                // silently ignore unknown fields
+                continue;
+            }
+
+            $key = $this->map[$field];
+
+            // manipulate the value if necessary
+            switch ($field) {
+                case self::DATETIMEORIGINAL:
+                    // QUICKTIME_DATE contains data on timezone
+                    // only set value if QUICKTIME_DATE has not been used
+                    if (!isset($mappedData[Exif::CREATION_DATE])) {
+                        try {
+                            $value = new DateTime($value);
+                        } catch (\Exception $e) {
+                            continue 2;
+                        }
+                    } else {
+                        continue 2;
+                    }
+
+                    break;
+                case self::QUICKTIME_DATE:
+                    try {
+                        $value = new DateTime($value);
+                    } catch (\Exception $e) {
+                        continue 2;
+                    }
+
+                    break;
+                case self::FRAMERATE:
+                    $value = $this->normalizeComponent($value);
+                    break;
+                case self::GPSLATITUDE:
+                case self::GPSLONGITUDE:
+                    $matches = [];
+                    preg_match('/^([+-][0-9\.]+)([+-][0-9\.]+)\/$/', $value, $matches);
+                    if (count($matches) == 3 &&
+                        !preg_match('/^\+0+\.0+$/', $matches[1]) &&
+                        !preg_match('/^\+0+\.0+$/', $matches[2])) {
+                        $mappedData[Exif::LATITUDE] = $matches[1];
+                        $mappedData[Exif::LONGITUDE] = $matches[2];
+                    }
+                    continue 2;
+                case self::QUICKTIME_GPSALTITUDE:
+                case self::QUICKTIME_GPSLATITUDE:
+                case self::QUICKTIME_GPSLONGITUDE:
+                    $location_data = $this->readISO6709($value);
+                    $mappedData[Exif::LATITUDE]  = $location_data['latitude'];
+                    $mappedData[Exif::LONGITUDE] = $location_data['longitude'];
+                    $mappedData[Exif::ALTITUDE]  = $location_data['altitude'];
+                    //$value = $this->normalizeComponent($value);
+                    continue 2;
+            }
+
+            // set end result
+            $mappedData[$key] = $value;
+        }
+
+        // add GPS coordinates, if available
+        if ((isset($mappedData[Exif::LATITUDE])) && (isset($mappedData[Exif::LONGITUDE]))) {
+            $mappedData[Exif::GPS] = sprintf('%s,%s', $mappedData[Exif::LATITUDE], $mappedData[Exif::LONGITUDE]);
+        } else {
+            unset($mappedData[Exif::GPS]);
+        }
+
+        // Swap width and height if needed
+        if (isset($data['tags']) && isset($data['tags']['rotate'])
+            && ($data['tags']['rotate'] === '90' || $data['tags']['rotate'] === '270')) {
+            $tmp = $mappedData[Exif::WIDTH];
+            $mappedData[Exif::WIDTH] = $mappedData[Exif::HEIGHT];
+            $mappedData[Exif::HEIGHT] = $tmp;
+        }
+
+        return $mappedData;
+    }
+
+    /**
+     * Determines if given field is a section
+     *
+     * @param string $field
+     * @return bool
+     */
+    protected function isSection($field)
+    {
+        return (in_array($field, $this->sections));
+    }
+
+    /**
+     * Determines if the given field is known,
+     * in a case insensitive way for its first letter.
+     * Also update $field to keep it valid against the known fields.
+     *
+     * @param  string  &$field
+     * @return bool
+     */
+    protected function isFieldKnown(&$field)
+    {
+        $lcfField = lcfirst($field);
+        if (array_key_exists($lcfField, $this->map)) {
+            $field = $lcfField;
+
+            return true;
+        }
+
+        $ucfField = ucfirst($field);
+        if (array_key_exists($ucfField, $this->map)) {
+            $field = $ucfField;
+
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Normalize component
+     *
+     * @param string $component
+     * @return float
+     */
+    protected function normalizeComponent($rational)
+    {
+        $parts = explode('/', $rational, 2);
+        if (count($parts) == 1) {
+            return (float) $parts[0];
+        }
+        // case part[1] is 0, div by 0 is forbidden.
+        if ($parts[1] == 0) {
+            return (float) 0;
+        }
+        return (float) $parts[0] / $parts[1];
+    }
+
+
+    /**
+
+     * Converts results of ISO6709 parsing
+     * to decimal format for latitude and longitude
+     * See https://github.com/seanson/python-iso6709.git.
+     *
+     * @param string sign
+     * @param string degrees
+     * @param string minutes
+     * @param string seconds
+     * @param string fraction
+     *
+     * @return float
+     */
+    public function convertDMStoDecimal(
+        string $sign,
+        string $degrees,
+        string $minutes,
+        string $seconds,
+        string $fraction
+    ) {
+        if ($fraction !== '') {
+            if ($seconds !== '') {
+                $seconds = $seconds . $fraction;
+            } elseif ($minutes !== '') {
+                $minutes = $minutes . $fraction;
+            } else {
+                $degrees = $degrees . $fraction;
+            }
+        }
+        $decimal = floatval($degrees) + floatval($minutes) / 60.0 + floatval($seconds) / 3600.0;
+        if ($sign == '-') {
+            $decimal = -1.0 * $decimal;
+        }
+        return $decimal;
+    }
+
+    /**
+     * Returns the latitude, longitude and altitude
+     * of a GPS coordiante formattet with ISO6709
+     * See https://github.com/seanson/python-iso6709.git.
+     *
+     * @param string val_ISO6709
+     *
+     * @return array
+     */
+    public function readISO6709(string $val_ISO6709)
+    {
+        $return = [
+            'latitude' => null,
+            'longitude' => null,
+            'altitude' => null,
+        ];
+        $matches = [];
+        // Adjustment compared to https://github.com/seanson/python-iso6709.git
+        // Altitude have format +XX.XXXX -> Adjustment for decimal
+
+        preg_match(
+            '/^(?<lat_sign>\+|-)' .
+            '(?<lat_degrees>[0,1]?\d{2})' .
+            '(?<lat_minutes>\d{2}?)?' .
+            '(?<lat_seconds>\d{2}?)?' .
+            '(?<lat_fraction>\.\d+)?' .
+            '(?<lng_sign>\+|-)' .
+            '(?<lng_degrees>[0,1]?\d{2})' .
+            '(?<lng_minutes>\d{2}?)?' .
+            '(?<lng_seconds>\d{2}?)?' .
+            '(?<lng_fraction>\.\d+)?' .
+            '(?<alt>[\+\-][0-9]\d*(\.\d+)?)?\/$/',
+            $val_ISO6709,
+            $matches
+        );
+
+        $return['latitude'] =
+            $this->convertDMStoDecimal(
+                $matches['lat_sign'],
+                $matches['lat_degrees'],
+                $matches['lat_minutes'],
+                $matches['lat_seconds'],
+                $matches['lat_fraction']
+            );
+
+        $return['longitude'] =
+            $this->convertDMStoDecimal(
+                $matches['lng_sign'],
+                $matches['lng_degrees'],
+                $matches['lng_minutes'],
+                $matches['lng_seconds'],
+                $matches['lng_fraction']
+            );
+        if (isset($matches['alt'])) {
+            $return['altitude'] = doubleval($matches['alt']);
+        }
+        return $return;
+    }
+}
diff --git a/lib/PHPExif/Mapper/Native.php b/lib/PHPExif/Mapper/Native.php
index 9ecde02..31f942f 100644
--- a/lib/PHPExif/Mapper/Native.php
+++ b/lib/PHPExif/Mapper/Native.php
@@ -34,6 +34,7 @@ class Native implements MapperInterface
     const CREDIT           = 'credit';
     const EXPOSURETIME     = 'ExposureTime';
     const FILESIZE         = 'FileSize';
+    const FILENAME         = 'FileName';
     const FOCALLENGTH      = 'FocalLength';
     const FOCUSDISTANCE    = 'FocusDistance';
     const HEADLINE         = 'headline';
@@ -52,6 +53,20 @@ class Native implements MapperInterface
     const YRESOLUTION      = 'YResolution';
     const GPSLATITUDE      = 'GPSLatitude';
     const GPSLONGITUDE     = 'GPSLongitude';
+    const GPSALTITUDE      = 'GPSAltitude';
+    const IMGDIRECTION     = 'GPSImgDirection';
+    const MAKE             = 'Make';
+    const LENS             = 'LensInfo';
+    const LENS_LR          = 'UndefinedTag:0xA434';
+    const LENS_TYPE        = 'LensType';
+    const DESCRIPTION      = 'caption';
+    const SUBJECT          = 'subject';
+    const FRAMERATE        = 'framerate';
+    const DURATION         = 'duration';
+    const CITY             = 'city';
+    const SUBLOCATION      = 'sublocation';
+    const STATE            = 'state';
+    const COUNTRY          = 'country';
 
     const SECTION_FILE      = 'FILE';
     const SECTION_COMPUTED  = 'COMPUTED';
@@ -103,6 +118,7 @@ class Native implements MapperInterface
         self::DATETIMEORIGINAL => Exif::CREATION_DATE,
         self::EXPOSURETIME     => Exif::EXPOSURE,
         self::FILESIZE         => Exif::FILESIZE,
+        self::FILENAME         => Exif::FILENAME,
         self::FOCALLENGTH      => Exif::FOCAL_LENGTH,
         self::ISOSPEEDRATINGS  => Exif::ISO,
         self::MIMETYPE         => Exif::MIMETYPE,
@@ -110,8 +126,23 @@ class Native implements MapperInterface
         self::SOFTWARE         => Exif::SOFTWARE,
         self::XRESOLUTION      => Exif::HORIZONTAL_RESOLUTION,
         self::YRESOLUTION      => Exif::VERTICAL_RESOLUTION,
-        self::GPSLATITUDE      => Exif::GPS,
-        self::GPSLONGITUDE     => Exif::GPS,
+        self::GPSLATITUDE      => Exif::LATITUDE,
+        self::GPSLONGITUDE     => Exif::LONGITUDE,
+        self::GPSALTITUDE      => Exif::ALTITUDE,
+        self::IMGDIRECTION     => Exif::IMGDIRECTION,
+        self::MAKE             => Exif::MAKE,
+        self::LENS             => Exif::LENS,
+        self::LENS_LR          => Exif::LENS,
+        self::LENS_TYPE        => Exif::LENS,
+        self::DESCRIPTION      => Exif::DESCRIPTION,
+        self::SUBJECT          => Exif::KEYWORDS,
+        self::FRAMERATE        => Exif::FRAMERATE,
+        self::DURATION         => Exif::DURATION,
+        self::SUBLOCATION      => Exif::SUBLOCATION,
+        self::CITY             => Exif::CITY,
+        self::STATE            => Exif::STATE,
+        self::COUNTRY          => Exif::COUNTRY
+
     );
 
     /**
@@ -125,6 +156,7 @@ public function mapRawData(array $data)
     {
         $mappedData = array();
         $gpsData = array();
+
         foreach ($data as $field => $value) {
             if ($this->isSection($field) && is_array($value)) {
                 $subData = $this->mapRawData($value);
@@ -143,9 +175,16 @@ public function mapRawData(array $data)
             // manipulate the value if necessary
             switch ($field) {
                 case self::DATETIMEORIGINAL:
+                    // Check if OffsetTimeOriginal (0x9011) is available
                     try {
-                        $value = new DateTime($value);
-                    } catch (Exception $exception) {
+                        if (isset($data['UndefinedTag:0x9011'])) {
+                            $timezone = new \DateTimeZone($data['UndefinedTag:0x9011']);
+                            $value = new \DateTime($value, $timezone);
+                        } else {
+                            $value = new \DateTime($value);
+                        }
+                    } catch (\Exception $e) {
+                        // Provided DateTimeOriginal or OffsetTimeOriginal invalid
                         continue 2;
                     }
                     break;
@@ -172,16 +211,41 @@ public function mapRawData(array $data)
                         $value = (int) reset($parts) / (int) end($parts);
                     }
                     break;
+                case self::ISOSPEEDRATINGS:
+                    $value = explode(" ", $value)[0];
+                    break;
                 case self::XRESOLUTION:
                 case self::YRESOLUTION:
                     $resolutionParts = explode('/', $value);
                     $value = (int) reset($resolutionParts);
                     break;
                 case self::GPSLATITUDE:
-                    $gpsData['lat'] = $this->extractGPSCoordinate($value);
+                    $GPSLatitudeRef = (!(empty($data['GPSLatitudeRef'][0]))) ? $data['GPSLatitudeRef'][0] : '';
+                    $value = $this->extractGPSCoordinate((array)$value, $GPSLatitudeRef);
                     break;
                 case self::GPSLONGITUDE:
-                    $gpsData['lon'] = $this->extractGPSCoordinate($value);
+                    $GPSLongitudeRef = (!(empty($data['GPSLongitudeRef'][0]))) ? $data['GPSLongitudeRef'][0] : '';
+                    $value = $this->extractGPSCoordinate((array)$value, $GPSLongitudeRef);
+                    break;
+                case self::GPSALTITUDE:
+                    $flp = 1;
+                    if (!(empty($data['GPSAltitudeRef'][0]))) {
+                        $flp = ($data['GPSAltitudeRef'][0] == '1' || $data['GPSAltitudeRef'][0] == "\u{0001}") ? -1 : 1;
+                    }
+                    $value = $flp * $this->normalizeComponent($value);
+                    break;
+                case self::IMGDIRECTION:
+                    $value = $this->normalizeComponent($value);
+                    break;
+                case self::LENS_LR:
+                    if (!(empty($mappedData[Exif::LENS]))) {
+                        $mappedData[Exif::LENS] = $value;
+                    }
+                    break;
+                case self::LENS_TYPE:
+                    if (!(empty($mappedData[Exif::LENS]))) {
+                        $mappedData[Exif::LENS] = $value;
+                    }
                     break;
             }
 
@@ -190,17 +254,8 @@ public function mapRawData(array $data)
         }
 
         // add GPS coordinates, if available
-        if (count($gpsData) === 2) {
-            $latitudeRef = empty($data['GPSLatitudeRef'][0]) ? 'N' : $data['GPSLatitudeRef'][0];
-            $longitudeRef = empty($data['GPSLongitudeRef'][0]) ? 'E' : $data['GPSLongitudeRef'][0];
-
-            $gpsLocation = sprintf(
-                '%s,%s',
-                (strtoupper($latitudeRef) === 'S' ? -1 : 1) * $gpsData['lat'],
-                (strtoupper($longitudeRef) === 'W' ? -1 : 1) * $gpsData['lon']
-            );
-
-            $mappedData[Exif::GPS] = $gpsLocation;
+        if ((isset($mappedData[Exif::LATITUDE])) && (isset($mappedData[Exif::LONGITUDE]))) {
+            $mappedData[Exif::GPS] = sprintf('%s,%s', $mappedData[Exif::LATITUDE], $mappedData[Exif::LONGITUDE]);
         } else {
             unset($mappedData[Exif::GPS]);
         }
@@ -249,41 +304,35 @@ protected function isFieldKnown(&$field)
     /**
      * Extract GPS coordinates from components array
      *
-     * @param array|string $components
+     * @param array $coordinate
+     * @param string $ref
      * @return float
      */
-    protected function extractGPSCoordinate($components)
+    protected function extractGPSCoordinate($coordinate, $ref)
     {
-        if (!is_array($components)) {
-            $components = array($components);
-        }
-        $components = array_map(array($this, 'normalizeComponent'), $components);
-
-        if (count($components) > 2) {
-            return floatval($components[0]) + (floatval($components[1]) / 60) + (floatval($components[2]) / 3600);
-        }
-
-        return reset($components);
+        $degrees = count($coordinate) > 0 ? $this->normalizeComponent($coordinate[0]) : 0;
+        $minutes = count($coordinate) > 1 ? $this->normalizeComponent($coordinate[1]) : 0;
+        $seconds = count($coordinate) > 2 ? $this->normalizeComponent($coordinate[2]) : 0;
+        $flip = ($ref == 'W' || $ref == 'S') ? -1 : 1;
+        return $flip * ($degrees + (float) $minutes / 60 + (float) $seconds / 3600);
     }
 
     /**
      * Normalize component
      *
-     * @param mixed $component
-     * @return int|float
+     * @param string $component
+     * @return float
      */
-    protected function normalizeComponent($component)
+    protected function normalizeComponent($rational)
     {
-        $parts = explode('/', $component);
-
-        if (count($parts) > 1) {
-            if ($parts[1]) {
-                return intval($parts[0]) / intval($parts[1]);
-            }
-
-            return 0;
+        $parts = explode('/', $rational, 2);
+        if (count($parts) == 1) {
+            return (float) $parts[0];
         }
-
-        return floatval(reset($parts));
+        // case part[1] is 0, div by 0 is forbidden.
+        if ($parts[1] == 0) {
+            return (float) 0;
+        }
+        return (float) $parts[0] / $parts[1];
     }
 }
diff --git a/lib/PHPExif/Reader/Reader.php b/lib/PHPExif/Reader/Reader.php
index 34b7bb5..b6a322f 100755
--- a/lib/PHPExif/Reader/Reader.php
+++ b/lib/PHPExif/Reader/Reader.php
@@ -14,6 +14,7 @@
 use PHPExif\Adapter\AdapterInterface;
 use PHPExif\Adapter\NoAdapterException;
 use PHPExif\Adapter\Exiftool as ExiftoolAdapter;
+use PHPExif\Adapter\FFprobe as FFprobeAdapter;
 use PHPExif\Adapter\Native as NativeAdapter;
 
 /**
@@ -29,6 +30,7 @@ class Reader implements ReaderInterface
 {
     const TYPE_NATIVE   = 'native';
     const TYPE_EXIFTOOL = 'exiftool';
+    const TYPE_FFPROBE  = 'ffprobe';
 
     /**
      * The current adapter
@@ -79,6 +81,9 @@ public static function factory($type)
             case self::TYPE_EXIFTOOL:
                 $adapter = new ExiftoolAdapter();
                 break;
+            case self::TYPE_FFPROBE:
+                $adapter = new FFProbeAdapter();
+                break;
             default:
                 throw new \InvalidArgumentException(
                     sprintf('Unknown type "%1$s"', $type)
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 7470936..dc60816 100755
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -22,9 +22,6 @@
     <logging>
         <log type="coverage-html"
             target="tests/log/report"
-            charset="UTF-8"
-            yui="true"
-            highlight="true"
             lowUpperBound="40"
             highLowerBound="70" />
         <log type="testdox-html" target="tests/log/testdox.html" />
diff --git a/tests/PHPExif/Adapter/AdapterAbstractTest.php b/tests/PHPExif/Adapter/AdapterAbstractTest.php
index 46982da..0a5d246 100644
--- a/tests/PHPExif/Adapter/AdapterAbstractTest.php
+++ b/tests/PHPExif/Adapter/AdapterAbstractTest.php
@@ -1,16 +1,15 @@
 <?php
 /**
  * @covers \PHPExif\Adapter\AdapterAbstract::<!public>
- * @covers \PHPExif\Adapter\AdapterInterface
  */
-class AdapterAbstractTest extends \PHPUnit_Framework_TestCase
+class AdapterAbstractTest extends PHPUnit\Framework\TestCase
 {
     /**
      * @var \PHPExif\Adapter\Exiftool|\PHPExif\Adapter\Native
      */
     protected $adapter;
 
-    public function setUp()
+    protected function setUp(): void
     {
         $this->adapter = new \PHPExif\Adapter\Native();
     }
@@ -229,4 +228,3 @@ public function testGetHydratorLazyLoadingSetsInProperty()
         $this->assertInstanceOf($hydratorClass, $reflProp->getValue($this->adapter));
     }
 }
-
diff --git a/tests/PHPExif/Adapter/ExiftoolProcOpenTest.php b/tests/PHPExif/Adapter/ExiftoolProcOpenTest.php
index 9d9bd39..81bc457 100644
--- a/tests/PHPExif/Adapter/ExiftoolProcOpenTest.php
+++ b/tests/PHPExif/Adapter/ExiftoolProcOpenTest.php
@@ -1,9 +1,9 @@
 <?php
 // disable the mock by default
 namespace
-{  
+{
     $mockProcOpen = false;
-}  
+}
 
 // same namespace as SUT
 namespace PHPExif\Adapter
@@ -26,21 +26,21 @@ function proc_open($cmd, array $descriptorspec , &$pipes = array())
     /**
      * @covers \PHPExif\Adapter\Exiftool::<!public>
      */
-    class ExiftoolProcOpenTest extends \PHPUnit_Framework_TestCase
+    class ExiftoolProcOpenTest extends \PHPUnit\Framework\TestCase
     {
         /**
          * @var \PHPExif\Adapter\Exiftool
          */
         protected $adapter;
 
-        public function setUp()
+        public function setUp() : void
         {
             global $mockProcOpen;
             $mockProcOpen = true;
             $this->adapter = new \PHPExif\Adapter\Exiftool();
         }
 
-        public function tearDown()
+        public function tearDown() : void
         {
             global $mockProcOpen;
             $mockProcOpen = false;
@@ -49,10 +49,10 @@ public function tearDown()
         /**
          * @group exiftool
          * @covers \PHPExif\Adapter\Exiftool::getCliOutput
-         * @expectedException RuntimeException
          */
         public function testGetCliOutput()
         {
+            $this->expectException('RuntimeException');
             $reflMethod = new \ReflectionMethod('\PHPExif\Adapter\Exiftool', 'getCliOutput');
             $reflMethod->setAccessible(true);
 
diff --git a/tests/PHPExif/Adapter/ExiftoolTest.php b/tests/PHPExif/Adapter/ExiftoolTest.php
index 49029e5..dc291a6 100644
--- a/tests/PHPExif/Adapter/ExiftoolTest.php
+++ b/tests/PHPExif/Adapter/ExiftoolTest.php
@@ -2,14 +2,14 @@
 /**
  * @covers \PHPExif\Adapter\Exiftool::<!public>
  */
-class ExiftoolTest extends \PHPUnit_Framework_TestCase
+class ExiftoolTest extends \PHPUnit\Framework\TestCase
 {
     /**
      * @var \PHPExif\Adapter\Exiftool
      */
     protected $adapter;
 
-    public function setUp()
+    public function setUp(): void
     {
         $this->adapter = new \PHPExif\Adapter\Exiftool();
     }
@@ -46,10 +46,10 @@ public function testSetToolPathInProperty()
     /**
      * @group exiftool
      * @covers \PHPExif\Adapter\Exiftool::setToolPath
-     * @expectedException InvalidArgumentException
      */
     public function testSetToolPathThrowsException()
     {
+        $this->expectException('InvalidArgumentException');
         $this->adapter->setToolPath('/foo/bar');
     }
 
@@ -60,7 +60,7 @@ public function testSetToolPathThrowsException()
      */
     public function testGetToolPathLazyLoadsPath()
     {
-        $this->assertInternalType('string', $this->adapter->getToolPath());
+        $this->assertIsString($this->adapter->getToolPath());
     }
 
     /**
@@ -105,7 +105,7 @@ public function testGetExifFromFile()
         $this->adapter->setOptions(array('encoding' => array('iptc' => 'cp1252')));
         $result = $this->adapter->getExifFromFile($file);
         $this->assertInstanceOf('\PHPExif\Exif', $result);
-        $this->assertInternalType('array', $result->getRawData());
+        $this->assertIsArray($result->getRawData());
         $this->assertNotEmpty($result->getRawData());
     }
 
@@ -119,7 +119,7 @@ public function testGetExifFromFileWithUtf8()
         $this->adapter->setOptions(array('encoding' => array('iptc' => 'utf8')));
         $result = $this->adapter->getExifFromFile($file);
         $this->assertInstanceOf('\PHPExif\Exif', $result);
-        $this->assertInternalType('array', $result->getRawData());
+        $this->assertIsArray($result->getRawData());
         $this->assertNotEmpty($result->getRawData());
     }
 
@@ -140,6 +140,6 @@ public function testGetCliOutput()
             )
         );
 
-        $this->assertInternalType('string', $result);
+        $this->assertIsString($result);
     }
 }
diff --git a/tests/PHPExif/Adapter/FFprobeTest.php b/tests/PHPExif/Adapter/FFprobeTest.php
new file mode 100755
index 0000000..3bb0b3b
--- /dev/null
+++ b/tests/PHPExif/Adapter/FFprobeTest.php
@@ -0,0 +1,98 @@
+<?php
+/**
+ * @covers \PHPExif\Adapter\Native::<!public>
+ */
+class FFprobeTest extends \PHPUnit\Framework\TestCase
+{
+    /**
+     * @var \PHPExif\Adapter\FFprobe
+     */
+    protected $adapter;
+
+    public function setUp(): void
+    {
+        $this->adapter = new \PHPExif\Adapter\FFprobe();
+    }
+
+
+    /**
+     * @group ffprobe
+     * @covers \PHPExif\Adapter\FFprobe::getToolPath
+     */
+    public function testGetToolPathFromProperty()
+    {
+        $reflProperty = new \ReflectionProperty('\PHPExif\Adapter\FFprobe', 'toolPath');
+        $reflProperty->setAccessible(true);
+        $expected = '/foo/bar/baz';
+        $reflProperty->setValue($this->adapter, $expected);
+
+        $this->assertEquals($expected, $this->adapter->getToolPath());
+    }
+
+    /**
+     * @group ffprobe
+     * @covers \PHPExif\Adapter\FFprobe::setToolPath
+     */
+    public function testSetToolPathInProperty()
+    {
+        $reflProperty = new \ReflectionProperty('\PHPExif\Adapter\FFprobe', 'toolPath');
+        $reflProperty->setAccessible(true);
+
+        $expected = '/tmp';
+        $this->adapter->setToolPath($expected);
+
+        $this->assertEquals($expected, $reflProperty->getValue($this->adapter));
+    }
+
+    /**
+     * @group ffprobe
+     * @covers \PHPExif\Adapter\FFprobe::setToolPath
+     */
+    public function testSetToolPathThrowsException()
+    {
+        $this->expectException('InvalidArgumentException');
+        $this->adapter->setToolPath('/foo/bar');
+    }
+
+    /**
+     * @group ffprobe
+     * @covers \PHPExif\Adapter\FFprobe::getToolPath
+     */
+    public function testGetToolPathLazyLoadsPath()
+    {
+        $this->assertIsString($this->adapter->getToolPath());
+    }
+
+    /**
+     * @group ffprobe
+     * @covers \PHPExif\Adapter\FFprobe::getExifFromFile
+     */
+    public function testGetExifFromFileHasData()
+    {
+        $file = PHPEXIF_TEST_ROOT . '/files/IMG_3824.MOV';
+        $result = $this->adapter->getExifFromFile($file);
+        $this->assertInstanceOf('\PHPExif\Exif', $result);
+        $this->assertIsArray($result->getRawData());
+        $this->assertNotEmpty($result->getRawData());
+
+        $file = PHPEXIF_TEST_ROOT . '/files/IMG_3825.MOV';
+        $result = $this->adapter->getExifFromFile($file);
+        $this->assertInstanceOf('\PHPExif\Exif', $result);
+        $this->assertIsArray($result->getRawData());
+        $this->assertNotEmpty($result->getRawData());
+    }
+
+    /**
+     * @group ffprobe
+     * @covers \PHPExif\Adapter\FFprobe::getExifFromFile
+     */
+    public function testErrorImageUsed()
+    {
+        $file = PHPEXIF_TEST_ROOT . '/files/morning_glory_pool_500.jpg';;
+        $result = $this->adapter->getExifFromFile($file);
+        $this->assertIsBool($result);
+        $this->assertEquals(false, $result);
+    }
+
+
+}
diff --git a/tests/PHPExif/Adapter/NativeTest.php b/tests/PHPExif/Adapter/NativeTest.php
index 0df554e..87a0bf4 100755
--- a/tests/PHPExif/Adapter/NativeTest.php
+++ b/tests/PHPExif/Adapter/NativeTest.php
@@ -2,14 +2,14 @@
 /**
  * @covers \PHPExif\Adapter\Native::<!public>
  */
-class NativeTest extends \PHPUnit_Framework_TestCase
+class NativeTest extends \PHPUnit\Framework\TestCase
 {
     /**
      * @var \PHPExif\Adapter\Native
      */
     protected $adapter;
 
-    public function setUp()
+    public function setUp(): void
     {
         $this->adapter = new \PHPExif\Adapter\Native();
     }
@@ -120,7 +120,7 @@ public function testGetExifFromFileHasData()
         $file = PHPEXIF_TEST_ROOT . '/files/morning_glory_pool_500.jpg';
         $result = $this->adapter->getExifFromFile($file);
         $this->assertInstanceOf('\PHPExif\Exif', $result);
-        $this->assertInternalType('array', $result->getRawData());
+        $this->assertIsArray($result->getRawData());
         $this->assertNotEmpty($result->getRawData());
     }
 
diff --git a/tests/PHPExif/ExifTest.php b/tests/PHPExif/ExifTest.php
index 82881d2..ba6e57b 100755
--- a/tests/PHPExif/ExifTest.php
+++ b/tests/PHPExif/ExifTest.php
@@ -2,7 +2,7 @@
 /**
  * @covers \PHPExif\Exif::<!public>
  */
-class ExifTest extends \PHPUnit_Framework_TestCase
+class ExifTest extends \PHPUnit\Framework\TestCase
 {
     /**
      * @var \PHPExif\Exif
@@ -12,7 +12,7 @@ class ExifTest extends \PHPUnit_Framework_TestCase
     /**
      * Setup function before the tests
      */
-    public function setUp()
+    public function setUp(): void
     {
         $this->exif = new \PHPExif\Exif();
     }
@@ -125,10 +125,26 @@ public function testSetData()
      * @covers \PHPExif\Exif::getJobtitle
      * @covers \PHPExif\Exif::getMimeType
      * @covers \PHPExif\Exif::getFileSize
+     * @covers \PHPExif\Exif::getFileName
      * @covers \PHPExif\Exif::getHeadline
      * @covers \PHPExif\Exif::getColorSpace
      * @covers \PHPExif\Exif::getOrientation
      * @covers \PHPExif\Exif::getGPS
+     * @covers \PHPExif\Exif::getDescription
+     * @covers \PHPExif\Exif::getMake
+     * @covers \PHPExif\Exif::getAltitude
+     * @covers \PHPExif\Exif::getLatitude
+     * @covers \PHPExif\Exif::getLongitude
+     * @covers \PHPExif\Exif::getImgDirection
+     * @covers \PHPExif\Exif::getLens
+     * @covers \PHPExif\Exif::getContentIdentifier
+     * @covers \PHPExif\Exif::getFramerate
+     * @covers \PHPExif\Exif::getDuration
+     * @covers \PHPExif\Exif::getMicroVideoOffset
+     * @covers \PHPExif\Exif::getCity
+     * @covers \PHPExif\Exif::getSublocation
+     * @covers \PHPExif\Exif::getState
+     * @covers \PHPExif\Exif::getCountry
      * @param string $accessor
      */
     public function testUndefinedPropertiesReturnFalse($accessor)
@@ -169,10 +185,26 @@ public function providerUndefinedPropertiesReturnFalse()
             array('getJobtitle'),
             array('getMimeType'),
             array('getFileSize'),
+            array('getFileName'),
             array('getHeadline'),
             array('getColorSpace'),
             array('getOrientation'),
             array('getGPS'),
+            array('getDescription'),
+            array('getMake'),
+            array('getAltitude'),
+            array('getLatitude'),
+            array('getLongitude'),
+            array('getImgDirection'),
+            array('getLens'),
+            array('getContentIdentifier'),
+            array('getFramerate'),
+            array('getDuration'),
+            array('getMicroVideoOffset'),
+            array('getCity'),
+            array('getSublocation'),
+            array('getState'),
+            array('getCountry'),
         );
     }
 
@@ -487,6 +519,18 @@ public function testGetFileSize()
         $this->assertEquals($expected, $this->exif->getFileSize());
     }
 
+    /**
+     * @group exif
+     * @covers \PHPExif\Exif::getFileName
+     */
+    public function testGetFileName()
+    {
+        $expected = '27852365.jpg';
+        $data[\PHPExif\Exif::FILENAME] = $expected;
+        $this->exif->setData($data);
+        $this->assertEquals($expected, $this->exif->getFileName());
+    }
+
     /**
      * @group exif
      * @covers \PHPExif\Exif::getOrientation
@@ -511,6 +555,186 @@ public function testGetGPS()
         $this->assertEquals($expected, $this->exif->getGPS());
     }
 
+    /**
+     * @group exif
+     * @covers \PHPExif\Exif::getDescription
+     */
+    public function testGetDescription()
+    {
+        $expected = 'Lorem ipsum';
+        $data[\PHPExif\Exif::DESCRIPTION] = $expected;
+        $this->exif->setData($data);
+        $this->assertEquals($expected, $this->exif->getDescription());
+    }
+
+    /**
+     * @group exif
+     * @covers \PHPExif\Exif::getMake
+     */
+    public function testGetMake()
+    {
+        $expected = 'Make';
+        $data[\PHPExif\Exif::MAKE] = $expected;
+        $this->exif->setData($data);
+        $this->assertEquals($expected, $this->exif->getMake());
+    }
+
+    /**
+     * @group exif
+     * @covers \PHPExif\Exif::getAltitude
+     */
+    public function testGetAltitude()
+    {
+        $expected = '8848';
+        $data[\PHPExif\Exif::ALTITUDE] = $expected;
+        $this->exif->setData($data);
+        $this->assertEquals($expected, $this->exif->getAltitude());
+    }
+
+    /**
+     * @group exif
+     * @covers \PHPExif\Exif::getLatitude
+     */
+    public function testGetLatitude()
+    {
+        $expected = '40.333452380556';
+        $data[\PHPExif\Exif::LATITUDE] = $expected;
+        $this->exif->setData($data);
+        $this->assertEquals($expected, $this->exif->getLatitude());
+    }
+
+    /**
+     * @group exif
+     * @covers \PHPExif\Exif::getLongitude
+     */
+    public function testGetLongitude()
+    {
+        $expected = '-20.167314813889';
+        $data[\PHPExif\Exif::LONGITUDE] = $expected;
+        $this->exif->setData($data);
+        $this->assertEquals($expected, $this->exif->getLongitude());
+    }
+
+    /**
+     * @group exif
+     * @covers \PHPExif\Exif::getImgDirection
+     */
+    public function testGetImgDirection()
+    {
+        $expected = '180';
+        $data[\PHPExif\Exif::IMGDIRECTION] = $expected;
+        $this->exif->setData($data);
+        $this->assertEquals($expected, $this->exif->getImgDirection());
+    }
+
+    /**
+     * @group exif
+     * @covers \PHPExif\Exif::getLens
+     */
+    public function testGetLens()
+    {
+        $expected = '70 - 200mm';
+        $data[\PHPExif\Exif::LENS] = $expected;
+        $this->exif->setData($data);
+        $this->assertEquals($expected, $this->exif->getLens());
+    }
+
+    /**
+     * @group exif
+     * @covers \PHPExif\Exif::getContentIdentifier
+     */
+    public function testGetContentIdentifier()
+    {
+        $expected = 'C09DCB26-D321-4254-9F68-2E2E7FA16155';
+        $data[\PHPExif\Exif::CONTENTIDENTIFIER] = $expected;
+        $this->exif->setData($data);
+        $this->assertEquals($expected, $this->exif->getContentIdentifier());
+    }
+
+    /**
+     * @group exif
+     * @covers \PHPExif\Exif::getFramerate
+     */
+    public function testGetFramerate()
+    {
+        $expected = '24';
+        $data[\PHPExif\Exif::FRAMERATE] = $expected;
+        $this->exif->setData($data);
+        $this->assertEquals($expected, $this->exif->getFramerate());
+    }
+
+    /**
+     * @group exif
+     * @covers \PHPExif\Exif::getDuration
+     */
+    public function testGetDuration()
+    {
+        $expected = '1s';
+        $data[\PHPExif\Exif::DURATION] = $expected;
+        $this->exif->setData($data);
+        $this->assertEquals($expected, $this->exif->getDuration());
+    }
+
+    /**
+     * @group exif
+     * @covers \PHPExif\Exif::getMicroVideoOffset
+     */
+    public function testGetMicroVideoOffset()
+    {
+        $expected = '3062730';
+        $data[\PHPExif\Exif::MICROVIDEOOFFSET] = $expected;
+        $this->exif->setData($data);
+        $this->assertEquals($expected, $this->exif->getMicroVideoOffset());
+    }
+
+    /**
+     * @group exif
+     * @covers \PHPExif\Exif::getCity
+     */
+    public function testGetCity()
+    {
+        $expected = 'New York';
+        $data[\PHPExif\Exif::CITY] = $expected;
+        $this->exif->setData($data);
+        $this->assertEquals($expected, $this->exif->getCity());
+    }
+
+    /**
+     * @group exif
+     * @covers \PHPExif\Exif::getSublocation
+     */
+    public function testGetSublocation()
+    {
+        $expected = 'sublocation';
+        $data[\PHPExif\Exif::SUBLOCATION] = $expected;
+        $this->exif->setData($data);
+        $this->assertEquals($expected, $this->exif->getSublocation());
+    }
+
+    /**
+     * @group exif
+     * @covers \PHPExif\Exif::getState
+     */
+    public function testGetState()
+    {
+        $expected = 'New York';
+        $data[\PHPExif\Exif::STATE] = $expected;
+        $this->exif->setData($data);
+        $this->assertEquals($expected, $this->exif->getState());
+    }
+
+    /**
+     * @group exif
+     * @covers \PHPExif\Exif::getCountry
+     */
+    public function testGetCountry()
+    {
+        $expected = 'USA';
+        $data[\PHPExif\Exif::COUNTRY] = $expected;
+        $this->exif->setData($data);
+        $this->assertEquals($expected, $this->exif->getCountry());
+    }
+
     /**
      * @group exif
      * @covers \PHPExif\Exif::setAperture
@@ -535,10 +759,26 @@ public function testGetGPS()
      * @covers \PHPExif\Exif::setJobtitle
      * @covers \PHPExif\Exif::setMimeType
      * @covers \PHPExif\Exif::setFileSize
+     * @covers \PHPExif\Exif::setFileName
      * @covers \PHPExif\Exif::setHeadline
      * @covers \PHPExif\Exif::setColorSpace
      * @covers \PHPExif\Exif::setOrientation
      * @covers \PHPExif\Exif::setGPS
+     * @covers \PHPExif\Exif::setDescription
+     * @covers \PHPExif\Exif::setMake
+     * @covers \PHPExif\Exif::setAltitude
+     * @covers \PHPExif\Exif::setLongitude
+     * @covers \PHPExif\Exif::setLatitude
+     * @covers \PHPExif\Exif::setImgDirection
+     * @covers \PHPExif\Exif::setLens
+     * @covers \PHPExif\Exif::setContentIdentifier
+     * @covers \PHPExif\Exif::setFramerate
+     * @covers \PHPExif\Exif::setDuration
+     * @covers \PHPExif\Exif::setMicroVideoOffset
+     * @covers \PHPExif\Exif::setCity
+     * @covers \PHPExif\Exif::setSublocation
+     * @covers \PHPExif\Exif::setState
+     * @covers \PHPExif\Exif::setCountry
      */
     public function testMutatorMethodsSetInProperty()
     {
@@ -637,4 +877,3 @@ public function testAdapterConsistency()
         }
     }
 }
-
diff --git a/tests/PHPExif/Hydrator/MutatorTest.php b/tests/PHPExif/Hydrator/MutatorTest.php
index 1bf40f6..6e05ff2 100644
--- a/tests/PHPExif/Hydrator/MutatorTest.php
+++ b/tests/PHPExif/Hydrator/MutatorTest.php
@@ -1,14 +1,13 @@
 <?php
 /**
  * @covers \PHPExif\Hydrator\Mutator::<!public>
- * @covers \PHPExif\Hydrator\HydratorInterface
  */
-class MutatorTest extends \PHPUnit_Framework_TestCase
+class MutatorTest extends \PHPUnit\Framework\TestCase
 {
     /**
      * Setup function before the tests
      */
-    public function setUp()
+    protected function setUp(): void
     {
     }
 
@@ -70,4 +69,3 @@ public function setBar()
     {
     }
 }
-
diff --git a/tests/PHPExif/Mapper/ExiftoolMapperTest.php b/tests/PHPExif/Mapper/ExiftoolMapperTest.php
index 09d2456..84fbba0 100644
--- a/tests/PHPExif/Mapper/ExiftoolMapperTest.php
+++ b/tests/PHPExif/Mapper/ExiftoolMapperTest.php
@@ -2,11 +2,11 @@
 /**
  * @covers \PHPExif\Mapper\Exiftool::<!public>
  */
-class ExiftoolMapperTest extends \PHPUnit_Framework_TestCase
+class ExiftoolMapperTest extends \PHPUnit\Framework\TestCase
 {
     protected $mapper;
 
-    public function setUp()
+    public function setUp(): void
     {
         $this->mapper = new \PHPExif\Mapper\Exiftool;
     }
@@ -49,6 +49,27 @@ public function testMapRawDataMapsFieldsCorrectly()
         unset($map[\PHPExif\Mapper\Exiftool::FOCALLENGTH]);
         unset($map[\PHPExif\Mapper\Exiftool::GPSLATITUDE]);
         unset($map[\PHPExif\Mapper\Exiftool::GPSLONGITUDE]);
+        unset($map[\PHPExif\Mapper\Exiftool::CAPTION]);
+        unset($map[\PHPExif\Mapper\Exiftool::CONTENTIDENTIFIER]);
+        unset($map[\PHPExif\Mapper\Exiftool::KEYWORDS]);
+        unset($map[\PHPExif\Mapper\Exiftool::DATETIMEORIGINAL]);
+        unset($map[\PHPExif\Mapper\Exiftool::DATETIMEORIGINAL_QUICKTIME]);
+        unset($map[\PHPExif\Mapper\Exiftool::MAKE_QUICKTIME]);
+        unset($map[\PHPExif\Mapper\Exiftool::MODEL_QUICKTIME]);
+        unset($map[\PHPExif\Mapper\Exiftool::FRAMERATE]);
+        unset($map[\PHPExif\Mapper\Exiftool::FRAMERATE_QUICKTIME_1]);
+        unset($map[\PHPExif\Mapper\Exiftool::FRAMERATE_QUICKTIME_2]);
+        unset($map[\PHPExif\Mapper\Exiftool::FRAMERATE_QUICKTIME_3]);
+        unset($map[\PHPExif\Mapper\Exiftool::DURATION]);
+        unset($map[\PHPExif\Mapper\Exiftool::DURATION_QUICKTIME]);
+        unset($map[\PHPExif\Mapper\Exiftool::GPSLATITUDE_QUICKTIME]);
+        unset($map[\PHPExif\Mapper\Exiftool::GPSLONGITUDE_QUICKTIME]);
+        unset($map[\PHPExif\Mapper\Exiftool::GPSALTITUDE_QUICKTIME]);
+        unset($map[\PHPExif\Mapper\Exiftool::MICROVIDEOOFFSET]);
+        unset($map[\PHPExif\Mapper\Exiftool::CITY]);
+        unset($map[\PHPExif\Mapper\Exiftool::SUBLOCATION]);
+        unset($map[\PHPExif\Mapper\Exiftool::STATE]);
+        unset($map[\PHPExif\Mapper\Exiftool::COUNTRY]);
 
         // create raw data
         $keys = array_keys($map);
@@ -60,7 +81,7 @@ public function testMapRawDataMapsFieldsCorrectly()
         $mapped = $this->mapper->mapRawData($rawData);
 
         $i = 0;
-        foreach ($mapped as $key => $value) {
+	foreach ($mapped as $key => $value) {
             $this->assertEquals($map[$keys[$i]], $key);
             $i++;
         }
@@ -111,9 +132,80 @@ public function testMapRawDataCorrectlyFormatsCreationDate()
         $result = reset($mapped);
         $this->assertInstanceOf('\\DateTime', $result);
         $this->assertEquals(
-            reset($rawData), 
+            reset($rawData),
+            $result->format('Y:m:d H:i:s')
+        );
+    }
+
+    /**
+     * @group mapper
+     * @covers \PHPExif\Mapper\Exiftool::mapRawData
+     */
+    public function testMapRawDataCorrectlyFormatsCreationDateWithTimeZone()
+    {
+        $data = array (
+          array(
+            \PHPExif\Mapper\Exiftool::DATETIMEORIGINAL => '2015:04:01 12:11:09+0200',
+          ),
+          array(
+              \PHPExif\Mapper\Exiftool::DATETIMEORIGINAL => '2015:04:01 12:11:09',
+              'ExifIFD:OffsetTimeOriginal' => '+0200',
+          ),
+          array(
+              \PHPExif\Mapper\Exiftool::DATETIMEORIGINAL_QUICKTIME => '2015-04-01T12:11:09+0200',
+              \PHPExif\Mapper\Exiftool::DATETIMEORIGINAL => '2015:04:01 12:11:09',
+              'ExifIFD:OffsetTimeOriginal' => '+0200',
+          )
+        );
+
+        foreach ($data as $key => $rawData) {
+            $mapped = $this->mapper->mapRawData($rawData);
+
+            $result = reset($mapped);
+            $this->assertInstanceOf('\\DateTime', $result);
+            $this->assertEquals(
+                '2015:04:01 12:11:09',
+                $result->format('Y:m:d H:i:s')
+            );
+            $this->assertEquals(
+                7200,
+                $result->getOffset()
+            );
+            $this->assertEquals(
+                '+02:00',
+                $result->getTimezone()->getName()
+            );
+        }
+
+    }
+
+    /**
+     * @group mapper
+     * @covers \PHPExif\Mapper\Exiftool::mapRawData
+     */
+    public function testMapRawDataCorrectlyFormatsCreationDateWithTimeZone2()
+    {
+        $rawData = array(
+            \PHPExif\Mapper\Exiftool::DATETIMEORIGINAL => '2015:04:01 12:11:09',
+            'ExifIFD:OffsetTimeOriginal' => '+0200',
+        );
+
+        $mapped = $this->mapper->mapRawData($rawData);
+
+        $result = reset($mapped);
+        $this->assertInstanceOf('\\DateTime', $result);
+        $this->assertEquals(
+            '2015:04:01 12:11:09',
             $result->format('Y:m:d H:i:s')
         );
+        $this->assertEquals(
+            7200,
+            $result->getOffset()
+        );
+        $this->assertEquals(
+            '+02:00',
+            $result->getTimezone()->getName()
+        );
     }
 
     /**
@@ -131,6 +223,21 @@ public function testMapRawDataCorrectlyIgnoresIncorrectCreationDate()
         $this->assertEquals(false, reset($mapped));
     }
 
+    /**
+     * @group mapper
+     * @covers \PHPExif\Mapper\Exiftool::mapRawData
+     */
+    public function testMapRawDataCorrectlyIgnoresIncorrectCreationDate2()
+    {
+        $rawData = array(
+            \PHPExif\Mapper\Exiftool::DATETIMEORIGINAL_QUICKTIME => '2015:04:01',
+        );
+
+        $mapped = $this->mapper->mapRawData($rawData);
+
+        $this->assertEquals(false, reset($mapped));
+    }
+
     /**
      * @group mapper
      * @covers \PHPExif\Mapper\Exiftool::mapRawData
@@ -184,38 +291,46 @@ public function testMapRawDataCorrectlyFormatsGPSData()
             )
         );
 
-        $expected = '40.333452380556,-20.167314813889';
-        $this->assertCount(1, $result);
-        $this->assertEquals($expected, reset($result));
+        $expected_gps = '40.333452380556,-20.167314813889';
+        $expected_lat = '40.333452380556';
+        $expected_lon = '-20.167314813889';
+        $this->assertCount(3, $result);
+        $this->assertEquals($expected_gps, $result['gps']);
+        $this->assertEquals($expected_lat, $result['latitude']);
+        $this->assertEquals($expected_lon, $result['longitude']);
     }
 
     /**
      * @group mapper
      * @covers \PHPExif\Mapper\Exiftool::mapRawData
      */
-    public function testMapRawDataCorrectlyFormatsNumericGPSData()
+    public function testMapRawDataIncorrectlyFormatedGPSData()
     {
+        $this->mapper->setNumeric(false);
         $result = $this->mapper->mapRawData(
             array(
-                \PHPExif\Mapper\Exiftool::GPSLATITUDE  => '40.333452381',
+                \PHPExif\Mapper\Exiftool::GPSLATITUDE  => '40 degrees 20\' 0.42857" N',
                 'GPS:GPSLatitudeRef'                   => 'North',
-                \PHPExif\Mapper\Exiftool::GPSLONGITUDE => '20.167314814',
+                \PHPExif\Mapper\Exiftool::GPSLONGITUDE => '20 degrees 10\' 2.33333" W',
                 'GPS:GPSLongitudeRef'                  => 'West',
             )
         );
 
-        $expected = '40.333452381,-20.167314814';
-        $this->assertCount(1, $result);
-        $this->assertEquals($expected, reset($result));
+        $expected_gps = false;
+        $expected_lat = false;
+        $expected_lon = false;
+        $this->assertCount(3, $result);
+        $this->assertEquals($expected_gps, $result['gps']);
+        $this->assertEquals($expected_lat, $result['latitude']);
+        $this->assertEquals($expected_lon, $result['longitude']);
     }
 
     /**
      * @group mapper
      * @covers \PHPExif\Mapper\Exiftool::mapRawData
      */
-    public function testMapRawDataCorrectlyIgnoresIncorrectGPSData()
+    public function testMapRawDataCorrectlyFormatsNumericGPSData()
     {
-        $this->mapper->setNumeric(false);
         $result = $this->mapper->mapRawData(
             array(
                 \PHPExif\Mapper\Exiftool::GPSLATITUDE  => '40.333452381',
@@ -225,14 +340,20 @@ public function testMapRawDataCorrectlyIgnoresIncorrectGPSData()
             )
         );
 
-        $this->assertCount(0, $result);
+        $expected_gps = '40.333452381,-20.167314814';
+        $expected_lat = '40.333452381';
+        $expected_lon = '-20.167314814';
+        $this->assertCount(3, $result);
+        $this->assertEquals($expected_gps, $result['gps']);
+        $this->assertEquals($expected_lat, $result['latitude']);
+        $this->assertEquals($expected_lon, $result['longitude']);
     }
 
     /**
      * @group mapper
      * @covers \PHPExif\Mapper\Exiftool::mapRawData
      */
-    public function testMapRawDataCorrectlyIgnoresIncompleteGPSData()
+    public function testMapRawDataOnlyLatitude()
     {
         $result = $this->mapper->mapRawData(
             array(
@@ -241,7 +362,7 @@ public function testMapRawDataCorrectlyIgnoresIncompleteGPSData()
             )
         );
 
-        $this->assertCount(0, $result);
+        $this->assertCount(1, $result);
     }
 
     /**
@@ -293,4 +414,179 @@ public function testMapRawDataCorrectlyIgnoresInvalidCreateDate()
             $result
         );
     }
+
+    /**
+     * @group mapper
+     * @covers \PHPExif\Mapper\Exiftool::mapRawData
+     */
+    public function testMapRawDataCorrectlyAltitude()
+    {
+        $result = $this->mapper->mapRawData(
+            array(
+                \PHPExif\Mapper\Exiftool::GPSALTITUDE  => '122.053',
+                'GPS:GPSAltitudeRef'                   => '0',
+            )
+        );
+	$expected = 122.053;
+        $this->assertEquals($expected, reset($result));
+    }
+
+    /**
+     * @group mapper
+     * @covers \PHPExif\Mapper\Exiftool::mapRawData
+     */
+    public function testMapRawDataCorrectlyNegativeAltitude()
+    {
+        $result = $this->mapper->mapRawData(
+            array(
+                \PHPExif\Mapper\Exiftool::GPSALTITUDE  => '122.053',
+                'GPS:GPSAltitudeRef'                   => '1',
+            )
+        );
+        $expected = '-122.053';
+        $this->assertEquals($expected, reset($result));
+    }
+
+    /**
+     * @group mapper
+     * @covers \PHPExif\Mapper\Exiftool::mapRawData
+     */
+    public function testMapRawDataCorrectlyFormatsQuicktimeGPSData()
+    {
+        $result = $this->mapper->mapRawData(
+            array(
+                \PHPExif\Mapper\Exiftool::GPSLATITUDE_QUICKTIME  => '40.333',
+                'GPS:GPSLatitudeRef'                             => 'North',
+                \PHPExif\Mapper\Exiftool::GPSLONGITUDE_QUICKTIME => '-20.167',
+                'GPS:GPSLongitudeRef'                            => 'West',
+            )
+        );
+        $expected_gps = '40.333,-20.167';
+        $expected_lat = '40.333';
+        $expected_lon = '-20.167';
+        $this->assertCount(3, $result);
+        $this->assertEquals($expected_gps, $result['gps']);
+        $this->assertEquals($expected_lat, $result['latitude']);
+        $this->assertEquals($expected_lon, $result['longitude']);
+    }
+
+    /**
+     * @group mapper
+     * @covers \PHPExif\Mapper\Exiftool::mapRawData
+     */
+    public function testMapRawDataCorrectlyQuicktimeAltitude()
+    {
+        $result = $this->mapper->mapRawData(
+            array(
+                \PHPExif\Mapper\Exiftool::GPSALTITUDE_QUICKTIME  => '122.053',
+                'Composite:GPSAltitudeRef'                       => '1',
+            )
+        );
+        $expected = -122.053;
+        $this->assertEquals($expected, reset($result));
+    }
+
+    /**
+     * @group mapper
+     * @covers \PHPExif\Mapper\Exiftool::mapRawData
+     */
+    public function testMapRawDataCorrectlyHeightVideo()
+    {
+
+      $rawData = array(
+          '600'  => array(
+                            \PHPExif\Mapper\Exiftool::IMAGEHEIGHT_VIDEO  => '800x600',
+                        ),
+          '600'  => array(
+                            \PHPExif\Mapper\Exiftool::IMAGEHEIGHT_VIDEO  => '800x600',
+                            'Composite:Rotation'                        => '0',
+                        ),
+          '800'  => array(
+                            \PHPExif\Mapper\Exiftool::IMAGEHEIGHT_VIDEO  => '800x600',
+                            'Composite:Rotation'                        => '90',
+                       ),
+          '800'  => array(
+                            \PHPExif\Mapper\Exiftool::IMAGEHEIGHT_VIDEO  => '800x600',
+                            'Composite:Rotation'                        => '270',
+                        ),
+          '600'  => array(
+                            \PHPExif\Mapper\Exiftool::IMAGEHEIGHT_VIDEO  => '800x600',
+                            'Composite:Rotation'                        => '360',
+                        ),
+          '600'  => array(
+                            \PHPExif\Mapper\Exiftool::IMAGEHEIGHT_VIDEO  => '800x600',
+                            'Composite:Rotation'                        => '180',
+                        ),
+      );
+
+      foreach ($rawData as $expected => $value) {
+          $mapped = $this->mapper->mapRawData($value);
+
+          $this->assertEquals($expected, $mapped['height']);
+      }
+    }
+
+
+
+        /**
+         * @group mapper
+         * @covers \PHPExif\Mapper\Exiftool::mapRawData
+         */
+        public function testMapRawDataCorrectlyWidthVideo()
+        {
+
+          $rawData = array(
+              '800'  => array(
+                                \PHPExif\Mapper\Exiftool::IMAGEWIDTH_VIDEO  => '800x600',
+                            ),
+              '800'  => array(
+                                \PHPExif\Mapper\Exiftool::IMAGEWIDTH_VIDEO  => '800x600',
+                                'Composite:Rotation'                        => '0',
+                            ),
+              '600'  => array(
+                                \PHPExif\Mapper\Exiftool::IMAGEWIDTH_VIDEO  => '800x600',
+                                'Composite:Rotation'                        => '90',
+                            ),
+              '600'  => array(
+                                \PHPExif\Mapper\Exiftool::IMAGEWIDTH_VIDEO  => '800x600',
+                                'Composite:Rotation'                        => '270',
+                            ),
+              '800'  => array(
+                                \PHPExif\Mapper\Exiftool::IMAGEWIDTH_VIDEO  => '800x600',
+                                'Composite:Rotation'                        => '360',
+                            ),
+              '800'  => array(
+                                \PHPExif\Mapper\Exiftool::IMAGEWIDTH_VIDEO  => '800x600',
+                                'Composite:Rotation'                        => '180',
+                            ),
+          );
+
+          foreach ($rawData as $expected => $value) {
+              $mapped = $this->mapper->mapRawData($value);
+
+              $this->assertEquals($expected, $mapped['width']);
+          }
+        }
+
+
+        /**
+         * @group mapper
+         * @covers \PHPExif\Mapper\Exiftool::mapRawData
+         */
+        public function testMapRawDataCorrectlyIsoFormats()
+        {
+            $expected = array(
+                '80' => array(
+                    'ExifIFD:ISO'     => '80',
+                ),
+                '800' => array(
+                    'ExifIFD:ISO'     => '800 0 0',
+                ),
+            );
+
+            foreach ($expected as $key => $value) {
+    		        $result = $this->mapper->mapRawData($value);
+    	          $this->assertEquals($key, reset($result));
+            }
+        }
 }
diff --git a/tests/PHPExif/Mapper/FFprobeMapperTest.php b/tests/PHPExif/Mapper/FFprobeMapperTest.php
new file mode 100644
index 0000000..72a2015
--- /dev/null
+++ b/tests/PHPExif/Mapper/FFprobeMapperTest.php
@@ -0,0 +1,494 @@
+<?php
+/**
+ * @covers \PHPExif\Mapper\FFprobe::<!public>
+ */
+class FFprobeMapperTest extends \PHPUnit\Framework\TestCase
+{
+    protected $mapper;
+
+    public function setUp(): void
+    {
+        $this->mapper = new \PHPExif\Mapper\FFprobe;
+    }
+
+    /**
+     * @group mapper
+     */
+    public function testClassImplementsCorrectInterface()
+    {
+        $this->assertInstanceOf('\\PHPExif\\Mapper\\MapperInterface', $this->mapper);
+    }
+
+    /**
+     * @group mapper
+     * @covers \PHPExif\Mapper\FFprobe::mapRawData
+     */
+    public function testMapRawDataIgnoresFieldIfItDoesntExist()
+    {
+        $rawData = array('foo' => 'bar');
+        $mapped = $this->mapper->mapRawData($rawData);
+
+        $this->assertCount(0, $mapped);
+    }
+
+    /**
+     * @group mapper
+     * @covers \PHPExif\Mapper\FFprobe::mapRawData
+     */
+    public function testMapRawDataMapsFieldsCorrectly()
+    {
+        $reflProp = new \ReflectionProperty(get_class($this->mapper), 'map');
+        $reflProp->setAccessible(true);
+        $map = $reflProp->getValue($this->mapper);
+
+        // ignore custom formatted data stuff:
+        unset($map[\PHPExif\Mapper\FFprobe::FILESIZE]);
+        unset($map[\PHPExif\Mapper\FFprobe::FILENAME]);
+        unset($map[\PHPExif\Mapper\FFprobe::MIMETYPE]);
+        unset($map[\PHPExif\Mapper\FFprobe::GPSLATITUDE]);
+        unset($map[\PHPExif\Mapper\FFprobe::GPSLONGITUDE]);
+        unset($map[\PHPExif\Mapper\FFprobe::QUICKTIME_GPSALTITUDE]);
+        unset($map[\PHPExif\Mapper\FFprobe::QUICKTIME_GPSLATITUDE]);
+        unset($map[\PHPExif\Mapper\FFprobe::QUICKTIME_GPSLONGITUDE]);
+        unset($map[\PHPExif\Mapper\FFprobe::QUICKTIME_DESCRIPTION]);
+        unset($map[\PHPExif\Mapper\FFprobe::QUICKTIME_MAKE]);
+        unset($map[\PHPExif\Mapper\FFprobe::QUICKTIME_MODEL]);
+        unset($map[\PHPExif\Mapper\FFprobe::QUICKTIME_CONTENTIDENTIFIER]);
+        unset($map[\PHPExif\Mapper\FFprobe::QUICKTIME_DESCRIPTION]);
+        unset($map[\PHPExif\Mapper\FFprobe::QUICKTIME_TITLE]);
+        unset($map[\PHPExif\Mapper\FFprobe::QUICKTIME_DATE]);
+        unset($map[\PHPExif\Mapper\FFprobe::QUICKTIME_KEYWORDS]);
+        unset($map[\PHPExif\Mapper\FFprobe::FRAMERATE]);
+        unset($map[\PHPExif\Mapper\FFprobe::DURATION]);
+
+        // create raw data
+        $keys = array_keys($map);
+        $values = array();
+        $values = array_pad($values, count($keys), 'foo');
+        $rawData = array_combine($keys, $values);
+
+        $mapped = $this->mapper->mapRawData($rawData);
+
+        $i = 0;
+        foreach ($mapped as $key => $value) {
+            $this->assertEquals($map[$keys[$i]], $key);
+            $i++;
+        }
+    }
+
+    /**
+     * @group mapper
+     * @covers \PHPExif\Mapper\FFprobe::mapRawData
+     */
+    public function testMapRawDataCorrectlyFormatsDateTimeOriginal()
+    {
+        $rawData = array(
+            \PHPExif\Mapper\FFprobe::DATETIMEORIGINAL => '2015:04:01 12:11:09',
+        );
+
+        $mapped = $this->mapper->mapRawData($rawData);
+
+        $result = reset($mapped);
+        $this->assertInstanceOf('\\DateTime', $result);
+        $this->assertEquals(
+            reset($rawData),
+            $result->format('Y:m:d H:i:s')
+        );
+    }
+
+
+    /**
+     * @group mapper
+     * @covers \PHPExif\Mapper\FFprobe::mapRawData
+     */
+    public function testMapRawDataCorrectlyFormatsCreationDateQuicktime()
+    {
+        $rawData = array(
+            \PHPExif\Mapper\FFprobe::QUICKTIME_DATE => '2015-04-01T12:11:09+0200',
+            \PHPExif\Mapper\FFprobe::DATETIMEORIGINAL => '2015-04-01T12:11:09.000000Z',
+        );
+
+        $mapped = $this->mapper->mapRawData($rawData);
+
+        $result = reset($mapped);
+        $this->assertInstanceOf('\\DateTime', $result);
+        $this->assertEquals(
+            '2015:04:01 12:11:09',
+            $result->format('Y:m:d H:i:s')
+        );
+        $this->assertEquals(
+            7200,
+            $result->getOffset()
+        );
+        $this->assertEquals(
+            '+02:00',
+            $result->getTimezone()->getName()
+        );
+    }
+
+    /**
+     * @group mapper
+     * @covers \PHPExif\Mapper\FFprobe::mapRawData
+     */
+    public function testMapRawDataCorrectlyFormatsCreationDateWithTimeZone()
+    {
+        $rawData = array(
+            \PHPExif\Mapper\FFprobe::DATETIMEORIGINAL => '2015:04:01 12:11:09+0200',
+        );
+
+        $mapped = $this->mapper->mapRawData($rawData);
+
+        $result = reset($mapped);
+        $this->assertInstanceOf('\\DateTime', $result);
+        $this->assertEquals(
+            '2015:04:01 12:11:09',
+            $result->format('Y:m:d H:i:s')
+        );
+        $this->assertEquals(
+            7200,
+            $result->getOffset()
+        );
+        $this->assertEquals(
+            '+02:00',
+            $result->getTimezone()->getName()
+        );
+    }
+
+    /**
+     * @group mapper
+     * @covers \PHPExif\Mapper\FFprobe::mapRawData
+     */
+    public function testMapRawDataCorrectlyIgnoresIncorrectDateTimeOriginal()
+    {
+        $rawData = array(
+            \PHPExif\Mapper\FFprobe::DATETIMEORIGINAL => '2015:04:01',
+        );
+
+        $mapped = $this->mapper->mapRawData($rawData);
+
+        $this->assertEquals(false, reset($mapped));
+    }
+
+    /**
+     * @group mapper
+     * @covers \PHPExif\Mapper\FFprobe::mapRawData
+     */
+    public function testMapRawDataCorrectlyIgnoresIncorrectDateTimeOriginal2()
+    {
+        $rawData = array(
+            \PHPExif\Mapper\FFprobe::QUICKTIME_DATE => '2015:04:01',
+        );
+
+        $mapped = $this->mapper->mapRawData($rawData);
+
+        $this->assertEquals(false, reset($mapped));
+    }
+
+    /**
+     * @group mapper
+     * @covers \PHPExif\Mapper\FFprobe::mapRawData
+     */
+    public function testMapRawDataCorrectlyFormatsQuickTimeGPSData()
+    {
+        $expected = array(
+          '+27.5916+86.5640+8850/' => array(
+                          \PHPExif\Exif::LATITUDE => '27.5916',
+                          \PHPExif\Exif::LONGITUDE => '86.5640',
+                          \PHPExif\Exif::ALTITUDE => '8850',
+                      ),
+        );
+
+
+        foreach ($expected as $key => $value) {
+		        $result = $this->mapper->mapRawData(array('com.apple.quicktime.location.ISO6709' => $key));
+
+	          $this->assertEquals($value[\PHPExif\Exif::LATITUDE], $result[\PHPExif\Exif::LATITUDE]);
+            $this->assertEquals($value[\PHPExif\Exif::LONGITUDE], $result[\PHPExif\Exif::LONGITUDE]);
+            $this->assertEquals($value[\PHPExif\Exif::ALTITUDE], $result[\PHPExif\Exif::ALTITUDE]);
+        }
+    }
+
+    /**
+     * @group mapper
+     * @covers \PHPExif\Mapper\FFprobe::mapRawData
+     */
+    public function testMapRawDataCorrectlyRotatesDimensions()
+    {
+        $expected = array(
+          '600' => array(
+                          'tags' => array('rotate' => '90'),
+                          'width' => '800',
+                          'height' => '600',
+                      ),
+        );
+
+
+        foreach ($expected as $key => $value) {
+		        $result = $this->mapper->mapRawData($value);
+
+	          $this->assertEquals($key, $result[\PHPExif\Exif::WIDTH]);
+        }
+    }
+
+    /**
+     * @group mapper
+     * @covers \PHPExif\Mapper\FFprobe::mapRawData
+     */
+    public function testMapRawDataCorrectlyFormatsGPSData()
+    {
+        $expected = array(
+            '+40.333452380952,+20.167314814815' => array(
+                'location'     => '+40.333452380952+20.167314814815/',
+            ),
+            '+0,+0' => array(
+                'location'     => '+0+0/',
+            ),
+            '+71.706936,-42.604303' => array(
+                'location'     => '+71.706936-42.604303/',
+            ),
+        );
+
+        foreach ($expected as $key => $value) {
+		        $result = $this->mapper->mapRawData($value);
+	          $this->assertEquals($key, $result[\PHPExif\Exif::GPS]);
+        }
+    }
+
+    /**
+     * @group mapper
+     * @covers \PHPExif\Mapper\FFprobe::mapRawData
+     */
+    public function testMapRawDataCorrectlyFramerate()
+    {
+        $expected = array(
+            '30' => array(
+                'avg_frame_rate'     => '30',
+            ),
+            '20' => array(
+                'avg_frame_rate'     => '200/10',
+            )
+        );
+
+        foreach ($expected as $key => $value) {
+		        $result = $this->mapper->mapRawData($value);
+	          $this->assertEquals($key, reset($result));
+        }
+    }
+
+    public function testMapRawDataCorrectlyFormatsDifferentDateTimeString()
+    {
+        $rawData = array(
+            \PHPExif\Mapper\FFprobe::DATETIMEORIGINAL => '2014-12-15 00:12:00'
+        );
+
+        $mapped = $this->mapper->mapRawData(
+            $rawData
+        );
+
+        $result = reset($mapped);
+        $this->assertInstanceOf('\DateTime', $result);
+        $this->assertEquals(
+            reset($rawData),
+            $result->format("Y-m-d H:i:s")
+        );
+    }
+
+    public function testMapRawDataCorrectlyIgnoresInvalidCreateDate()
+    {
+        $rawData = array(
+            \PHPExif\Mapper\FFprobe::DATETIMEORIGINAL => 'Invalid Date String'
+        );
+
+        $result = $this->mapper->mapRawData(
+            $rawData
+        );
+
+        $this->assertCount(0, $result);
+        $this->assertNotEquals(
+            reset($rawData),
+            $result
+        );
+    }
+
+    /**
+     * @group mapper
+     * @covers \PHPExif\Mapper\FFprobe::normalizeComponent
+     */
+    public function testNormalizeComponentCorrectly()
+    {
+        $reflMethod = new \ReflectionMethod('\PHPExif\Mapper\FFprobe', 'normalizeComponent');
+        $reflMethod->setAccessible(true);
+
+        $rawData = array(
+            '2/800' => 0.0025,
+            '1/400' => 0.0025,
+            '0/1'   => 0,
+            '1/0'   => 0,
+            '0'     => 0,
+        );
+
+        foreach ($rawData as $value => $expected) {
+            $normalized = $reflMethod->invoke($this->mapper, $value);
+
+            $this->assertEquals($expected, $normalized);
+        }
+    }
+
+    /**
+     * @group mapper
+     * @covers \PHPExif\Mapper\Native::mapRawData
+     */
+    public function testMapRawDataMatchesFieldsWithoutCaseSensibilityOnFirstLetter()
+    {
+        $rawData = array(
+            'Width' => '800',
+            'mimeType' => 'video/quicktime',
+        );
+        $mapped = $this->mapper->mapRawData($rawData);
+        $this->assertCount(2, $mapped);
+        $keys = array_keys($mapped);
+
+        $expected = array(
+            \PHPExif\Mapper\FFprobe::WIDTH,
+            \PHPExif\Mapper\FFprobe::MIMETYPE
+        );
+        $this->assertEquals($expected, $keys);
+    }
+
+    /**
+     * @group mapper
+     * @covers \PHPExif\Mapper\FFprobe::readISO6709
+     */
+    public function testreadISO6709()
+    {
+        $reflMethod = new \ReflectionMethod('\PHPExif\Mapper\FFprobe', 'readISO6709');
+        $reflMethod->setAccessible(true);
+
+        $testcase = array(
+          '+27.5916+086.5640+8850/' => array(
+                          'latitude' => '27.5916',
+                          'longitude' => '86.5640',
+                          'altitude' => '8850',
+                      ),
+          '+1234.7-09854.1/' => array(
+                          'latitude' => '12.578333333333333',
+                          'longitude' => '-98.90166666666667',
+                          'altitude' => null,
+                      ),
+          '+352139+1384339+3776/' => array(
+                          'latitude' => '35.36083333333333333333333333',
+                          'longitude' => '138.7275000000000000000000000',
+                          'altitude' => '3776',
+                      ),
+          '+40.75-074.00/' => array(
+                          'latitude' => '40.75',
+                          'longitude' => '-74',
+                          'altitude' => null,
+                      ),
+          '+123456.7-0985432.1/' => array(
+                          'latitude' => '12.58241666666666666666666667',
+                          'longitude' => '-98.90891666666666666666666667',
+                          'altitude' => null,
+                      ),
+          '-90+000+2800/' => array(
+                          'latitude' => '-90',
+                          'longitude' => '0',
+                          'altitude' => '2800',
+                      ),
+          '+35.658632+139.745411/' => array(
+                          'latitude' => '35.658632',
+                          'longitude' => '139.745411',
+                          'altitude' => null,
+                      ),
+          '+48.8577+002.295/' => array(
+                          'latitude' => '48.8577',
+                          'longitude' => '2.295',
+                          'altitude' => null,
+                      ),
+          '+48.8577+002.295-50/' => array(
+                          'latitude' => '48.8577',
+                          'longitude' => '2.295',
+                          'altitude' => '-50',
+                      ),
+        );
+
+        foreach ($testcase as $key => $expected) {
+          $result = $reflMethod->invoke($this->mapper, $key);
+          $this->assertEquals($expected, $result);
+        }
+    }
+
+    /**
+     * @group mapper
+     * @covers \PHPExif\Mapper\FFprobe::convertDMStoDecimal
+     */
+    public function testconvertDMStoDecimal()
+    {
+
+        $reflMethod = new \ReflectionMethod('\PHPExif\Mapper\FFprobe', 'convertDMStoDecimal');
+        $reflMethod->setAccessible(true);
+
+        $testcase = array(
+          '+27.5916' => array(
+                          'sign' => '+',
+                          'degrees' => '27',
+                          'minutes' => '',
+                          'seconds' => '',
+                          'fraction' => '.5916',
+                      ),
+          '+86.5640' => array(
+                          'sign' => '+',
+                          'degrees' => '86',
+                          'minutes' => '',
+                          'seconds' => '',
+                          'fraction' => '.5640',
+                      ),
+          '12.578333333333333' => array(
+                          'sign' => '+',
+                          'degrees' => '12',
+                          'minutes' => '34',
+                          'seconds' => '',
+                          'fraction' => '.7',
+                      ),
+          '-98.90166666666666666666666667' => array(
+                          'sign' => '-',
+                          'degrees' => '098',
+                          'minutes' => '54',
+                          'seconds' => '',
+                          'fraction' => '.1',
+                      ),
+          '+35.36083333333333333333333333' => array(
+                          'sign' => '+',
+                          'degrees' => '35',
+                          'minutes' => '21',
+                          'seconds' => '39',
+                          'fraction' => '',
+                      ),
+          '+138.7275000000000000000000000' => array(
+                          'sign' => '+',
+                          'degrees' => '138',
+                          'minutes' => '43',
+                          'seconds' => '39',
+                          'fraction' => '',
+                      ),
+          '12.58241666666666666666666667' => array(
+                          'sign' => '+',
+                          'degrees' => '12',
+                          'minutes' => '34',
+                          'seconds' => '56',
+                          'fraction' => '.7',
+                      ),
+          '-98.90891666666666666666666667' => array(
+                          'sign' => '-',
+                          'degrees' => '098',
+                          'minutes' => '54',
+                          'seconds' => '32',
+                          'fraction' => '.1',
+                      ),
+        );
+        foreach ($testcase as $expected => $key) {
+          $result = $reflMethod->invoke($this->mapper, $key['sign'], $key['degrees'],$key['minutes'],$key['seconds'],$key['fraction']);
+          $this->assertEquals($expected, $result);
+        }
+    }
+}
diff --git a/tests/PHPExif/Mapper/NativeMapperTest.php b/tests/PHPExif/Mapper/NativeMapperTest.php
index d1c6228..92142db 100644
--- a/tests/PHPExif/Mapper/NativeMapperTest.php
+++ b/tests/PHPExif/Mapper/NativeMapperTest.php
@@ -2,11 +2,11 @@
 /**
  * @covers \PHPExif\Mapper\Native::<!public>
  */
-class NativeMapperTest extends \PHPUnit_Framework_TestCase
+class NativeMapperTest extends \PHPUnit\Framework\TestCase
 {
     protected $mapper;
 
-    public function setUp()
+    public function setUp(): void
     {
         $this->mapper = new \PHPExif\Mapper\Native;
     }
@@ -49,6 +49,12 @@ public function testMapRawDataMapsFieldsCorrectly()
         unset($map[\PHPExif\Mapper\Native::YRESOLUTION]);
         unset($map[\PHPExif\Mapper\Native::GPSLATITUDE]);
         unset($map[\PHPExif\Mapper\Native::GPSLONGITUDE]);
+        unset($map[\PHPExif\Mapper\Native::FRAMERATE]);
+        unset($map[\PHPExif\Mapper\Native::DURATION]);
+        unset($map[\PHPExif\Mapper\Native::CITY]);
+        unset($map[\PHPExif\Mapper\Native::SUBLOCATION]);
+        unset($map[\PHPExif\Mapper\Native::STATE]);
+        unset($map[\PHPExif\Mapper\Native::COUNTRY]);
 
         // create raw data
         $keys = array_keys($map);
@@ -86,6 +92,64 @@ public function testMapRawDataCorrectlyFormatsDateTimeOriginal()
         );
     }
 
+
+        /**
+         * @group mapper
+         * @covers \PHPExif\Mapper\Native::mapRawData
+         */
+        public function testMapRawDataCorrectlyFormatsCreationDateWithTimeZone()
+        {
+            $rawData = array(
+                \PHPExif\Mapper\Native::DATETIMEORIGINAL => '2015:04:01 12:11:09+0200',
+            );
+
+            $mapped = $this->mapper->mapRawData($rawData);
+
+            $result = reset($mapped);
+            $this->assertInstanceOf('\\DateTime', $result);
+            $this->assertEquals(
+                '2015:04:01 12:11:09',
+                $result->format('Y:m:d H:i:s')
+            );
+            $this->assertEquals(
+                7200,
+                $result->getOffset()
+            );
+            $this->assertEquals(
+                '+02:00',
+                $result->getTimezone()->getName()
+            );
+        }
+
+        /**
+         * @group mapper
+         * @covers \PHPExif\Mapper\Native::mapRawData
+         */
+        public function testMapRawDataCorrectlyFormatsCreationDateWithTimeZone2()
+        {
+            $rawData = array(
+                \PHPExif\Mapper\Native::DATETIMEORIGINAL => '2015:04:01 12:11:09',
+                'UndefinedTag:0x9011' => '+0200',
+            );
+
+            $mapped = $this->mapper->mapRawData($rawData);
+
+            $result = reset($mapped);
+            $this->assertInstanceOf('\\DateTime', $result);
+            $this->assertEquals(
+                '2015:04:01 12:11:09',
+                $result->format('Y:m:d H:i:s')
+            );
+            $this->assertEquals(
+                7200,
+                $result->getOffset()
+            );
+            $this->assertEquals(
+                '+02:00',
+                $result->getTimezone()->getName()
+            );
+        }
+
     /**
      * @group mapper
      * @covers \PHPExif\Mapper\Native::mapRawData
@@ -210,7 +274,7 @@ public function testMapRawDataFlattensRawDataWithSections()
      * @group mapper
      * @covers \PHPExif\Mapper\Native::mapRawData
      */
-    public function testMapRawDataMacthesFieldsWithoutCaseSensibilityOnFirstLetter()
+    public function testMapRawDataMatchesFieldsWithoutCaseSensibilityOnFirstLetter()
     {
         $rawData = array(
             \PHPExif\Mapper\Native::ORIENTATION => 'Portrait',
@@ -255,8 +319,31 @@ public function testMapRawDataCorrectlyFormatsGPSData()
         );
 
         foreach ($expected as $key => $value) {
-            $result = $this->mapper->mapRawData($value);
-            $this->assertEquals($key, reset($result));
+		        $result = $this->mapper->mapRawData($value);
+	          $this->assertEquals($key, $result[\PHPExif\Exif::GPS]);
+        }
+    }
+
+    /**
+     * @group mapper
+     * @covers \PHPExif\Mapper\Native::mapRawData
+     */
+    public function testMapRawDataCorrectlyFormatsAltitudeData()
+    {
+        $expected = array(
+            8848.0 => array(
+                'GPSAltitude'     => '8848',
+                'GPSAltitudeRef'  => '0',
+            ),
+            -10994.0 => array(
+                'GPSAltitude'     => '10994',
+                'GPSAltitudeRef'  => '1',
+            ),
+        );
+
+        foreach ($expected as $key => $value) {
+		        $result = $this->mapper->mapRawData($value);
+	          $this->assertEquals($key, $result[\PHPExif\Exif::ALTITUDE]);
         }
     }
 
@@ -317,4 +404,25 @@ public function testNormalizeComponentCorrectly()
             $this->assertEquals($expected, $normalized);
         }
     }
+
+    /**
+     * @group mapper
+     * @covers \PHPExif\Mapper\Native::mapRawData
+     */
+    public function testMapRawDataCorrectlyIsoFormats()
+    {
+        $expected = array(
+            '80' => array(
+                'ISOSpeedRatings'     => '80',
+            ),
+            '800' => array(
+                'ISOSpeedRatings'     => '800 0 0',
+            ),
+        );
+
+        foreach ($expected as $key => $value) {
+		        $result = $this->mapper->mapRawData($value);
+	          $this->assertEquals($key, reset($result));
+        }
+    }
 }
diff --git a/tests/PHPExif/Reader/ReaderTest.php b/tests/PHPExif/Reader/ReaderTest.php
index f84a0cf..177218c 100644
--- a/tests/PHPExif/Reader/ReaderTest.php
+++ b/tests/PHPExif/Reader/ReaderTest.php
@@ -1,10 +1,9 @@
 <?php
 /**
  * @covers \PHPExif\Reader\Reader::<!public>
- * @covers \PHPExif\Reader\ReaderInterface
  * @covers \PHPExif\Adapter\NoAdapterException
  */
-class ReaderTest extends \PHPUnit_Framework_TestCase
+class ReaderTest extends \PHPUnit\Framework\TestCase
 {
     /**
      * @var \PHPExif\Reader\Reader
@@ -14,7 +13,7 @@ class ReaderTest extends \PHPUnit_Framework_TestCase
     /**
      * Setup function before the tests
      */
-    public function setUp()
+    public function setUp() : void
     {
         $adapter = $this->getMockBuilder('\PHPExif\Adapter\AdapterInterface')->getMockForAbstractClass();
         $this->reader = new \PHPExif\Reader\Reader($adapter);
@@ -54,10 +53,10 @@ public function testGetAdapterFromProperty()
      * @group reader
      * @covers \PHPExif\Reader\Reader::getAdapter
      * @covers \PHPExif\Adapter\NoAdapterException
-     * @expectedException \PHPExif\Adapter\NoAdapterException
      */
     public function testGetAdapterThrowsExceptionWhenNoAdapterIsSet()
     {
+        $this->expectException('\PHPExif\Adapter\NoAdapterException');
         $reflProperty = new \ReflectionProperty('\PHPExif\Reader\Reader', 'adapter');
         $reflProperty->setAccessible(true);
         $reflProperty->setValue($this->reader, null);
@@ -84,10 +83,10 @@ public function testGetExifPassedToAdapter()
     /**
      * @group reader
      * @covers \PHPExif\Reader\Reader::factory
-     * @expectedException InvalidArgumentException
      */
     public function testFactoryThrowsException()
     {
+        $this->expectException('InvalidArgumentException');
         \PHPExif\Reader\Reader::factory('foo');
     }
 
@@ -132,6 +131,21 @@ public function testFactoryAdapterTypeExiftool()
         $this->assertInstanceOf('\PHPExif\Adapter\Exiftool', $adapter);
     }
 
+    /**
+     * @group reader
+     * @covers \PHPExif\Reader\Reader::factory
+     */
+    public function testFactoryAdapterTypeFFprobe()
+    {
+        $reader = \PHPExif\Reader\Reader::factory(\PHPExif\Reader\Reader::TYPE_FFPROBE);
+        $reflProperty = new \ReflectionProperty('\PHPExif\Reader\Reader', 'adapter');
+        $reflProperty->setAccessible(true);
+
+        $adapter = $reflProperty->getValue($reader);
+
+        $this->assertInstanceOf('\PHPExif\Adapter\FFprobe', $adapter);
+    }
+
     /**
      * @group reader
      * @covers \PHPExif\Reader\Reader::getExifFromFile
@@ -155,4 +169,3 @@ public function testGetExifFromFileCallsReadMethod()
         $this->assertEquals($expectedResult, $result);
     }
 }
-
diff --git a/tests/files/IMG_3824.MOV b/tests/files/IMG_3824.MOV
new file mode 100644
index 0000000..fa0aaf0
Binary files /dev/null and b/tests/files/IMG_3824.MOV differ
diff --git a/tests/files/IMG_3825.MOV b/tests/files/IMG_3825.MOV
new file mode 100644
index 0000000..34de79c
Binary files /dev/null and b/tests/files/IMG_3825.MOV differ