Skip to content

Commit db3b950

Browse files
committed
Make derivatives configurable
This commit introduces a new configuration option so it will make it easier for consuming applications to override default derivative settings.
1 parent 16ddae4 commit db3b950

4 files changed

Lines changed: 168 additions & 35 deletions

File tree

app/services/hyrax/file_set_derivatives_service.rb

Lines changed: 24 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -67,49 +67,39 @@ def supported_mime_types
6767
end
6868

6969
def create_pdf_derivatives(filename)
70-
Hydra::Derivatives::PdfDerivatives.create(filename,
71-
outputs: [{
72-
label: :thumbnail,
73-
format: 'jpg',
74-
size: '338x493',
75-
url: derivative_url('thumbnail'),
76-
layer: 0
77-
}])
78-
extract_full_text(filename, derivative_url('extracted_text'))
70+
create_derivatives_for(:pdf, filename, Hydra::Derivatives::PdfDerivatives)
7971
end
8072

8173
def create_office_document_derivatives(filename)
82-
Hydra::Derivatives::DocumentDerivatives.create(filename,
83-
outputs: [{
84-
label: :thumbnail, format: 'jpg',
85-
size: '200x150>',
86-
url: derivative_url('thumbnail'),
87-
layer: 0
88-
}])
89-
extract_full_text(filename, derivative_url('extracted_text'))
74+
create_derivatives_for(:office, filename, Hydra::Derivatives::DocumentDerivatives)
9075
end
9176

9277
def create_audio_derivatives(filename)
93-
Hydra::Derivatives::AudioDerivatives.create(filename,
94-
outputs: [{ label: 'mp3', format: 'mp3', url: derivative_url('mp3'), mime_type: 'audio/mpeg', container: 'service_file' },
95-
{ label: 'ogg', format: 'ogg', url: derivative_url('ogg'), mime_type: 'audio/ogg', container: 'service_file' }])
78+
create_derivatives_for(:audio, filename, Hydra::Derivatives::AudioDerivatives)
9679
end
9780

9881
def create_video_derivatives(filename)
99-
Hydra::Derivatives::VideoDerivatives.create(filename,
100-
outputs: [{ label: :thumbnail, format: 'jpg', url: derivative_url('thumbnail'), mime_type: 'image/jpeg' },
101-
{ label: 'webm', format: 'webm', url: derivative_url('webm'), mime_type: 'video/webm', container: 'service_file' },
102-
{ label: 'mp4', format: 'mp4', url: derivative_url('mp4'), mime_type: 'video/mp4', container: 'service_file' }])
82+
create_derivatives_for(:video, filename, Hydra::Derivatives::VideoDerivatives)
10383
end
10484

10585
def create_image_derivatives(filename)
106-
# We're asking for layer 0, becauase otherwise pyramidal tiffs flatten all the layers together into the thumbnail
107-
Hydra::Derivatives::ImageDerivatives.create(filename,
108-
outputs: [{ label: :thumbnail,
109-
format: 'jpg',
110-
size: '200x150>',
111-
url: derivative_url('thumbnail'),
112-
layer: 0 }])
86+
create_derivatives_for(:image, filename, Hydra::Derivatives::ImageDerivatives)
87+
end
88+
89+
# Routes +extracted_text+ outputs to full text extraction; the rest to +processor+.
90+
def create_derivatives_for(type, filename, processor)
91+
text_outputs, processor_outputs = derivative_outputs(type).partition { |output| output[:container] == 'extracted_text' }
92+
processor.create(filename, outputs: processor_outputs) if processor_outputs.any?
93+
extract_full_text(filename, text_outputs) if text_outputs.any?
94+
end
95+
96+
# Resolves configured specs for +type+: callable values are invoked with the
97+
# file set, and +:url+ destination names become real derivative URLs.
98+
def derivative_outputs(type)
99+
Hyrax.config.derivative_options.fetch(type).map do |output|
100+
resolved = output.transform_values { |value| value.respond_to?(:call) ? value.call(file_set) : value }
101+
resolved.merge(url: derivative_url(resolved[:url]))
102+
end
113103
end
114104

115105
def derivative_path_factory
@@ -119,12 +109,11 @@ def derivative_path_factory
119109
# Calls the Hydra::Derivates::FulltextExtraction unless the extract_full_text
120110
# configuration option is set to false
121111
# @param [String] filename of the object to be used for full text extraction
122-
# @param [String] uri to the file set (deligated to file_set)
123-
def extract_full_text(filename, uri)
112+
# @param [Array<Hash>] outputs the resolved extracted_text output specs
113+
def extract_full_text(filename, outputs)
124114
return unless Hyrax.config.extract_full_text?
125115

