Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
a35ae68
Add transcript_ids property to file set model(s)
eltiffster Mar 27, 2026
9d63a58
Add transcript_ids to file set form
eltiffster Mar 27, 2026
6c55ba0
Copy changes from @kirkkwang's draft PR, WIP: Implement Transcription…
eltiffster Mar 31, 2026
2a4df1d
Support transcriptions for ActiveFedora works
eltiffster Apr 1, 2026
ec91a73
Fix broken specs
eltiffster Apr 8, 2026
b4a9419
Normalize VTT file set language values to 2-letter language codes.
eltiffster Apr 8, 2026
757101b
Simplify iiif_manifest_presenter_spec#annotates_content
eltiffster Apr 9, 2026
6a5da1d
Add comments and rubocop fixes
eltiffster Apr 9, 2026
1d60f21
Add language field to file set form and move transcript ids form hint…
eltiffster Apr 9, 2026
3da19f1
Add VTT transcripts to default audio/video partials
eltiffster Apr 10, 2026
465ce27
Fix "l is not a function" error when displaying VTT transcripts in An…
eltiffster Apr 13, 2026
1371460
Fix "l is not a function" error for other file types. The implementat…
eltiffster Apr 14, 2026
5ed7239
Use the file set presenter to render the file set edit form instead o…
eltiffster Apr 14, 2026
148273e
Allow transcriptions_controller to serve file types other than VTT. F…
eltiffster Apr 14, 2026
3067885
Fix syntax error in clover.js
eltiffster Apr 14, 2026
6dd9b6e
Increase the height of Clover IIIF viewer so that thumbnails for work…
eltiffster Apr 15, 2026
5319280
Rename "transcriptions" to "transcripts" for consistency
eltiffster Apr 15, 2026
26de256
Account for ActiveTriples::Resource in a transcript's language field.…
eltiffster Apr 16, 2026
ff06bb5
Remove unused fallback label, since a file set should always have a t…
eltiffster Apr 16, 2026
1a10000
Make language optional again in IIIF manifest annotations, especially…
eltiffster Apr 16, 2026
d0074b0
Delete a stray, unnecessary comment
eltiffster Apr 16, 2026
8368731
Rename .valid_transcripts to .available_transcripts
eltiffster Apr 17, 2026
7ff49e6
Update file_set_form_helper_spec.rb
eltiffster Apr 17, 2026
b7dfbac
Merge branch 'main' into av-transcripts
eltiffster Apr 17, 2026
2252b97
Fixes for Koppie: use file_ids_ssim.first instead of original_file_id
eltiffster Apr 20, 2026
5c3eca3
Fix and refactor file_set_form_helper_spec to use top-level context b…
eltiffster Apr 20, 2026
15a6c27
Rubocop fixes and add comment
eltiffster Apr 20, 2026
1bca09e
Refactor file set form
eltiffster Apr 20, 2026
4738f74
Use cached parent for FileSetFormHelper instead of running an extra q…
eltiffster Apr 21, 2026
18d41a0
Add vtt file metadata to file_set_form_helper_spec
eltiffster Apr 21, 2026
e1cb16e
When searching for available transcripts. use an fq filter to capture…
eltiffster Apr 21, 2026
3887e63
Merge branch 'main' into av-transcripts
eltiffster Apr 22, 2026
c5a6cfe
Fix whitespace and add some clarifying comments.
eltiffster Apr 23, 2026
dd1fc7f
Authorize transcript before streaming the file
eltiffster Apr 24, 2026
66e0607
Remove test for ActiveTriples::Resource in presenters/hyrax/displays_…
eltiffster Apr 24, 2026
f06ef78
Make `transcript_ids` property conditional on Hyrax.config.file_set_i…
eltiffster Apr 27, 2026
54d7d69
Update metadata profiles to fix allinson and koppie builds
eltiffster Apr 28, 2026
b9dc1ea
Move the transcripts_ids form field to views/records/edit_fields so i…
eltiffster Apr 28, 2026
893f599
Move transcript_ids into the file set metadata schema
eltiffster Apr 29, 2026
5b914be
Merge branch 'main' into av-transcripts
eltiffster Apr 29, 2026
cbc3d58
Update clover.js to v.3.6.0, which officially fixes https://github.co…
eltiffster Apr 29, 2026
d51a870
Merge branch 'main' into av-transcripts
eltiffster May 1, 2026
97da895
Merge branch 'main' into av-transcripts
orangewolf May 8, 2026
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
14 changes: 14 additions & 0 deletions .dassie/config/metadata_profiles/m3_profile.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -942,3 +942,17 @@ properties:
view:
render_as: faceted
html_dl: true
transcript_ids:
available_on:
class:
- Hyrax::FileSet
indexing:
- transcript_ids_ssim
data_type: array
display_label:
default: Transcripts
form:
display: true
primary: false
range: http://www.w3.org/2001/XMLSchema#string
property_uri: http://vocabulary.samvera.org/ns#transcriptIds
14 changes: 14 additions & 0 deletions .koppie/config/metadata_profiles/m3_profile.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -914,3 +914,17 @@ properties:
view:
render_as: faceted
html_dl: true
transcript_ids:
available_on:
class:
- FileSet
indexing:
- transcript_ids_ssim
data_type: array
display_label:
default: Transcripts
form:
display: true
primary: false
range: http://www.w3.org/2001/XMLSchema#string
property_uri: http://vocabulary.samvera.org/ns#transcriptIds
8 changes: 8 additions & 0 deletions app/assets/stylesheets/hyrax/_viewer.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,17 @@
left: 0;
}

