From c258303700db4bf7447e0dc6e78cb2412faab128 Mon Sep 17 00:00:00 2001 From: Andy Heathershaw Date: Sun, 17 Sep 2017 09:20:35 +0100 Subject: [PATCH] #41: Read and display more photographer-specific details --- app/Helpers/MiscHelper.php | 44 ++++++++++ app/Photo.php | 3 + app/Services/PhotoService.php | 87 +++++++++++++++++-- ..._161742_add_photography_pillar_columns.php | 40 +++++++++ resources/lang/en/gallery.php | 11 +++ .../views/themes/base/gallery/photo.blade.php | 50 ++++++++--- 6 files changed, 218 insertions(+), 17 deletions(-) create mode 100644 database/migrations/2017_09_16_161742_add_photography_pillar_columns.php diff --git a/app/Helpers/MiscHelper.php b/app/Helpers/MiscHelper.php index 4ae022a..71d9a5e 100644 --- a/app/Helpers/MiscHelper.php +++ b/app/Helpers/MiscHelper.php @@ -52,6 +52,50 @@ class MiscHelper return (int) $val; } + /** + * Convert a decimal (e.g. 3.5) to a fraction (e.g. 7/2). + * Adapted from: http://jonisalonen.com/2012/converting-decimal-numbers-to-ratios/ + * + * @param float $decimal the decimal number. + * + * @return array|bool a 1/2 would be [1, 2] array (this can be imploded with '/' to form a string) + */ + public static function decimalToFraction($decimal) + { + if ($decimal < 0 || !is_numeric($decimal)) { + // Negative digits need to be passed in as positive numbers + // and prefixed as negative once the response is imploded. + return false; + } + if ($decimal == 0) { + return [0, 0]; + } + + $tolerance = 1.e-4; + + $numerator = 1; + $h2 = 0; + $denominator = 0; + $k2 = 1; + $b = 1 / $decimal; + do { + $b = 1 / $b; + $a = floor($b); + $aux = $numerator; + $numerator = $a * $numerator + $h2; + $h2 = $aux; + $aux = $denominator; + $denominator = $a * $denominator + $k2; + $k2 = $aux; + $b = $b - $a; + } while (abs($decimal - $numerator / $denominator) > $decimal * $tolerance); + + return [ + $numerator, + $denominator + ]; + } + public static function getEnvironmentFilePath() { return sprintf('%s/.env', dirname(dirname(__DIR__))); diff --git a/app/Photo.php b/app/Photo.php index 520ab24..486e979 100644 --- a/app/Photo.php +++ b/app/Photo.php @@ -32,6 +32,9 @@ class Photo extends Model 'height', 'is_analysed', 'raw_exif_data', + 'aperture_fnumber', + 'iso_number', + 'shutter_speed', 'created_at', 'updated_at' ]; diff --git a/app/Services/PhotoService.php b/app/Services/PhotoService.php index af069e8..cfce2a0 100644 --- a/app/Services/PhotoService.php +++ b/app/Services/PhotoService.php @@ -6,6 +6,7 @@ use App\Album; use App\AlbumSources\IAlbumSource; use App\Helpers\FileHelper; use App\Helpers\ImageHelper; +use App\Helpers\MiscHelper; use App\Helpers\ThemeHelper; use App\Photo; use Symfony\Component\HttpFoundation\File\File; @@ -68,18 +69,23 @@ class PhotoService $this->photo->mime_type = $imageInfo['mime']; // Read the Exif data - $exifData = @exif_read_data($photoFile); - $isExifDataFound = ($exifData !== false && is_array($exifData)); - - if (is_null($this->photo->raw_exif_data)) + if (empty($this->photo->raw_exif_data)) { + $exifData = @exif_read_data($photoFile); + $isExifDataFound = ($exifData !== false && is_array($exifData)); $this->photo->raw_exif_data = $isExifDataFound ? base64_encode(serialize($exifData)) : ''; } + else + { + $exifData = unserialize(base64_decode($this->photo->raw_exif_data)); + $isExifDataFound = ($exifData !== false && is_array($exifData)); + } $angleToRotate = 0; - // If Exif data contains an Orientation, ensure we rotate the original image as such - if ($isExifDataFound && isset($exifData['Orientation'])) + // If Exif data contains an Orientation, ensure we rotate the original image as such (providing we don't + // currently have a metadata version - i.e. it hasn't been read and rotated already before) + if ($isExifDataFound && isset($exifData['Orientation']) && is_null($this->photo->metadata_version)) { switch ($exifData['Orientation']) { @@ -117,6 +123,10 @@ class PhotoService $this->photo->camera_make = $this->metadataCameraMake($exifData, $this->photo->camera_make); $this->photo->camera_model = $this->metadataCameraModel($exifData, $this->photo->camera_model); $this->photo->camera_software = $this->metadataCameraSoftware($exifData, $this->photo->camera_software); + $this->photo->aperture_fnumber = $this->metadataApertureFNumber($exifData, $this->photo->aperture_fnumber); + $this->photo->iso_number = $this->metadataIsoNumber($exifData, $this->photo->iso_number); + $this->photo->focal_length = $this->metadataFocalLength($exifData, $this->photo->focal_length); + $this->photo->shutter_speed = $this->metadataExposureTime($exifData, $this->photo->shutter_speed); } $this->photo->is_analysed = true; @@ -288,6 +298,22 @@ class PhotoService @unlink($photoPath); } + private function calculateValueFromFraction($input) + { + $split = explode('/', $input); + if (count($split) != 2) + { + return $split; + } + + $numerator = intval($split[0]); + $denominator = intval($split[1]); + + return $denominator == 0 + ? 0 + : ($numerator / $denominator); + } + private function downloadToTemporaryFolder() { $photoPath = tempnam(sys_get_temp_dir(), 'BlueTwilight_'); @@ -306,6 +332,23 @@ class PhotoService return $photoPath; } + private function metadataApertureFNumber(array $exifData, $originalValue = null) + { + if (isset($exifData['FNumber'])) + { + $value = $this->calculateValueFromFraction($exifData['FNumber']); + + if (intval($value) === $value) + { + return sprintf('f/%d', $value); + } + + return sprintf('f/%0.1f', $value); + } + + return $originalValue; + } + private function metadataCameraMake(array $exifData, $originalValue = null) { if (isset($exifData['Make'])) @@ -355,4 +398,36 @@ class PhotoService return preg_replace('/^([\d]{4}):([\d]{2}):([\d]{2})/', '$1-$2-$3', $dateTime); } + + private function metadataExposureTime(array $exifData, $originalValue = null) + { + if (isset($exifData['ExposureTime'])) + { + $decimal = $this->calculateValueFromFraction($exifData['ExposureTime']); + $fraction = MiscHelper::decimalToFraction($decimal); + return sprintf('%d/%d', $fraction[0], $fraction[1]); + } + + return $originalValue; + } + + private function metadataFocalLength(array $exifData, $originalValue = null) + { + if (isset($exifData['FocalLength'])) + { + return $this->calculateValueFromFraction($exifData['FocalLength']); + } + + return $originalValue; + } + + private function metadataIsoNumber(array $exifData, $originalValue = null) + { + if (isset($exifData['ISOSpeedRatings'])) + { + return $exifData['ISOSpeedRatings']; + } + + return $originalValue; + } } \ No newline at end of file diff --git a/database/migrations/2017_09_16_161742_add_photography_pillar_columns.php b/database/migrations/2017_09_16_161742_add_photography_pillar_columns.php new file mode 100644 index 0000000..d8249a0 --- /dev/null +++ b/database/migrations/2017_09_16_161742_add_photography_pillar_columns.php @@ -0,0 +1,40 @@ +string('aperture_fnumber', 20)->nullable(true); + $table->string('iso_number', 20)->nullable(true); + $table->string('shutter_speed', 20)->nullable(true); + $table->string('focal_length', 20)->nullable(true); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('photos', function (Blueprint $table) + { + $table->dropColumn('aperture_fnumber'); + $table->dropColumn('iso_number'); + $table->dropColumn('shutter_speed'); + $table->dropColumn('focal_length'); + }); + } +} diff --git a/resources/lang/en/gallery.php b/resources/lang/en/gallery.php index f210a5f..6d2c0c4 100644 --- a/resources/lang/en/gallery.php +++ b/resources/lang/en/gallery.php @@ -7,9 +7,18 @@ return [ 'default' => 'Default', 'slideshow' => 'Slideshow' ], + 'aperture' => 'Aperture:', 'back_to_album' => 'Back to :name', + 'camera_make' => 'Camera make:', + 'camera_model' => 'Camera model:', + 'camera_software' => 'Camera software:', + 'date_taken' => 'Date taken:', + 'file_name' => 'File name:', + 'focal_length' => 'Focal length:', + 'focal_length_units' => ':valuemm', 'index_no_results_heading' => 'Start something amazing', 'index_no_results_text' => 'This gallery is currently empty. If you are the owner of this gallery, you can create new albums and upload photos using the :admin_link.', + 'iso_rating' => 'ISO speed rating:', 'label_intro' => 'All photos tagged with the label ":name".', 'label_no_results_text' => 'No photos are tagged with the label ":name".', 'label_no_results_text_2' => 'If you are an admin of this gallery, you can upload and tag photos in the :admin_link.', @@ -25,6 +34,8 @@ return [ 'previous_button' => '« Previous Photo', 'show_more_labels' => '... and :count other|... and :count others', 'show_raw_exif_data' => 'Show all EXIF data', + 'shutter_speed' => 'Shutter speed:', + 'shutter_speed_units' => ':value sec.', 'statistics' => [ 'album_by_photos' => 'Top 10 largest albums - number of photos', 'album_by_size' => 'Top 10 largest albums - photo size (MB)', diff --git a/resources/views/themes/base/gallery/photo.blade.php b/resources/views/themes/base/gallery/photo.blade.php index 2b9a047..2cc23eb 100644 --- a/resources/views/themes/base/gallery/photo.blade.php +++ b/resources/views/themes/base/gallery/photo.blade.php @@ -21,7 +21,7 @@
-