Skip to content

Commit

Permalink
Speed up IIIF Manifest Generation
Browse files Browse the repository at this point in the history
This brings a 1140 page manifest from 11 seconds to 2.6 seconds to
generate.
  • Loading branch information
tpendragon committed Sep 1, 2017
1 parent 0e21483 commit 1545262
Show file tree
Hide file tree
Showing 6 changed files with 191 additions and 27 deletions.
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ GIT

GIT
remote: git://github.com/samvera-labs/iiif_manifest.git
revision: d9c249d0aa2ecb758d9ee2d6176a82c86dd6462d
revision: 760a6d90abca70afb980b603bbdb6dd557eb3a58
specs:
iiif_manifest (0.2.0)
activesupport (>= 4)
Expand Down
51 changes: 30 additions & 21 deletions app/services/manifest_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ def ranges
# Helper method for generating the URL to the resource manifest
# @return [String]
def manifest_url
helper.polymorphic_url([:manifest, resource])
helper.manifest_url(resource)
end

##
Expand Down Expand Up @@ -238,14 +238,7 @@ def initialize(resource, parent_node)
# Stringify the image using the decorator
# @return [String]
def to_s
resource.decorate.header
end

##
# Retrieve the ID for the resource
# @return [String]
def derivative_id
resource.id
Valkyrie::ResourceDecorator.new(resource).header
end