// Universal Viewer:
// Show various footer, header, left and right panels on larger screens only
@media only screen and (min-width: 640px) {
.viewer-wrapper {
height: 640px;
}
}

// Clover IIIF Viewer:
// Increase the height so that thumbnails below the player are visible
// (for works with multiple audio/video files)
.viewer-wrapper.clover-viewer-wrapper {
height: 775px;
}
11 changes: 7 additions & 4 deletions app/controllers/concerns/hyrax/works_controller_behavior.rb
Original file line number Diff line number Diff line change
Expand Up @@ -139,12 +139,15 @@ def inspect_work
end

def manifest
headers['Access-Control-Allow-Origin'] = '*'
locale = params[:locale] || current_user&.preferred_locale || I18n.default_locale
I18n.with_locale(locale) do
headers['Access-Control-Allow-Origin'] = '*'

json = iiif_manifest_builder.manifest_for(presenter: iiif_manifest_presenter)
json = iiif_manifest_builder.manifest_for(presenter: iiif_manifest_presenter)

respond_to do |wants|
wants.any { render json: json }
respond_to do |wants|
wants.any { render json: json }
end
end
end

Expand Down
31 changes: 31 additions & 0 deletions app/controllers/hyrax/transcripts_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# frozen_string_literal: true

module Hyrax
class TranscriptsController < DownloadsController
def show
# Using the extracted text from the index is blocked
# by https://github.com/samvera/hyrax/issues/7410, so we
# need to get the original file instead.
file_metadata = find_file_metadata(file_set: Hyrax.query_service.find_by(id: params.require(:id)))
file = Hyrax.storage_adapter.find_by(id: file_metadata.file_identifier)

prepare_file_headers_valkyrie(metadata: file_metadata, file: file)
response.headers['Access-Control-Allow-Origin'] = '*'
send_file file.disk_path, data_options(file_metadata)
end

private

def disposition
'inline'
end

def data_options(file_metadata)
{
type: "#{file_metadata.mime_type}; charset=utf-8",
filename: file_metadata.original_filename,
disposition: disposition
}
end
end
end
40 changes: 40 additions & 0 deletions app/forms/concerns/hyrax/transcripts_behavior.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# frozen_string_literal: true
module Hyrax
module TranscriptsBehavior
extend ActiveSupport::Concern

class_methods do
def available_transcripts(parent:, current_ability:)
member_ids = Hyrax.custom_queries.find_child_file_set_ids(resource: parent)
Hyrax::SolrQueryService.new
# Cast Valkyrie::IDs to strings
.with_ids(ids: member_ids.map(&:to_s).to_a)
.accessible_by(ability: current_ability, action: :edit)
.solr_documents(
# Using "has_model_ssim:*FileSet" in fq will return both FileSet
# and Hyrax::FileSet documents. In test mode, Koppie and Sirenia
# index file sets with has_model_ssim:Hyrax::FileSet.
# But in dev mode, they index file sets with
# has_model_ssim:FileSet instead. This query covers both cases.
fq: [mime_type_filter_query.to_s, "has_model_ssim:*FileSet"],
fl: "id,title_tesim",
rows: 1000
)
end

private

def mime_type_filter_query
valid_mime_types.map { |type| "mime_type_ssi:\"#{type}\"" }.join(" OR ")
end

# According to IIIF, .srt and .ttml are also acceptable but may
# not be supported by viewers. Clover and Ramp are confirmed to work
# with .vtt. (https://iiif.io/api/cookbook/recipe/0219-using-caption-file/).
# When Hyrax supports Ramp, we may want to add "text/plain" (.srt) to this list.
def valid_mime_types
["text/vtt"]
end
end
end
end
2 changes: 1 addition & 1 deletion app/forms/hyrax/forms/file_set_edit_form.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class FileSetEditForm

