bpl-derivatives 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.rspec +1 -0
- data/.travis.yml +7 -0
- data/Gemfile +11 -0
- data/LICENSE.txt +21 -0
- data/README.md +60 -0
- data/Rakefile +7 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/bpl-derivatives.gemspec +50 -0
- data/lib/bpl/derivatives.rb +74 -0
- data/lib/bpl/derivatives/audio_encoder.rb +27 -0
- data/lib/bpl/derivatives/config.rb +64 -0
- data/lib/bpl/derivatives/datastream_decorator.rb +31 -0
- data/lib/bpl/derivatives/input_object_decorator.rb +11 -0
- data/lib/bpl/derivatives/io_decorator.rb +15 -0
- data/lib/bpl/derivatives/logger.rb +25 -0
- data/lib/bpl/derivatives/output_object_decorator.rb +12 -0
- data/lib/bpl/derivatives/processors.rb +18 -0
- data/lib/bpl/derivatives/processors/audio.rb +5 -0
- data/lib/bpl/derivatives/processors/document.rb +45 -0
- data/lib/bpl/derivatives/processors/ffmpeg.rb +21 -0
- data/lib/bpl/derivatives/processors/image.rb +76 -0
- data/lib/bpl/derivatives/processors/jpeg2k_image.rb +127 -0
- data/lib/bpl/derivatives/processors/processor.rb +43 -0
- data/lib/bpl/derivatives/processors/raw_image.rb +37 -0
- data/lib/bpl/derivatives/processors/shell_based_processor.rb +103 -0
- data/lib/bpl/derivatives/processors/video.rb +10 -0
- data/lib/bpl/derivatives/processors/video/config.rb +66 -0
- data/lib/bpl/derivatives/processors/video/processor.rb +41 -0
- data/lib/bpl/derivatives/runners/audio_derivatives.rb +7 -0
- data/lib/bpl/derivatives/runners/document_derivatives.rb +7 -0
- data/lib/bpl/derivatives/runners/image_derivatives.rb +15 -0
- data/lib/bpl/derivatives/runners/jpeg2k_image_derivatives.rb +15 -0
- data/lib/bpl/derivatives/runners/pdf_derivatives.rb +4 -0
- data/lib/bpl/derivatives/runners/runner.rb +59 -0
- data/lib/bpl/derivatives/runners/video_derivatives.rb +7 -0
- data/lib/bpl/derivatives/services/capability_service.rb +17 -0
- data/lib/bpl/derivatives/services/mime_type_service.rb +14 -0
- data/lib/bpl/derivatives/services/persist_basic_contained_output_file_service.rb +73 -0
- data/lib/bpl/derivatives/services/persist_datastream_output_service.rb +30 -0
- data/lib/bpl/derivatives/services/persist_file_system_output_service.rb +31 -0
- data/lib/bpl/derivatives/services/persist_output_file_service.rb +24 -0
- data/lib/bpl/derivatives/services/retrieve_source_file_from_datastream_service.rb +12 -0
- data/lib/bpl/derivatives/services/retrieve_source_file_service.rb +13 -0
- data/lib/bpl/derivatives/services/tempfile_service.rb +65 -0
- data/lib/bpl/derivatives/version.rb +5 -0
- data/lib/color_profiles/license.txt +7 -0
- data/lib/color_profiles/sRGB_IEC61966-2-1_no_black_scaling.icc +0 -0
- metadata +238 -0
@@ -0,0 +1,66 @@
|
|
1
|
+
module BPL::Derivatives::Processors::Video
|
2
|
+
class Config
|
3
|
+
attr_writer :video_bitrate, :video_attributes, :size_attributes, :audio_attributes
|
4
|
+
|
5
|
+
def video_bitrate
|
6
|
+
@video_bitrate ||= default_video_bitrate
|
7
|
+
end
|
8
|
+
|
9
|
+
def video_attributes
|
10
|
+
@video_attributes ||= default_video_attributes
|
11
|
+
end
|
12
|
+
|
13
|
+
def size_attributes
|
14
|
+
@size_attributes ||= default_size_attributes
|
15
|
+
end
|
16
|
+
|
17
|
+
def audio_attributes
|
18
|
+
@audio_attributes ||= default_audio_attributes
|
19
|
+
end
|
20
|
+
|
21
|
+
def mpeg4
|
22
|
+
audio_encoder = BPL::Derivatives::AudioEncoder.new
|
23
|
+
@mpeg4 ||= CodecConfig.new("-vcodec libx264 -acodec #{audio_encoder.audio_encoder}")
|
24
|
+
end
|
25
|
+
|
26
|
+
def webm
|
27
|
+
@webm ||= CodecConfig.new('-vcodec libvpx -acodec libvorbis')
|
28
|
+
end
|
29
|
+
|
30
|
+
def mkv
|
31
|
+
@mkv ||= CodecConfig.new('-vcodec ffv1')
|
32
|
+
end
|
33
|
+
|
34
|
+
def jpeg
|
35
|
+
@jpeg ||= CodecConfig.new('-vcodec mjpeg')
|
36
|
+
end
|
37
|
+
|
38
|
+
class CodecConfig
|
39
|
+
attr_writer :codec
|
40
|
+
|
41
|
+
def initialize(default)
|
42
|
+
@codec = default
|
43
|
+
end
|
44
|
+
|
45
|
+
attr_reader :codec
|
46
|
+
end
|
47
|
+
|
48
|
+
protected
|
49
|
+
|
50
|
+
def default_video_bitrate
|
51
|
+
'345k'
|
52
|
+
end
|
53
|
+
|
54
|
+
def default_video_attributes
|
55
|
+
"-g 30 -b:v #{video_bitrate}"
|
56
|
+
end
|
57
|
+
|
58
|
+
def default_size_attributes
|
59
|
+
"320x240"
|
60
|
+
end
|
61
|
+
|
62
|
+
def default_audio_attributes
|
63
|
+
"-ac 2 -ab 96k -ar 44100"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module BPL::Derivatives::Processors
|
2
|
+
module Video
|
3
|
+
class Processor < BPL::Derivatives::Processors::Processor
|
4
|
+
include Ffmpeg
|
5
|
+
|
6
|
+
cattr_accessor :config
|
7
|
+
self.config = Config.new
|
8
|
+
|
9
|
+
protected
|
10
|
+
|
11
|
+
def options_for(format)
|
12
|
+
input_options = ""
|
13
|
+
output_options = "-s #{config.size_attributes} #{codecs(format)}"
|
14
|
+
|
15
|
+
if format == "jpg"
|
16
|
+
input_options += " -itsoffset -2"
|
17
|
+
output_options += " -vframes 1 -an -f rawvideo"
|
18
|
+
else
|
19
|
+
output_options += " #{config.video_attributes} #{config.audio_attributes}"
|
20
|
+
end
|
21
|
+
|
22
|
+
{ Ffmpeg::OUTPUT_OPTIONS => output_options, Ffmpeg::INPUT_OPTIONS => input_options }
|
23
|
+
end
|
24
|
+
|
25
|
+
def codecs(format)
|
26
|
+
case format
|
27
|
+
when 'mp4'
|
28
|
+
config.mpeg4.codec
|
29
|
+
when 'webm'
|
30
|
+
config.webm.codec
|
31
|
+
when "mkv"
|
32
|
+
config.mkv.codec
|
33
|
+
when "jpg"
|
34
|
+
config.jpeg.codec
|
35
|
+
else
|
36
|
+
raise ArgumentError, "Unknown format `#{format}'"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module BPL::Derivatives
|
2
|
+
class ImageDerivatives < Runner
|
3
|
+
# Adds format: 'png' as the default to each of the directives
|
4
|
+
def self.transform_directives(options)
|
5
|
+
options.each do |directive|
|
6
|
+
directive.reverse_merge!(format: 'jpg')
|
7
|
+
end
|
8
|
+
options
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.processor_class
|
12
|
+
Processors::Image
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module BPL::Derivatives
|
2
|
+
class Jpeg2kImageDerivatives < Runner
|
3
|
+
# # Adds format: 'png' as the default to each of the directives
|
4
|
+
def self.transform_directives(options)
|
5
|
+
options.each do |directive|
|
6
|
+
directive.reverse_merge!(format: 'jp2')
|
7
|
+
end
|
8
|
+
options
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.processor_class
|
12
|
+
Processors::Jpeg2kImage
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module BPL
|
2
|
+
module Derivatives
|
3
|
+
class Runner
|
4
|
+
class << self
|
5
|
+
attr_writer :output_file_service
|
6
|
+
end
|
7
|
+
|
8
|
+
# Use the output service configured for this class or default to the global setting
|
9
|
+
def self.output_file_service
|
10
|
+
@output_file_service || BPL::Derivatives.config.output_file_service
|
11
|
+
end
|
12
|
+
|
13
|
+
class << self
|
14
|
+
attr_writer :source_file_service
|
15
|
+
end
|
16
|
+
|
17
|
+
# Use the source service configured for this class or default to the global setting
|
18
|
+
def self.source_file_service
|
19
|
+
@source_file_service || BPL::Derivatives.config.source_file_service
|
20
|
+
end
|
21
|
+
|
22
|
+
# @param [String, ActiveFedora::Base] object_or_filename path to the source file, or an object
|
23
|
+
# @param [Hash] options options to pass to the encoder
|
24
|
+
# @options options [Array] :outputs a list of desired outputs, each entry is a hash that has :label (optional), :format and :url
|
25
|
+
def self.create(object_or_filename, options)
|
26
|
+
io_object = input_object(object_or_filename, options)
|
27
|
+
source_file(io_object, options) do |f|
|
28
|
+
io_object.source_path = f.path
|
29
|
+
transform_directives(options.delete(:outputs)).each do |instructions|
|
30
|
+
processor_class.new(io_object,
|
31
|
+
instructions.merge(source_file_service: source_file_service),
|
32
|
+
output_file_service: output_file_service).process
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.input_object(object_or_filename, options)
|
38
|
+
if options.key?(:source_datastream)
|
39
|
+
return BPL::Derivatives::DatastreamDecorator.new(object_or_filename, options.fetch(:source_datastream))
|
40
|
+
else
|
41
|
+
return BPL::Derivatives::InputObjectDecorator.new(object_or_filename)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Override this method if you need to add any defaults
|
46
|
+
def self.transform_directives(options)
|
47
|
+
options
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.source_file(object_or_filename, options, &block)
|
51
|
+
source_file_service.call(object_or_filename, options, &block)
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.processor_class
|
55
|
+
raise "Overide the processor_class method in a sub class"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'open3'
|
2
|
+
|
3
|
+
module BPL::Derivatives
|
4
|
+
class CapabilityService
|
5
|
+
attr_accessor :ffmpeg_output
|
6
|
+
def capture_output
|
7
|
+
@ffmpeg_output = Open3.capture3('ffmpeg -codecs').to_s
|
8
|
+
rescue StandardError
|
9
|
+
BPL::Dervivatives.base_logger.warn('Unable to find ffmpeg')
|
10
|
+
@ffmpeg_output = ""
|
11
|
+
end
|
12
|
+
|
13
|
+
def fdk_aac?
|
14
|
+
@ffmpeg_output.include?('--enable-libfdk-aac') || @ffmpeg_output.include?('--with-fdk-aac')
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'mime/types'
|
2
|
+
|
3
|
+
module BPL::Derivatives
|
4
|
+
module MimeTypeService
|
5
|
+
# @param [String] file_path path to a file
|
6
|
+
def self.mime_type(file_path)
|
7
|
+
MIME::Types.type_for(file_path).first.to_s
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.type_lookup(m_type)
|
11
|
+
MIME::Types[m_type].first
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module BPL::Derivatives
|
2
|
+
# This Service is an implementation of the Hydra::Derivatives::PeristOutputFileService
|
3
|
+
# It supports basic contained files, which is the behavior associated with Fedora 3 file datastreams that were migrated to Fedora 4
|
4
|
+
# and, at the time that this class was authored, corresponds to the behavior of ActiveFedora::Base.attach_file and ActiveFedora::Base.attached_files
|
5
|
+
### Rename this
|
6
|
+
class PersistBasicContainedOutputFileService < PersistOutputFileService
|
7
|
+
# This method conforms to the signature of the .call method on Hydra::Derivatives::PeristOutputFileService
|
8
|
+
# * Persists the file within the object at destination_name
|
9
|
+
#
|
10
|
+
# NOTE: Uses basic containment. If you want to use direct containment (ie. with PCDM) you must use a different service (ie. Hydra::Works::AddFileToGenericFile Service)
|
11
|
+
#
|
12
|
+
# @param [BPL::Derivatives::OutputObjectDecorator] content the data to be persisted
|
13
|
+
# @param [Hash] directives directions which can be used to determine where to persist to.
|
14
|
+
# @option directives [String] url This can determine the path of the object.
|
15
|
+
# @option directives [String] format The file extension (e.g. 'jpg')
|
16
|
+
def self.call(object, directives)
|
17
|
+
file = io(object.content, directives)
|
18
|
+
remote_file = retrieve_remote_file(directives)
|
19
|
+
remote_file.content = file
|
20
|
+
remote_file.mime_type = determine_mime_type(file)
|
21
|
+
remote_file.original_name = determine_original_name(file)
|
22
|
+
remote_file.save
|
23
|
+
end
|
24
|
+
|
25
|
+
# @param file [Hydra::Derivatives::IoDecorator]
|
26
|
+
def self.determine_original_name(file)
|
27
|
+
if file.respond_to? :original_filename
|
28
|
+
file.original_filename
|
29
|
+
else
|
30
|
+
"derivative"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# @param file [Hydra::Derivatives::IoDecorator]
|
35
|
+
def self.determine_mime_type(file)
|
36
|
+
if file.respond_to? :mime_type
|
37
|
+
file.mime_type
|
38
|
+
else
|
39
|
+
"appliction/octet-stream"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Override this implementation if you need a remote file from a different location
|
44
|
+
# @return [OutputObjectDelegator]
|
45
|
+
def self.retrieve_remote_file(directives)
|
46
|
+
uri = URI(directives.fetch(:url))
|
47
|
+
raise ArgumentError, "#{uri} is not an http uri" unless uri.scheme == 'http'
|
48
|
+
BPL::Derivatives.config.output_file_class.constantize.new(uri.to_s)
|
49
|
+
end
|
50
|
+
private_class_method :retrieve_remote_file
|
51
|
+
|
52
|
+
# @param [IO,String] content the data to be persisted
|
53
|
+
# @param [Hash] directives directions which can be used to determine where to persist to.
|
54
|
+
# @return [Hydra::Derivatives::IoDecorator]
|
55
|
+
def self.io(content, directives)
|
56
|
+
charset = charset(content) if directives[:format] == 'txt' || !directives.fetch(:binary, true)
|
57
|
+
BPL::Derivatives::IoDecorator.new(content, new_mime_type(directives.fetch(:format), charset))
|
58
|
+
end
|
59
|
+
private_class_method :io
|
60
|
+
|
61
|
+
def self.new_mime_type(extension, charset = nil)
|
62
|
+
fmt = mime_format(extension)
|
63
|
+
fmt += "; charset=#{charset}" if charset
|
64
|
+
fmt
|
65
|
+
end
|
66
|
+
|
67
|
+
# Strings (from FullText) have encoding. Retrieve it
|
68
|
+
def self.charset(content)
|
69
|
+
content.encoding.name if content.respond_to?(:encoding)
|
70
|
+
end
|
71
|
+
private_class_method :charset
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module BPL::Derivatives
|
2
|
+
class PersistDatastreamOutputService < PersistOutputFileService
|
3
|
+
def self.call(object, directives)
|
4
|
+
datastream = retrieve_datastream(object.original_object, directives)
|
5
|
+
datastream.content = object.content
|
6
|
+
datastream.mimeType = determine_mime_type(directives.fetch(:format))
|
7
|
+
datastream.save
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.determine_mime_type(format)
|
11
|
+
mime_format(format)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.retrieve_datastream(object, directives)
|
15
|
+
dsid = directives.fetch(:dsid)
|
16
|
+
raise ArgumentError, "#{dsid} is blank" if dsid.blank?
|
17
|
+
output_datastream(object, dsid)
|
18
|
+
end
|
19
|
+
private_class_method :retrieve_datastream
|
20
|
+
|
21
|
+
|
22
|
+
def self.output_datastream(object, dsid)
|
23
|
+
return object.datastreams[dsid] if object.datastreams[dsid]
|
24
|
+
ds = BPL::Derivatives.config.output_file_class.new(object.inner_object, dsid) #ActiveFedora::Datastream
|
25
|
+
object.add_datastream(ds)
|
26
|
+
ds
|
27
|
+
end
|
28
|
+
private_class_method :output_datastream
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module BPL::Derivatives
|
2
|
+
class PersistFileSystemOutputService < PersistOutputFileService
|
3
|
+
def self.call(object, directives)
|
4
|
+
filename = determine_original_name(object.content)
|
5
|
+
write_file(object.content, directives, filename)
|
6
|
+
end
|
7
|
+
|
8
|
+
# @param file [Hydra::Derivatives::IoDecorator]
|
9
|
+
def self.determine_original_name(file)
|
10
|
+
if file.respond_to? :original_filename
|
11
|
+
file.original_filename
|
12
|
+
else
|
13
|
+
"derivative"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.write_file(content, directives, filename)
|
18
|
+
path = directives.fetch(:path)
|
19
|
+
fmt = directives.fetch(:format)
|
20
|
+
full_file_name = File.join(path, "#{filename}.#{fmt}")
|
21
|
+
raise ArgumentError, "path directive is blank" if path.blank?
|
22
|
+
raise ArgumentError, "format directive is blank" if fmt.blank?
|
23
|
+
File.open(full_file_name, "w+") do |f|
|
24
|
+
f.write(content)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
private_class_method :write_file
|
28
|
+
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module BPL::Derivatives
|
2
|
+
class PersistOutputFileService
|
3
|
+
# Persists the file within the object at destination_name. Uses basic containment.
|
4
|
+
# If you want to use direct containment (ie. with PCDM) you must use a different service (ie. Hydra::Works::AddFileToGenericFile Service)
|
5
|
+
# @param [String] file_path the path to the file to be added
|
6
|
+
# @param [Hash] directives directions which can be used to determine where to persist to.
|
7
|
+
# @option directives [String] url This can determine the path of the object.
|
8
|
+
def self.call(__object_or_file_path, _directives)
|
9
|
+
raise NotImplementedError, "PersistOutputFileService is an abstract class. Implement `call' on #{self.class.name}"
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.mime_format(extension)
|
13
|
+
case extension
|
14
|
+
when 'mp4'
|
15
|
+
'video/mp4' # default is application/mp4
|
16
|
+
when 'webm'
|
17
|
+
'video/webm' # default is audio/webm
|
18
|
+
else
|
19
|
+
MIME::Types.type_for(extension).first.to_s
|
20
|
+
end
|
21
|
+
end
|
22
|
+
private_class_method :mime_format
|
23
|
+
end
|
24
|
+
end
|