##
Expand Down Expand Up @@ -286,7 +279,7 @@ def file
# Retrieve an instance of the IIIFManifest::IIIFEndpoint for the service endpoint
# @return [IIIFManifest::IIIFEndpoint]
def endpoint
IIIFManifest::IIIFEndpoint.new(helper.manifest_image_path(derivative_id),
IIIFManifest::IIIFEndpoint.new(helper.manifest_image_path(resource),
profile: "http://iiif.io/api/image/2/level2.json")
end

Expand All @@ -311,15 +304,31 @@ def default_url_options
Figgy.default_url_options
end

def manifest_url(resource)
if resource.is_a?(Collection)
"#{protocol}://#{host}/collections/#{resource.id}/manifest"
else
"#{protocol}://#{host}/concern/#{resource.model_name.plural}/#{resource.id}/manifest"
end
end

def host
default_url_options[:host]
end

def protocol
default_url_options[:protocol] || "http"
end

##
# Retrieve the base URL for Riiif
# @param [String] id identifier for the image resource
# @return [String]
def manifest_image_path(id)
def manifest_image_path(resource)
if Rails.env.development? || Rails.env.test?
RiiifHelper.new.base_url(id)
RiiifHelper.new.base_url(resource.id)
else
CantaloupeHelper.new.base_url(id)
CantaloupeHelper.new.base_url(resource)
end
end

Expand All @@ -328,23 +337,23 @@ def manifest_image_path(id)
# @param [String] id identifier for the image resource
# @return [String]
def manifest_image_thumbnail_path(id)
"#{manifest_image_path(id)}/full/!200,150/0/default.jpg"
file_set = query_service.find_by(id: Valkyrie::ID.new(id))
"#{manifest_image_path(file_set)}/full/!200,150/0/default.jpg"
end

def query_service
Valkyrie.config.metadata_adapter.query_service
end
end

class CantaloupeHelper
def base_url(id)
file_set = query_service.find_by(id: Valkyrie::ID.new(id))
def base_url(file_set)
file_metadata = file_set.derivative_file
raise Valkyrie::Persistence::ObjectNotFoundError, id if file_metadata.nil?
raise Valkyrie::Persistence::ObjectNotFoundError, file_set.id if file_metadata.nil?
Pathname.new(Figgy.config['cantaloupe_url']).join(
CGI.escape("#{file_metadata.id}/intermediate_file.jp2")
).to_s
end

def query_service
Valkyrie.config.metadata_adapter.query_service
end
end

##
Expand Down
124 changes: 124 additions & 0 deletions app/services/manifest_builder/faster_iiif_manifest.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# frozen_string_literal: true
class ManifestBuilder
class Service
attr_reader :inner_hash
def initialize
@inner_hash = initial_attributes
end

delegate :[]=, :[], :as_json, :to_json, to: :inner_hash

def initial_attributes
{}
end
end

class FasterIIIFManifest < Service
def label=(label)
inner_hash["label"] = label
end

def description=(description)
inner_hash["description"] = description
end

def viewing_hint=(viewing_hint)
inner_hash["viewingHint"] = viewing_hint
end

def sequences
inner_hash["sequences"] || []
end

def sequences=(sequences)
inner_hash["sequences"] = sequences
end

def metadata=(metadata)
inner_hash["metadata"] = metadata
end

def see_also=(see_also)
inner_hash["seeAlso"] = see_also
end

def license=(license)
inner_hash["license"] = license
end

def initial_attributes
{
"@context" => "http://iiif.io/api/presentation/2/context.json",
"@type" => "sc:Manifest"
}
end

class Sequence < Service
def canvases
inner_hash["canvases"] || []
end

def canvases=(canvases)
inner_hash["canvases"] = canvases
end

def initial_attributes
{
"@type" => "sc:Sequence"
}
end
end

class Canvas < Service
def label=(label)
inner_hash["label"] = label
end

def images
inner_hash["images"] || []
end

def images=(images)
inner_hash["images"] = images
end

def initial_attributes
{
"@type" => "sc:Canvas"
}
end
end

class Range < Service
end

class Resource < Service
def service=(service)
inner_hash['service'] = service
end

def initial_attributes
{
"@type" => "sc:Range"
}
end
end

class Annotation < Service
def resource=(resource)
inner_hash["resource"] = resource
end

def resource
inner_hash["resource"]
end

def initial_attributes
{
"@type" => "oa:Annotation",
"motivation" => "sc:painting"
}
end
end
end
end
28 changes: 28 additions & 0 deletions app/services/manifest_builder/manifest_service_locator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,34 @@ def new(work)
end
end

def iiif_manifest_factory
::ManifestBuilder::FasterIIIFManifest
end

def iiif_service_factory
::ManifestBuilder::Service
end

def sequence_factory
::ManifestBuilder::FasterIIIFManifest::Sequence
end

def iiif_canvas_factory
::ManifestBuilder::FasterIIIFManifest::Canvas
end

def iiif_annotation_factory
::ManifestBuilder::FasterIIIFManifest::Annotation
end

def iiif_resource_factory
::ManifestBuilder::FasterIIIFManifest::Resource
end

def iiif_range_factory
::ManifestBuilder::FasterIIIFManifest::Range
end

##
# Override the Class method for instantiating a CompositeBuilder
# Insert the metadata manifest builder
Expand Down
7 changes: 2 additions & 5 deletions spec/services/manifest_builder/cantaloupe_helper_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,20 @@
let(:cantaloupe_helper) { described_class.new }
let(:file_set) { FactoryGirl.create_for_repository(:file_set) }
let(:derivative_file) { instance_double(FileMetadata) }
let(:query_service) { class_double(Valkyrie::Persistence::Postgres::QueryService) }

describe '#base_url' do
context 'with generated derivatives' do
before do
allow(derivative_file).to receive(:id).and_return('test')
allow(file_set).to receive(:derivative_file).and_return(derivative_file)
allow(query_service).to receive(:find_by).and_return(file_set)
allow(cantaloupe_helper).to receive(:query_service).and_return(query_service)
end
it 'generates a base URL for a JPEG2000 derivative' do
expect(cantaloupe_helper.base_url(file_set.id)).to eq 'http://localhost:8182/iiif/2/test%2Fintermediate_file.jp2'
expect(cantaloupe_helper.base_url(file_set)).to eq 'http://localhost:8182/iiif/2/test%2Fintermediate_file.jp2'
end
end
context 'without generated derivatives' do
it 'raises an Valkyrie::Persistence::ObjectNotFoundError' do
expect { cantaloupe_helper.base_url(file_set.id) }.to raise_error(Valkyrie::Persistence::ObjectNotFoundError)
expect { cantaloupe_helper.base_url(file_set) }.to raise_error(Valkyrie::Persistence::ObjectNotFoundError)
end
end
end
Expand Down
6 changes: 6 additions & 0 deletions spec/services/manifest_builder_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ def logical_structure(file_set_id)
expect(output["structures"].length).to eq 3
structure_canvas_id = output["structures"][2]["canvases"][0]
expect(canvas_id).to eq structure_canvas_id
first_image = output["sequences"][0]["canvases"][0]["images"][0]
expect(first_image["data"]).to eq nil
expect(first_image["@type"]).to eq "oa:Annotation"
expect(first_image["motivation"]).to eq "sc:painting"
expect(first_image["resource"]["data"]).to eq nil
expect(first_image["resource"]["service"]["@id"]).not_to be_nil
end

context "when in staging" do
Expand Down

0 comments on commit 1545262

Please sign in to comment.