self.terms = [:resource_type, :title, :creator, :contributor, :description,
:keyword, :license, :publisher, :date_created, :subject, :language,
:identifier, :based_near, :related_url,
:identifier, :based_near, :related_url, :transcript_ids,
:visibility_during_embargo, :visibility_after_embargo, :embargo_release_date,
:visibility_during_lease, :visibility_after_lease, :lease_expiration_date,
:visibility]
Expand Down
1 change: 1 addition & 0 deletions app/forms/hyrax/forms/file_set_form.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class FileSetForm < Hyrax::Forms::ResourceForm
include Hyrax::ContainedInWorksBehavior
include Hyrax::LeaseabilityBehavior
include Hyrax::PermissionBehavior
include Hyrax::TranscriptsBehavior

property :representative_id, type: Valkyrie::Types::String, writeable: false
property :thumbnail_id, type: Valkyrie::Types::String, writeable: false
Expand Down
24 changes: 24 additions & 0 deletions app/helpers/hyrax/file_set_form_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# frozen_string_literal: true

module Hyrax
module FileSetFormHelper
def render_transcript_ids_field?(file_set)
return unless file_set.persisted?
return if @parent.nil?
case file_set
when ActiveFedora::Base
file_set.video? || file_set.audio?
when Valkyrie::Resource
service = Hyrax::FileSetTypeService.new(file_set: file_set)
service.video? || service.audio?
end
end

def transcript_ids_select_options
options = Forms::FileSetForm.available_transcripts(parent: @parent, current_ability: current_ability)
options.each_with_object({}) do |doc, hash|
hash[doc.title_or_label] = doc.id.to_s
end
end
end
end
1 change: 1 addition & 0 deletions app/helpers/hyrax/hyrax_helper_behavior.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ module HyraxHelperBehavior
include Hyrax::FacetsHelper
include Hyrax::AttributesHelper
include Hyrax::WorksHelper
include Hyrax::FileSetFormHelper

##
# @return [Array<String>] the list of admin sets available for creating works for this user
Expand Down
1 change: 1 addition & 0 deletions app/indexers/hyrax/file_set_indexer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ def generate_solr_document # rubocop:disable Metrics/AbcSize, Metrics/MethodLeng
super.tap do |solr_doc|
solr_doc['hasRelatedMediaFragment_ssim'] = object.representative_id
solr_doc['hasRelatedImage_ssim'] = object.thumbnail_id
solr_doc['transcript_ids_ssim'] = object.transcript_ids
# Label is the actual file name. It's not editable by the user.
solr_doc['label_tesim'] = object.label
solr_doc['label_ssi'] = object.label
Expand Down
1 change: 1 addition & 0 deletions app/indexers/hyrax/indexers/file_set_indexer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ def to_solr # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Met
solr_doc['extracted_text_id_ssi'] = resource.extracted_text_id.to_s
solr_doc['hasRelatedMediaFragment_ssim'] = resource.representative_id.to_s
solr_doc['hasRelatedImage_ssim'] = resource.thumbnail_id.to_s
solr_doc['transcript_ids_ssim'] = resource.transcript_ids&.map(&:to_s)

index_lease(solr_doc)
index_embargo(solr_doc)
Expand Down
16 changes: 16 additions & 0 deletions app/models/concerns/hyrax/file_set/transcripts.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# frozen_string_literal: true
module Hyrax
class FileSet
module Transcripts
extend ActiveSupport::Concern

included do
if Hyrax.config.file_set_include_metadata?
property :transcript_ids, predicate: ::RDF::URI.new('http://vocabulary.samvera.org/ns#transcriptIds'), multiple: true do |index|
index.as :stored_sortable
end
end
end
end
end
end
1 change: 1 addition & 0 deletions app/models/concerns/hyrax/file_set_behavior.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ module FileSetBehavior
include Hyrax::FileSet::Indexing
include Hyrax::FileSet::BelongsToWorks
include Hyrax::FileSet::Querying
include Hyrax::FileSet::Transcripts
include HumanReadableType
include CoreMetadata
include Hyrax::BasicMetadata
Expand Down
7 changes: 7 additions & 0 deletions app/models/concerns/hyrax/solr_document_behavior.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ module Hyrax
# end
#
# @see https://github.com/projectblacklight/blacklight/wiki/Understanding-Rails-and-Blacklight#models

# rubocop:disable Metrics/ModuleLength
module SolrDocumentBehavior
ModelWrapper = ActiveFedoraDummyModel # alias for backward compatibility

