Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

issue #959: Cache TIFF width and height into fields on File media if they exist #983

Closed
wants to merge 15 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions modules/islandora_iiif/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,21 @@ This module implements a Views Style plugin. It provides the following settings:

1. Tile Source: A field that was added to the views list of fields with the image to be served. This should be a File or Image type field on a Media.
2. Structured Text field: This lets you specify a file field where OCR text with positional data, e.g., hOCR can be found.

### Media Attributes from IIIF Action

The module also provides an action that lets a site owner populate a TIFF or JP2 image's width and
height attributes into fields so the IIIF server is not bogged down trying to generate a manifest if
it doesn't have them.

To use it, either:

- Add it as a derivative reaction to a node with an Original FIle as its child, or
- Use it as a batch action, such as on a Paged Content object's list of child pages.

The action assumes the media type has fields with machine names of field_height and
field_width. Making this configurable would mean they would not appear
on entity list pages.
## Documentation

Official documentation is available on the [Islandora 8 documentation site](https://islandora.github.io/documentation/).
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
langcode: en
status: true
dependencies:
module:
- islandora_iiif
id: media_attributes_from_iiif_action
label: 'Media attributes from IIIF'
type: node
plugin: islandora_iiif:media_attributes_from_iiif_action:media
configuration: { }
15 changes: 15 additions & 0 deletions modules/islandora_iiif/config/schema/islandora_iiif.schema.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,18 @@ views.style.iiif_manifest:
type: sequence
sequence:
type: string
label: "Tile source field(s)"
iiif_ocr_file_field:
type: sequence
sequence:
type: string:
label: "IIIF hOCR file field"
structured_text_term:
type: string
label: "Structured text term"
getdimensions_from_sewrver:
type: boolean
label: "Retrieve image dimensions from IIIF server"
search_endpoint:
type: string
label: "Search endpoint path"
18 changes: 18 additions & 0 deletions modules/islandora_iiif/islandora_iiif.install
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

/**
* @file
* Install/update hook implementations.
*/

use Symfony\Component\Yaml\Yaml;

/**
* Add Media Attributes from IIIF action.
*/
function islandora_iiif_update_92001(&$sandbox) {
$config_id = 'system.action.media_attributes_from_iiif_action';
$config_path = \Drupal::service('extension.list.module')->getPath('islandora_iiif') . '/config/optional/' . $config_id . '.yml';
$data = Yaml::parseFile($config_path);
\Drupal::configFactory()->getEditable($config_id)->setData($data)->save(TRUE);
}
4 changes: 4 additions & 0 deletions modules/islandora_iiif/islandora_iiif.services.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
services:
islandora_iiif:
class: Drupal\islandora_iiif\IiifInfo
arguments: ['@config.factory', '@http_client', '@logger.channel.islandora', '@jwt.authentication.jwt']
153 changes: 153 additions & 0 deletions modules/islandora_iiif/src/IiifInfo.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
<?php

namespace Drupal\islandora_iiif;

use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Logger\LoggerChannelInterface;
use Drupal\file\FileInterface;
use Drupal\jwt\Authentication\Provider\JwtAuth;

use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\ServerException;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs use GuzzleHttp\Exception\RequestException; also.

/**
* Get IIIF related info for a given File or Image entity.
*/
class IiifInfo {

/**
* The config factory.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;


/**
* The HTTP client.
*
* @var \GuzzleHttp\Client
*/
protected $httpClient;

/**
* This module's config.
*
* @var \Drupal\Core\Config\ImmutableConfig
*/
protected $iiifConfig;

/**
* JWT Auth provider service.
*
* @var \Drupal\jwt\Authentication\Provider\JwtAuth
*/
protected $jwtAuth;

/**
* The logger.
*
* @var \Drupal\Core\Logger\LoggerChannelInterface
*/
protected $logger;

/**
* Constructs an IiifInfo object.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory.
* @param \Guzzle\Http\Client $http_client
* The HTTP Client.
* @param \Drupal\Core\Logger\LoggerChannelInterface $channel
* Logger channel.
* @param \Drupal\jwt\Authentication\Provider\JwtAuth $jwt_auth
* The JWT auth provider.
*/
public function __construct(ConfigFactoryInterface $config_factory, Client $http_client, LoggerChannelInterface $channel, JwtAuth $jwt_auth) {
$this->configFactory = $config_factory;

$this->iiifConfig = $this->configFactory->get('islandora_iiif.settings');
$this->httpClient = $http_client;
$this->logger = $channel;
$this->jwtAuth = $jwt_auth;
}

/**
* The IIIF base URL for an image.
*
* Visiting this URL will resolve to the info.json for the image.
*
* @return string
* The absolute URL on the IIIF server.
*/
public function baseUrl($image) {

if ($this->iiifConfig->get('use_relative_paths')) {
$file_url = ltrim($image->createFileUrl(TRUE), '/');
}
else {
$file_url = $image->createFileUrl(FALSE);
}

$iiif_address = $this->iiifConfig->get('iiif_server');
$iiif_url = rtrim($iiif_address, '/') . '/' . urlencode($file_url);

return $iiif_url;
}

/**
* Retrieve an image's original dimensions via the IIIF server.
*
* @param \Drupal\File\FileInterface $file
* The image file.
*
* @return array|false
* The image dimensions in an array as [$width, $height]
*/
public function getImageDimensions(FileInterface $file) {
$iiif_url = $this->baseUrl($file);
try {
$info_json = $this->httpClient->request('get', $iiif_url, [
'headers' => [
'Authorization' => 'Bearer ' . $this->jwtAuth->generateToken(),
],
])->getBody();
$resource = json_decode($info_json, TRUE);
$width = $resource['width'];
$height = $resource['height'];
if (is_numeric($width) && is_numeric($height)) {
return [intval($width), intval($height)];
}
}
catch (ClientException | ConnectException | ServerException $e) {
alxp marked this conversation as resolved.
Show resolved Hide resolved
$this->logger->info("Error getting image file dimensions from IIIF server: " . $e->getMessage());
}
return FALSE;
}

/**
* The IIIF base URL for an image.
*
* Visiting this URL will resolve to the full image resized to the maximum dimensions given.
*
* @see https://iiif.io/api/image/2.1/
*
* @param Drupal\file\FileInterface $image
* The image entity.
* @param int width
* The maximum width of the image to be returned. 0 for no constraint.
* @param int $height
* The maxim um height of the image to be returned. 0 for no contraint.
*
* @return string
* The IIIF URl to retrieve the full image with the given max dimensions.
*/
public function getImageWithMaxDimensions($image, $width = 0, $height = 0) {
$base_url = $this->baseUrl($image);
return $base_url . "/full/!$width,$height/0/default.jpg";

}

}
165 changes: 165 additions & 0 deletions modules/islandora_iiif/src/Plugin/Action/MediaAttributesFromIiif.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
<?php

namespace Drupal\islandora_iiif\Plugin\Action;

use Drupal\Component\Datetime\TimeInterface;
use Drupal\Core\Action\Plugin\Action\SaveAction;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Logger\LoggerChannelInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\islandora\IslandoraUtils;
use Drupal\islandora\MediaSource\MediaSourceService;
use Drupal\islandora_iiif\IiifInfo;
use GuzzleHttp\Client;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
* Provides an action that can save any entity.
*
* @Action(
* id = "islandora_iiif:media_attributes_from_iiif_action",
* action_label = @Translation("Add image dimensions retrieved from the IIIF server"),
* deriver = "Drupal\Core\Action\Plugin\Action\Derivative\EntityChangedActionDeriver",
* )
*/
class MediaAttributesFromIiif extends SaveAction {

/**
* The HTTP client.
*
* @var \GuzzleHttp\Client
*/
protected $httpClient;

/**
* The IIIF Info service.
*
* @var \Drupal\islandora_iiif\IiifInfo
*/
protected $iiifInfo;

/**
* The logger.
*
* @var \Drupal\Core\Logger\LoggerChannelInterface
*/
protected $logger;

/**
* Islandora utility functions.
*
* @var \Drupal\islandora\IslandoraUtils
*/
protected $utils;

/**
* A MediaSourceService.
*
* @var \Drupal\islandora\MediaSource\MediaSourceService
*/
protected $mediaSource;

/**
* Constructs a TiffMediaSaveAction object.
*
* @param mixed[] $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin ID for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\Component\Datetime\TimeInterface $time
* The time service.
* @param \Guzzle\Http\Client $http_client
* The HTTP Client.
* @param \Drupal\islandora_iiif\IiifInfo $iiif_info
* The IIIF INfo service.
* @param \Drupal\islandora\IslandoraUtils $islandora_utils
* Islandora utility functions.
* @param \Drupal\islandora\MediaSource\MediaSourceService $media_source
* Islandora media service.
* @param \Drupal\Core\Logger\LoggerChannelInterface $channel
* Logger channel.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, TimeInterface $time, Client $http_client, IiifInfo $iiif_info, IslandoraUtils $islandora_utils, MediaSourceService $media_source, LoggerChannelInterface $channel) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_type_manager, $time);

$this->httpClient = $http_client;
$this->iiifInfo = $iiif_info;
$this->utils = $islandora_utils;
$this->mediaSource = $media_source;
$this->logger = $channel;
}

/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('entity_type.manager'),
$container->get('datetime.time'),
$container->get('http_client'),
$container->get('islandora_iiif'),
$container->get('islandora.utils'),
$container->get('islandora.media_source_service'),
$container->get('logger.channel.islandora')
);
}

/**
* {@inheritdoc}
*/
public function execute($entity = NULL) {
$width = $height = FALSE;

// Get the original File media use term.
$original_file_term = $this->utils->getTermForUri('http://pcdm.org/use#OriginalFile');

/**
* @var \Drupal\media\MediaInterface $original_file_media
*/
$original_file_mids = $this->utils->getMediaReferencingNodeAndTerm($entity, $original_file_term);
if (!empty($original_file_mids)) {

// Ordinarily there shouldn't be more than one Original File media but
// it's not guaranteed.
foreach ($original_file_mids as $original_file_mid) {

/**
* @var \Drupal\Media\MediaInterface $original_file_media
*/
$original_file_media = $this->entityTypeManager->getStorage('media')->load($original_file_mid);

// Get the media MIME Type.
$original_file = $this->mediaSource->getSourceFile($original_file_media);
$mime_type = $original_file->getMimeType();

if (in_array($mime_type, ['image/tiff', 'image/jp2'])) {
[$width, $height] = $this->iiifInfo->getImageDimensions($original_file);
}

// @todo Make field configurable. Low priority since this whole thing is a workaround for an Islandora limitation.
if ($original_file_media->hasField('field_width') && $original_file_media->hasField('field_height')) {
$original_file_media->set('field_height', $height);
$original_file_media->set('field_width', $width);
$original_file_media->save();
}
}
}
}

/**
* {@inheritdoc}
*/
public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {

/** @var \Drupal\Core\Entity\EntityInterface $object */
return $object->access('update', $account, $return_as_object);
}

}
Loading