From 6bf18e1eb09045ef0225063d2a60b0e6a955cd2a Mon Sep 17 00:00:00 2001 From: Derek Leadbetter Date: Mon, 18 Jul 2022 12:04:46 -0400 Subject: [PATCH] IIIF #5 - Updating resource to extract EXIF data after save --- Gemfile | 3 + Gemfile.lock | 2 + app/models/concerns/attachable.rb | 2 +- app/models/resource.rb | 15 ++- app/services/{images => iiif}/manifest.rb | 2 +- app/services/images/exif.rb | 28 +++++ client/src/components/ResourceExifModal.js | 102 ++++++++++++++++++ client/src/components/ResourceViewerModal.js | 30 ++++++ client/src/i18n/en.json | 7 ++ client/src/pages/Resource.js | 47 ++++---- db/migrate/20220706163617_create_resources.rb | 4 +- 11 files changed, 214 insertions(+), 28 deletions(-) rename app/services/{images => iiif}/manifest.rb (98%) create mode 100644 app/services/images/exif.rb create mode 100644 client/src/components/ResourceExifModal.js create mode 100644 client/src/components/ResourceViewerModal.js diff --git a/Gemfile b/Gemfile index 9a3ae56..f1f2cae 100644 --- a/Gemfile +++ b/Gemfile @@ -42,6 +42,9 @@ gem 'aws-sdk-s3' # Image processing gem 'mini_magick', '~> 4.11' +# EXIF data +gem 'exif', '~> 2.2.3' + group :development, :test do # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem gem 'debug', platforms: %i[ mri mingw x64_mingw ] diff --git a/Gemfile.lock b/Gemfile.lock index 52153a1..4211441 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -116,6 +116,7 @@ GEM dotenv (= 2.7.6) railties (>= 3.2) erubi (1.10.0) + exif (2.2.3) globalid (1.0.0) activesupport (>= 5.0) i18n (1.10.0) @@ -218,6 +219,7 @@ DEPENDENCIES controlled_vocabulary! debug dotenv-rails + exif (~> 2.2.3) jwt mini_magick (~> 4.11) pagy (~> 5.10) diff --git a/app/models/concerns/attachable.rb b/app/models/concerns/attachable.rb index 8f62f37..95af601 100644 --- a/app/models/concerns/attachable.rb +++ b/app/models/concerns/attachable.rb @@ -85,7 +85,7 @@ def generate_url_method(name) attachment = self.send(name) return nil unless attachment.attached? - "#{self.send("#{name}_base_url")}/full/500,/0/default.jpg" + "#{self.send("#{name}_base_url")}/full/^500,/0/default.jpg" end define_method("#{name}_thumbnail_url") do diff --git a/app/models/resource.rb b/app/models/resource.rb index d9693d4..d21b1c9 100644 --- a/app/models/resource.rb +++ b/app/models/resource.rb @@ -46,7 +46,7 @@ def content_metadata def content_preview_url return attachable_content_preview_url unless content.image? && content_converted.attached? - "#{content_base_url}/full/500,/0/default.jpg" + "#{content_base_url}/full/^500,/0/default.jpg" end def content_thumbnail_url @@ -66,6 +66,7 @@ def content_type def after_create convert create_manifest + extract_exif end private @@ -86,10 +87,20 @@ def convert end def create_manifest - self.manifest = Images::Manifest.create(self) + self.manifest = Iiif::Manifest.create(self) save end + + def extract_exif + return unless content.attached? && content.image? + + content.open do |file| + self.exif = JSON.dump(Images::Exif.extract(file)) + save + end + end + def set_uuid self.uuid = SecureRandom.uuid end diff --git a/app/services/images/manifest.rb b/app/services/iiif/manifest.rb similarity index 98% rename from app/services/images/manifest.rb rename to app/services/iiif/manifest.rb index 9749280..4ed6df3 100644 --- a/app/services/images/manifest.rb +++ b/app/services/iiif/manifest.rb @@ -1,4 +1,4 @@ -module Images +module Iiif class Manifest def self.create(resource) manifest = to_json('manifest.json') diff --git a/app/services/images/exif.rb b/app/services/images/exif.rb new file mode 100644 index 0000000..66e6b58 --- /dev/null +++ b/app/services/images/exif.rb @@ -0,0 +1,28 @@ +module Images + class Exif + def self.extract(file) + begin + data = ::Exif::Data.new(File.open(file.path)).to_h + encode data + rescue + # Do nothing. The image may not contain EXIF data + end + end + + private + + def self.encode(hash) + hash.keys.each do |key| + value = hash[key] + + if value.is_a?(String) + hash[key] = hash[key].force_encoding('ISO-8859-1').encode('UTF-8') + elsif value.is_a?(Hash) + hash[key] = encode(hash[key]) + end + end + + hash + end + end +end diff --git a/client/src/components/ResourceExifModal.js b/client/src/components/ResourceExifModal.js new file mode 100644 index 0000000..e98e235 --- /dev/null +++ b/client/src/components/ResourceExifModal.js @@ -0,0 +1,102 @@ +// @flow + +import React, { useCallback, type ComponentType } from 'react'; +import { + Button, + Grid, + Header, + Modal, + Table +} from 'semantic-ui-react'; +import _ from 'underscore'; +import { useTranslation } from 'react-i18next'; + +type Props = { + exif: any, + onClose: () => void +}; + +const ResourceExifModal: ComponentType = (props: Props) => { + const { t } = useTranslation(); + + /** + * Renders the header component and table structure. + * + * @type {function([*,*])} + */ + const renderHeader = useCallback(([key, value]) => ( + <> +
+ + + { _.map(Object.entries(value), renderItem) } + { _.isEmpty(value) && ( + + + No records + + + )} + +
+ + ), []); + + /** + * Renders the table row for the passed key/value. + * + * @type {function([*,*])} + */ + const renderItem = useCallback(([key, value]) => ( + + { key } + { value } + + ), []); + + return ( + + + + +
+ + +