Expand Down Expand Up @@ -164,10 +166,15 @@ def flexible?
schema_version.present?
end

def transcript_ids
self['transcript_ids_ssim']
end

private

def model_classifier(classifier)
classifier || ActiveFedora.model_mapper
end
end
# rubocop:enable Metrics/ModuleLength
end
35 changes: 35 additions & 0 deletions app/presenters/hyrax/annotates_content.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# frozen_string_literal: true

module Hyrax
module AnnotatesContent
extend ActiveSupport::Concern

include DisplaysTranscripts

def annotation_content
transcription_content if video? || audio?
end

private

def transcription_content
transcripts.map do |doc|
options = {
type: 'Text',
motivation: 'supplementing',
body_id: transcript_url(doc, host: hostname, file_ext: file_ext(doc.mime_type)),
format: doc.mime_type,
label: doc.title_or_label
}
options[:language] = language_code(doc.language) if language_code(doc.language)
IIIFManifest::V3::AnnotationContent.new(**options)
end
end

# If you change the accepted mime types in Hyrax::TranscriptsBehavior,
# you may also want to override this method
def file_ext(_mime_type)
"vtt"
end
end
end
59 changes: 59 additions & 0 deletions app/presenters/hyrax/displays_transcripts.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# frozen_string_literal: true
module Hyrax
module DisplaysTranscripts
extend ActiveSupport::Concern

# @return [Array<SolrDocument>] the solr documents represented by
# the audio/video file set's transcript_ids
def transcripts
return [] if transcript_ids.blank?
@transcripts ||= begin
results = Hyrax::SolrQueryService.new
.accessible_by(
ability: (try(:current_ability) || ability),
action: :read
)
.with_ids(ids: transcript_ids)
.solr_documents
sort_transcripts_by_language(results)
end
end

def transcript_url(document, host: request.base_url, file_ext: "vtt")
Hyrax::Engine.routes.url_helpers.transcript_url(document.id, host: host, file_ext: file_ext)
end

# Try our best to convert language field to an ISO 639-1 code for use in the IIIF manifest.
# @param [Array<String>] - a solr document's language field. Parseable values include an ISO 639-1 code,
# an ISO 639-3 code, or the English name for a language
# Examples: https://github.com/scsmith/language_list#examples
# @return [String or NilClass] - the 2-letter code, or nil if no value or value is unparseable
def language_code(language)
return if language.empty?
value = language.first
if URI.parse(value).scheme
# This is probably a Library of Congress languages URI
# like http://id.loc.gov/vocabulary/iso639-3/eng, which can
# be configured with the Questioning Authority gem.
# Try to extract the code from the URI.
LanguageList::LanguageInfo.find(value.split("/").last).try(:iso_639_1)
else
# Otherwise, assume it is a language code or name and try
# to convert it to a 2-letter code
LanguageList::LanguageInfo.find(value).try(:iso_639_1)
end
end

private

def sort_transcripts_by_language(results)
current_locale = I18n.locale.to_s

# Sort alphabetically by language code
sorted = results.sort_by { |doc| language_code(doc.language) || '' }

# Move current locale to front
sorted.partition { |doc| language_code(doc.language) == current_locale }.flatten
end
end
end
1 change: 1 addition & 0 deletions app/presenters/hyrax/file_set_presenter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class FileSetPresenter
include WithEvents
include DisplaysImage
include MissingMethodBehavior
include DisplaysTranscripts

attr_accessor :solr_document, :current_ability, :request

Expand Down
16 changes: 16 additions & 0 deletions app/presenters/hyrax/iiif_manifest_presenter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,21 @@ def sequence_rendering
end
end

##
# Provides a link to the work page as the homepage for the object
# represented by the IIIF manifest (see
# https://iiif.io/api/cookbook/recipe/0047-homepage/).
# This can be helpful if the IIIF manifest is reused on an external site.
# @return [Array<Hash{String => String}>]
Comment thread
orangewolf marked this conversation as resolved.
def homepage
[{
'id' => Rails.application.routes.url_helpers.polymorphic_url(model, host: hostname),
'type' => 'Text',
'format' => 'text/html',
'label' => { 'none' => [Array(title).first || ''] }
}]
end

##
# @return [Boolean]
def work?
Expand Down Expand Up @@ -189,6 +204,7 @@ class DisplayImagePresenter < Draper::Decorator

include Hyrax::DisplaysImage
include Hyrax::DisplaysContent
include Hyrax::AnnotatesContent

##
# @!attribute [w] ability
Expand Down
Loading
Loading