/** * @return ExifReader */ private static function instance() { if (self::$instance === null) { self::$instance = ExifReader::factory(ExifReader::TYPE_NATIVE); } return self::$instance; }
/** * @return ReaderInterface */ protected function getInstance() { if (!isset($this->instance)) { $this->instance = Reader::factory($this->type); } return $this->instance; }
public function boot() { FileBase::extend(function ($model) { $model->hasOne['exif'] = ['Hambern\\Exify\\Models\\Exif', 'delete' => true]; }); FileBase::extend(function ($model) { $model->bindEvent('model.afterCreate', function () use($model) { if (strpos($model->content_type, 'image') !== false) { $reader = Reader::factory(Reader::TYPE_NATIVE); $path = 'http://' . $_SERVER['SERVER_NAME'] . $model->path; $data = $reader->read($path)->getData(); foreach ($data as $k => $v) { $fill[snake_case($k)] = $v; } $exif = Exif::make($fill); $model->exif()->save($exif); } }); $model->bindEvent('model.beforeDelete', function () use($model) { if (strpos($model->content_type, 'image') !== false) { $model->exif()->delete(); } }); }); }
/** * Crops avatar image to square and corrects orientation. * * @return void */ private function cropAvatar() { // Image manipulation class part of codesleeve/laravel-stapler. $imagine = new Imagine(); // Gets full path of uploaded avatar, creates Imagine object to manipulate image. $avatarPath = \Auth::user()->avatar->url(); $image = $imagine->open(public_path() . $avatarPath); // Reads EXIF data with a wrapper around native exif_read_data() PHP function. $reader = Reader::factory(Reader::TYPE_NATIVE); $exifData = $reader->getExifFromFile(public_path() . $avatarPath)->getRawData(); $width = NULL; $height = NULL; if (array_key_exists('ExifImageWidth', $exifData) && array_key_exists('ExifImageLength')) { $width = $exifData['ExifImageWidth']; $height = $exifData['ExifImageLength']; } else { if (array_key_exists('COMPUTED', $exifData)) { $width = $exifData['COMPUTED']['Width']; $height = $exifData['COMPUTED']['Height']; } else { return false; } } // Crops off top and bottom for tall images. if ($height > $width) { $start = ($height - $width) / 2; $image->crop(new Point(0, $start), new Box($width, $width)); } else { $start = ($width - $height) / 2; $image->crop(new Point($start, 0), new Box($height, $height)); } // In case image does not have all EXIF data, including orientation. // Setting EXIF orientation to 1 stores the image as it is originally // oriented. if (!array_key_exists('Orientation', $exifData)) { $exifData['Orientation'] = 1; } // Adjusts orientation depending on EXIF orientation data. switch ($exifData['Orientation']) { // Rotates image if it is upside down. case 3: $image->rotate(180); break; // Rotates image 90 degrees to the right. // Rotates image 90 degrees to the right. case 6: $image->rotate(90); break; // Rotates image 90 degrees to the left. // Rotates image 90 degrees to the left. case 8: $image->rotate(-90); break; } // Replaces original avatar and ensures previous image is deleted. File::delete(public_path() . $avatarPath); $image->save(public_path() . $avatarPath); return true; }
/** * Get an array with the dimensions of an image, together with its * aspectratio and some other info. * * @param string $filename * @param boolean $safe * * @return array Specifics */ public function imageInfo($filename, $safe) { // This function is vulnerable to path traversal, so blocking it in // safe mode for now. if ($safe) { return null; } $fullpath = sprintf('%s/%s', $this->app['resources']->getPath('filespath'), $filename); if (!is_readable($fullpath) || !is_file($fullpath)) { return false; } $types = array(0 => 'unknown', 1 => 'gif', 2 => 'jpeg', 3 => 'png', 4 => 'swf', 5 => 'psd', 6 => 'bmp'); // Get the dimensions of the image $imagesize = getimagesize($fullpath); // Get the aspectratio if ($imagesize[1] > 0) { $ar = $imagesize[0] / $imagesize[1]; } else { $ar = 0; } $info = array('width' => $imagesize[0], 'height' => $imagesize[1], 'type' => $types[$imagesize[2]], 'mime' => $imagesize['mime'], 'aspectratio' => $ar, 'filename' => $filename, 'fullpath' => realpath($fullpath), 'url' => str_replace('//', '/', $this->app['resources']->getUrl('files') . $filename)); /** @var $reader \PHPExif\Reader\Reader */ $reader = ExifReader::factory(ExifReader::TYPE_NATIVE); try { // Get the EXIF data of the image $exif = $reader->read($fullpath); } catch (\RuntimeException $e) { // No EXIF data… create an empty object. $exif = new Exif(); } // GPS coordinates $gps = $exif->getGPS(); $gps = explode(',', $gps); // If the picture is turned by exif, ouput the turned aspectratio if (in_array($exif->getOrientation(), array(6, 7, 8))) { $exifturned = $imagesize[1] / $imagesize[0]; } else { $exifturned = $ar; } // Output the relevant EXIF info $info['exif'] = array('latitude' => isset($gps[0]) ? $gps[0] : false, 'longitude' => isset($gps[1]) ? $gps[1] : false, 'datetime' => $exif->getCreationDate(), 'orientation' => $exif->getOrientation(), 'aspectratio' => $exifturned ?: false); // Landscape if aspectratio > 5:4 $info['landscape'] = $ar >= 1.25 ? true : false; // Portrait if aspectratio < 4:5 $info['portrait'] = $ar <= 0.8 ? true : false; // Square-ish, if neither portrait or landscape $info['square'] = !$info['landscape'] && !$info['portrait']; return $info; }
public function __construct() { $this->exif = Reader::factory(Reader::TYPE_NATIVE); }
/** * Get an EXIF object. * * @return \PHPExif\Exif */ protected function getExif() { /** @var $reader \PHPExif\Reader\Reader */ $reader = ExifReader::factory(ExifReader::TYPE_NATIVE); try { // Get the EXIF data of the image $exif = $reader->read($this->fullpath); } catch (\RuntimeException $e) { // No EXIF data… create an empty object. $exif = new Exif(); } return $exif; }
/** * Execute the console command. * * @return mixed */ public function handle() { //for reporting $reporting = []; $reporting['photo-but-no-metadata-date'] = 0; $reporting['photo-but-corrupt-metadata-date'] = 0; $reporting['probably-not-a-photo'] = 0; $reporting['photos-renamed-for-copy-to-destination'] = 0; $reporting['photos-clobbered-for-copy-to-destination'] = 0; //get the inputs $sourceFolder = $this->argument('sourcefolder'); $destinationFolder = $this->argument('destinationfolder'); $createOnlyContentfulFolders = $this->option('create-only-contentful-folders'); //support for this is TODO $createDayFolders = $this->option('create-day-folders'); $priorityDateForSort = $this->option('priority-date-for-sort'); $onDuplicate = $this->option('on-duplicate'); $separateVideosAndPhotos = $this->option('separate-videos-and-photos'); //----input sanity checks---------------------------------------------- //set allowed options for --priority-date-for-sort (-p) $allowedPriorityDateForSort = ['metadata', 'filemodified']; //check option passed for --priority-date-for-sort (-p) is allowed if (!in_array($priorityDateForSort, $allowedPriorityDateForSort)) { $this->error('--priority-date-for-sort (-p) must be one of ' . implode(', ', $allowedPriorityDateForSort)); exit(1); //non-zero } //set allowed options for --on-duplicate (-o) $allowedOnDuplicate = ['rename', 'clobber']; //check option passed for --on-duplicate (-o) is allowed if (!in_array($onDuplicate, $allowedOnDuplicate)) { $this->error('--on-duplicate (-o) must be one of ' . implode(', ', $allowedOnDuplicate)); exit(1); //non-zero } //check $sourceFolder and $destinationFolder look and smell like folders //if(!is_dir($sourceFolder) or !is_dir($destinationFolder)) if (!Storage::has($sourceFolder) or !Storage::has($destinationFolder)) { $this->error('Please ensure that sourcefolder and destinationfolder are actually folders'); exit(1); //non-zero } //----/end input sanity checks----------------------------------------- // reader with Native adapter $reader = \PHPExif\Reader\Reader::factory(\PHPExif\Reader\Reader::TYPE_NATIVE); // reader with Exiftool adapter //$reader = \PHPExif\Reader\Reader::factory(\PHPExif\Reader\Reader::TYPE_EXIFTOOL); //$exif = $reader->read('/path/to/file'); $files = Storage::allFiles($sourceFolder); $bar = $this->output->createProgressBar(count($files)); //$counter = 1; foreach ($files as $file) { //DUMP($counter . '] ' . $file); //string filepath //DUMP($file . '---' . pathinfo(storage_path('app') . '/' . $file, PATHINFO_EXTENSION) . '----' . Storage::getMimetype($file)); //$counter++; //CONTINUE; try { $exif = $reader->read(storage_path('app') . '/' . $file); //what a good Storage:: way to do this (get the full path)? //date from metadata $metadataDate = $exif->getCreationdate(); //will be false or a DateTime (can be a goofy, invalid DateTime if date is missing or corrupt in the metadata) //just for reporting if (!$metadataDate) { $reporting['photo-but-no-metadata-date']++; } } catch (Exception $e) { $metadataDate = false; $reporting['probably-not-a-photo']++; } $metadataDateValid = false; if ($metadataDate instanceof DateTime) { $metadataDateValid = checkdate($metadataDate->format('m'), $metadataDate->format('d'), $metadataDate->format('Y')); if (!$metadataDateValid) { $this->error("Got a DateTime from {$file} metadata but it wasn't valid [{$metadataDate->format('Y-m-d')}]. Using filemodified date instead."); $reporting['photo-but-corrupt-metadata-date']++; } } //DUMP($metadataDate); //date from filemodified $filemodifiedDate = new DateTime(); $filemodifiedDate->setTimestamp(Storage::lastModified($file)); //we assume lastModified timestamp is always present on a file //DUMP($metadataDate); //DUMP($filemodifiedDate); //DUMP(); //DUMP(date('Y m d', Storage::lastModified($file))); //a timestamp //DUMP('a time: ' . date('Y m d', fileatime(storage_path('app') . '/' . $file))); //DUMP('c time: ' . date('Y m d', filectime(storage_path('app') . '/' . $file))); //DUMP('m time: ' . date('Y m d', filemtime(storage_path('app') . '/' . $file))); //DUMP('----'); //we are good to go from here $dateToUse = ''; if ($priorityDateForSort == 'metadata') { $dateToUse = $metadataDate; if (!$metadataDate or !$metadataDateValid) { $dateToUse = $filemodifiedDate; } } elseif ($priorityDateForSort == 'filemodified') { $dateToUse = $filemodifiedDate; } //create folder for this date in the (already existing) destinationfolder //and copy the file over $year = $dateToUse->format('Y'); $month = $dateToUse->format('m'); $day = $dateToUse->format('d'); $targetSubfolder = "{$year}/{$month}/"; //support --create-day-folders if ($createDayFolders) { $targetSubfolder = "{$year}/{$month}/{$day}/"; } //support --separate-videos-and-photos if ($separateVideosAndPhotos) { $mimetype = Storage::getMimetype($file); if (str_contains($mimetype, 'video') or strtolower(pathinfo(storage_path('app') . '/' . $file, PATHINFO_EXTENSION)) == 'mts') { $targetSubfolder = "videos/{$targetSubfolder}"; } else { $targetSubfolder = "photos/{$targetSubfolder}"; } } $targetFolder = "{$destinationFolder}/{$targetSubfolder}"; $finalDestination = $targetFolder . pathinfo($file, PATHINFO_BASENAME); try { Storage::makeDirectory($targetFolder); Storage::copy($file, $finalDestination); } catch (\League\Flysystem\FileExistsException $exception) { if ($onDuplicate == 'rename') { $reporting['photos-renamed-for-copy-to-destination']++; $pathParts = pathinfo($file); //will not contain 'extension' index if file has no extension if (!array_key_exists('extension', $pathParts)) { $pathParts['extension'] = ''; } $newFinalDestination = $targetFolder . $pathParts['filename'] . '_alt_' . mt_rand(1111, 9999) . '.' . $pathParts['extension']; $this->error("{$finalDestination} already exists in destinationfolder. Renaming to {$newFinalDestination}"); Storage::copy($file, $newFinalDestination); } elseif ($onDuplicate == 'clobber') { //NOTE this is more of a NOP than an actual overwrite clobber (ie. first one is kept) $reporting['photos-clobbered-for-copy-to-destination']++; } } $bar->advance(); } $bar->finish(); //summary report $dataRows = []; foreach ($reporting as $key => $value) { $dataRows[] = [$key, $value]; } $this->table(['Entity', 'Count'], $dataRows); }
/** * @param string $file * * @return Exif */ protected static function readExif($file) { if (static::$exifReader === null) { static::$exifReader = Reader::factory(Reader::TYPE_NATIVE); } try { $exif = static::$exifReader->read($file); return Exif::cast($exif); } catch (\RuntimeException $e) { return new Exif(); } }
/** * @group reader * @covers \PHPExif\Reader\Reader::factory */ public function testFactoryAdapterTypeExiftool() { $reader = \PHPExif\Reader\Reader::factory(\PHPExif\Reader\Reader::TYPE_EXIFTOOL); $reflProperty = new \ReflectionProperty('\\PHPExif\\Reader\\Reader', 'adapter'); $reflProperty->setAccessible(true); $adapter = $reflProperty->getValue($reader); $this->assertInstanceOf('\\PHPExif\\Adapter\\Exiftool', $adapter); }