126-
Hydra::Derivatives::FullTextExtract.create(filename,
127-
outputs: [{ url: uri, container: "extracted_text" }])
116+
Hydra::Derivatives::FullTextExtract.create(filename, outputs: outputs)
128117
end
129118
end
130119
end

lib/hyrax/configuration.rb

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,43 @@ def derivative_mime_type_mappings
284284
video: lookup_mimes(:video_mime_types) }
285285
end
286286

287+
attr_writer :derivative_options
288+
289+
##
290+
# The +outputs:+ specs passed to +Hydra::Derivatives+ per derivative type.
291+
# +:url+ is a destination name resolved to a real URL at create time; a
292+
# callable value is invoked with the file set. An +extracted_text+ container
293+
# routes to full text extraction (gated also by {#extract_full_text?}) — its
294+
# presence is the per-type toggle. Tune these instead of decorating
295+
# +Hyrax::FileSetDerivativesService+:
296+
#
297+
# Hyrax.config do |config|
298+
# config.derivative_options[:pdf].first[:size] = '676x986' # bigger thumbnail
299+
# config.derivative_options[:pdf].reject! { |o| o[:container] == 'extracted_text' } # no PDF text
300+
#
301+
# # transcode video to its source dimensions (falls back to 320x240)
302+
# sizer = ->(fs) { (w = Array(fs.width).first) && (h = Array(fs.height).first) ? "#{w}x#{h}" : '320x240' }
303+
# config.derivative_options[:video].each { |o| o[:size] = sizer }
304+
# end
305+
#
306+
# @return [Hash{Symbol => Array<Hash>}]
307+
# @see Hyrax::FileSetDerivativesService
308+
def derivative_options
309+
@derivative_options ||= {
310+
pdf: [{ label: :thumbnail, format: 'jpg', size: '338x493', url: 'thumbnail', layer: 0 },
311+
{ url: 'extracted_text', container: 'extracted_text' }],
312+
office: [{ label: :thumbnail, format: 'jpg', size: '200x150>', url: 'thumbnail', layer: 0 },
313+
{ url: 'extracted_text', container: 'extracted_text' }],
314+
audio: [{ label: 'mp3', format: 'mp3', url: 'mp3', mime_type: 'audio/mpeg', container: 'service_file' },
315+
{ label: 'ogg', format: 'ogg', url: 'ogg', mime_type: 'audio/ogg', container: 'service_file' }],
316+
video: [{ label: :thumbnail, format: 'jpg', url: 'thumbnail', mime_type: 'image/jpeg' },
317+
{ label: 'webm', format: 'webm', url: 'webm', mime_type: 'video/webm', container: 'service_file' },
318+
{ label: 'mp4', format: 'mp4', url: 'mp4', mime_type: 'video/mp4', container: 'service_file' }],
319+
# image uses layer: 0 because otherwise pyramidal tiffs flatten all the layers together into the thumbnail
320+
image: [{ label: :thumbnail, format: 'jpg', size: '200x150>', url: 'thumbnail', layer: 0 }]
321+
}
322+
end
323+
287324
attr_writer :derivative_services
288325
# The registered candidate derivative services. In the array, the first `valid?` candidate will
289326
# handle the derivative generation.

spec/lib/hyrax/configuration_spec.rb

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@
4747
it { is_expected.to respond_to(:default_admin_set_id) }
4848
it { is_expected.to respond_to(:derivative_services) }
4949
it { is_expected.to respond_to(:derivative_services=) }
50+
it { is_expected.to respond_to(:derivative_options) }
51+
it { is_expected.to respond_to(:derivative_options=) }
5052
it { is_expected.to respond_to(:display_media_download_link=) }
5153
it { is_expected.to respond_to(:display_media_download_link?) }
5254
it { is_expected.to respond_to(:display_microdata?) }
@@ -132,6 +134,31 @@
132134
end
133135
end
134136

137+
describe "#derivative_options" do
138+
it "defaults to output specs for each derivative type" do
139+
expect(described_class.new.derivative_options.keys)
140+
.to contain_exactly(:pdf, :office, :audio, :video, :image)
141+
end
142+
143+
it "resolves url to a destination name rather than a full url" do
144+
expect(described_class.new.derivative_options[:image])
145+
.to contain_exactly(hash_including(label: :thumbnail, url: 'thumbnail', layer: 0))
146+
end
147+
148+
it "is overridable" do
149+
config = described_class.new
150+
config.derivative_options = { image: [{ label: :big, format: 'jpg', size: '999x999', url: 'big' }] }
151+
expect(config.derivative_options[:image].first).to include(size: '999x999')
152+
end
153+
154+
it "extracts text for pdf and office by default" do
155+
%i[pdf office].each do |type|
156+
expect(described_class.new.derivative_options[type])
157+
.to include(hash_including(container: 'extracted_text'))
158+
end
159+
end
160+
end
161+
135162
describe "#use_valkyrie?" do
136163
before { stub_const("ENV", "HYRAX_SKIP_WINGS" => "true") }
137164
it "returns true if wings is disabled" do

