From 4e8ef41e480f28365085f96d855eaabdc8cccad1 Mon Sep 17 00:00:00 2001 From: Mason Ballengee Date: Wed, 13 Dec 2023 15:38:18 -0500 Subject: [PATCH] Add captions to playlist IIIF manifests --- app/models/iiif_playlist_canvas_presenter.rb | 37 ++++++++- .../iiif_playlist_canvas_presenter_spec.rb | 78 ++++++++++++++++--- 2 files changed, 103 insertions(+), 12 deletions(-) diff --git a/app/models/iiif_playlist_canvas_presenter.rb b/app/models/iiif_playlist_canvas_presenter.rb index 1aee9ef4ba..d290573128 100644 --- a/app/models/iiif_playlist_canvas_presenter.rb +++ b/app/models/iiif_playlist_canvas_presenter.rb @@ -68,7 +68,8 @@ def display_content def annotation_content return if cannot_read_item || master_file.nil? - playlist_item.marker.collect { |m| marker_content(m) } + annotations = supplemental_captions.collect { |file| supplemental_captions_data(file) } + annotations += playlist_item.marker.collect { |marker| marker_content(marker) } end def placeholder_content @@ -127,6 +128,40 @@ def marker_content(marker) IIIFManifest::V3::AnnotationContent.new(annotation_id: url, **marker_attributes(marker)) end + def supplemental_captions + files = master_file.supplemental_files(tag: 'caption') + files += [master_file.captions] if master_file.captions.present? && master_file.captions.persisted? + files + end + + def supplemental_captions_data(file) + url = if !file.is_a?(SupplementalFile) + Rails.application.routes.url_helpers.captions_master_file_url(master_file.id) + elsif file.tags.include?('caption') + Rails.application.routes.url_helpers.captions_master_file_supplemental_file_url(master_file.id, file.id) + end + IIIFManifest::V3::AnnotationContent.new(body_id: url, **supplemental_attributes(file)) + end + + def supplemental_attributes(file) + if file.is_a?(SupplementalFile) + label = file.tags.include?('machine_generated') ? file.label + ' (machine generated)' : file.label + format = file.file.content_type + language = file.language || 'en' + else + label = 'English' + format = file.mime_type + language = 'en' + end + { + motivation: 'supplementing', + label: label, + type: 'Text', + format: format, + language: language + } + end + def stream_urls stream_info[:stream_hls].collect do |d| [d[:quality], d[:url]] diff --git a/spec/models/iiif_playlist_canvas_presenter_spec.rb b/spec/models/iiif_playlist_canvas_presenter_spec.rb index 76f7aa8b88..d604187941 100644 --- a/spec/models/iiif_playlist_canvas_presenter_spec.rb +++ b/spec/models/iiif_playlist_canvas_presenter_spec.rb @@ -116,24 +116,80 @@ end describe '#annotation_content' do - let(:marker) { FactoryBot.create(:avalon_marker) } - let(:playlist_item) { FactoryBot.build(:playlist_item, clip: playlist_clip, marker: [marker]) } subject { presenter.annotation_content } - it "serializes playlist markers as iiif annotations" do - expect(subject).to all be_a(IIIFManifest::V3::AnnotationContent) - end + context 'item has markers and no captions' do + let(:marker) { FactoryBot.create(:avalon_marker) } + let(:playlist_item) { FactoryBot.build(:playlist_item, clip: playlist_clip, marker: [marker]) } + + it "serializes playlist markers as iiif annotations" do + expect(subject).to all be_a(IIIFManifest::V3::AnnotationContent) + expect(subject.length).to eq 1 + end + + it "includes marker label" do + expect(subject.first.value).to eq marker.title + end + + it "includes marker start_time" do + expect(subject.first.media_fragment).to eq "t=#{marker.start_time}" + end - it "includes marker label" do - expect(subject.first.value).to eq marker.title + it "includes 'highlighting' motivation" do + expect(subject.first.motivation).to eq 'highlighting' + end end - it "includes marker start_time" do - expect(subject.first.media_fragment).to eq "t=#{marker.start_time}" + context 'item has captions and no markers' do + let(:caption_file) { FactoryBot.create(:supplemental_file, :with_caption_file, :with_caption_tag) } + let(:master_file) { FactoryBot.create(:master_file, :with_captions, media_object: media_object, derivatives: [derivative], supplemental_files: [caption_file]) } + + it "serializes captions as iiif annotations" do + expect(subject).to all be_a(IIIFManifest::V3::AnnotationContent) + expect(subject.length).to eq 2 + end + + it "includes paths to supplemental and legacy caption files" do + expect(subject.any? { |content| content.body_id =~ /supplemental_files\/#{caption_file.id}\/captions/ }).to eq true + expect(subject.any? { |content| content.body_id =~ /master_files\/#{master_file.id}\/captions/ }).to eq true + end + + it "includes 'supplementing' motivation" do + expect(subject.any? { |content| content.motivation =~ /supplementing/ }).to eq true + end end - it "includes 'highlighting' motivation" do - expect(subject.first.motivation).to eq 'highlighting' + context 'item has captions and markers' do + let(:marker) { FactoryBot.create(:avalon_marker) } + let(:playlist_item) { FactoryBot.build(:playlist_item, clip: playlist_clip, marker: [marker]) } + let(:caption_file) { FactoryBot.create(:supplemental_file, :with_caption_file, :with_caption_tag) } + let(:master_file) { FactoryBot.create(:master_file, :with_captions, media_object: media_object, derivatives: [derivative], supplemental_files: [caption_file]) } + + it "serializes captions as iiif annotations" do + expect(subject).to all be_a(IIIFManifest::V3::AnnotationContent) + expect(subject.length).to eq 3 + end + + it "includes marker label" do + expect(subject.any? { |content| content.value =~ /#{marker.title}/ }).to eq true + end + + it "includes marker start_time" do + expect(subject.any? { |content| content.media_fragment =~ /t=#{marker.start_time}/ }).to eq true + end + + it "includes paths to supplemental and legacy caption files" do + expect(subject.any? { |content| content.body_id =~ /supplemental_files\/#{caption_file.id}\/captions/ }).to eq true + expect(subject.any? { |content| content.body_id =~ /master_files\/#{master_file.id}\/captions/ }).to eq true + end + + it "includes 'highlighting' motivation" do + expect(subject.any? { |content| content.motivation =~ /highlighting/ }).to eq true + end + + it "includes 'supplementing' motivation" do + expect(subject.any? { |content| content.motivation =~ /supplementing/ }).to eq true + end end context 'when a user does not have access to a restricted item' do