spec/services/hyrax/file_set_derivatives_service_spec.rb

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,66 @@
4242
end
4343
end
4444

45+
context "when an app overrides only the image thumbnail size" do
46+
let(:valid_file_set) do
47+
FactoryBot.valkyrie_create(:hyrax_file_metadata, :image, file_set_id: SecureRandom.uuid)
48+
end
49+
let(:custom_size) { '9999x9999' }
50+
51+
before do
52+
allow(Hyrax.config).to receive(:derivative_options).and_return(
53+
image: [Hyrax::Configuration.new.derivative_options[:image].first.merge(size: custom_size)]
54+
)
55+
end
56+
57+
it "creates the thumbnail at the configured size but keeps the other defaults" do
58+
allow(Hydra::Derivatives::ImageDerivatives).to receive(:create)
59+
described_class.new(valid_file_set).create_derivatives('foo.jpg')
60+
expect(Hydra::Derivatives::ImageDerivatives).to have_received(:create).with(
61+
'foo.jpg',
62+
outputs: contain_exactly(
63+
hash_including(size: custom_size, label: :thumbnail, format: 'jpg', layer: 0)
64+
)
65+
)
66+
end
67+
end
68+
69+
context "when given a pdf file" do
70+
let(:valid_file_set) do
71+
FactoryBot.valkyrie_create(:hyrax_file_metadata, mime_type: 'application/pdf', file_set_id: SecureRandom.uuid)
72+
end
73+
74+
before do
75+
allow(Hydra::Derivatives::PdfDerivatives).to receive(:create)
76+
allow(Hydra::Derivatives::FullTextExtract).to receive(:create)
77+
end
78+
79+
it "creates the thumbnail and extracts full text" do
80+
described_class.new(valid_file_set).create_derivatives('foo.pdf')
81+
expect(Hydra::Derivatives::PdfDerivatives).to have_received(:create).with(
82+
'foo.pdf', outputs: contain_exactly(hash_including(label: :thumbnail))
83+
)
84+
expect(Hydra::Derivatives::FullTextExtract).to have_received(:create).with(
85+
'foo.pdf', outputs: contain_exactly(hash_including(container: 'extracted_text'))
86+
)
87+
end
88+
89+
context "when the extracted_text output is removed from the config" do
90+
before do
91+
pdf_without_text = Hyrax::Configuration.new.derivative_options[:pdf].reject { |o| o[:container] == 'extracted_text' }
92+
allow(Hyrax.config).to receive(:derivative_options).and_return(pdf: pdf_without_text)
93+
end
94+
95+
it "still creates the thumbnail but skips full text extraction" do
96+
described_class.new(valid_file_set).create_derivatives('foo.pdf')
97+
expect(Hydra::Derivatives::PdfDerivatives).to have_received(:create).with(
98+
'foo.pdf', outputs: contain_exactly(hash_including(label: :thumbnail))
99+
)
100+
expect(Hydra::Derivatives::FullTextExtract).not_to have_received(:create)
101+
end
102+
end
103+
end
104+
45105
context "when given a video file" do
46106
let(:valid_file_set) do
47107
FactoryBot.valkyrie_create(:hyrax_file_metadata, :video_file, file_set_id: SecureRandom.uuid)
@@ -59,6 +119,26 @@
59119
)
60120
)
61121
end
122+
123+
context "when an output size is a callable" do
124+
let(:received_file_set) { [] }
125+
126+
before do
127+
size_from = ->(file_set) { received_file_set << file_set && '1280x720' }
128+
sized = Hyrax::Configuration.new.derivative_options[:video].map { |output| output.merge(size: size_from) }
129+
allow(Hyrax.config).to receive(:derivative_options).and_return(video: sized)
130+
end
131+
132+
it "invokes it with the file set and passes the result through" do
133+
allow(Hydra::Derivatives::VideoDerivatives).to receive(:create)
134+
described_class.new(valid_file_set).create_derivatives('foo.mov')
135+
136+
expect(received_file_set).to all(eq(valid_file_set))
137+
expect(Hydra::Derivatives::VideoDerivatives).to have_received(:create) do |_filename, outputs:|
138+
expect(outputs).to all(include(size: '1280x720'))
139+
end
140+
end
141+
end
62142
end
63143
end
64144
end

0 commit comments

Comments
 (0)