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.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +7 -0
  5. data/Gemfile +11 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +60 -0
  8. data/Rakefile +7 -0
  9. data/bin/console +14 -0
  10. data/bin/setup +8 -0
  11. data/bpl-derivatives.gemspec +50 -0
  12. data/lib/bpl/derivatives.rb +74 -0
  13. data/lib/bpl/derivatives/audio_encoder.rb +27 -0
  14. data/lib/bpl/derivatives/config.rb +64 -0
  15. data/lib/bpl/derivatives/datastream_decorator.rb +31 -0
  16. data/lib/bpl/derivatives/input_object_decorator.rb +11 -0
  17. data/lib/bpl/derivatives/io_decorator.rb +15 -0
  18. data/lib/bpl/derivatives/logger.rb +25 -0
  19. data/lib/bpl/derivatives/output_object_decorator.rb +12 -0
  20. data/lib/bpl/derivatives/processors.rb +18 -0
  21. data/lib/bpl/derivatives/processors/audio.rb +5 -0
  22. data/lib/bpl/derivatives/processors/document.rb +45 -0
  23. data/lib/bpl/derivatives/processors/ffmpeg.rb +21 -0
  24. data/lib/bpl/derivatives/processors/image.rb +76 -0
  25. data/lib/bpl/derivatives/processors/jpeg2k_image.rb +127 -0
  26. data/lib/bpl/derivatives/processors/processor.rb +43 -0
  27. data/lib/bpl/derivatives/processors/raw_image.rb +37 -0
  28. data/lib/bpl/derivatives/processors/shell_based_processor.rb +103 -0
  29. data/lib/bpl/derivatives/processors/video.rb +10 -0
  30. data/lib/bpl/derivatives/processors/video/config.rb +66 -0
  31. data/lib/bpl/derivatives/processors/video/processor.rb +41 -0
  32. data/lib/bpl/derivatives/runners/audio_derivatives.rb +7 -0
  33. data/lib/bpl/derivatives/runners/document_derivatives.rb +7 -0
  34. data/lib/bpl/derivatives/runners/image_derivatives.rb +15 -0
  35. data/lib/bpl/derivatives/runners/jpeg2k_image_derivatives.rb +15 -0
  36. data/lib/bpl/derivatives/runners/pdf_derivatives.rb +4 -0
  37. data/lib/bpl/derivatives/runners/runner.rb +59 -0
  38. data/lib/bpl/derivatives/runners/video_derivatives.rb +7 -0
  39. data/lib/bpl/derivatives/services/capability_service.rb +17 -0
  40. data/lib/bpl/derivatives/services/mime_type_service.rb +14 -0
  41. data/lib/bpl/derivatives/services/persist_basic_contained_output_file_service.rb +73 -0
  42. data/lib/bpl/derivatives/services/persist_datastream_output_service.rb +30 -0
  43. data/lib/bpl/derivatives/services/persist_file_system_output_service.rb +31 -0
  44. data/lib/bpl/derivatives/services/persist_output_file_service.rb +24 -0
  45. data/lib/bpl/derivatives/services/retrieve_source_file_from_datastream_service.rb +12 -0
  46. data/lib/bpl/derivatives/services/retrieve_source_file_service.rb +13 -0
  47. data/lib/bpl/derivatives/services/tempfile_service.rb +65 -0
  48. data/lib/bpl/derivatives/version.rb +5 -0
  49. data/lib/color_profiles/license.txt +7 -0
  50. data/lib/color_profiles/sRGB_IEC61966-2-1_no_black_scaling.icc +0 -0
  51. metadata +238 -0
@@ -0,0 +1,10 @@
1
+ module BPL::Derivatives::Processors
2
+ module Video
3
+ extend ActiveSupport::Autoload
4
+
5
+ eager_autoload do
6
+ autoload :Processor
7
+ autoload :Config
8
+ end
9
+ end
10
+ end
@@ -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,7 @@
1
+ module BPL::Derivatives
2
+ class AudioDerivatives < Runner
3
+ def self.processor_class
4
+ Processors::Audio
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module BPL::Derivatives
2
+ class DocumentDerivatives < Runner
3
+ def self.processor_class
4
+ Processors::Document
5
+ end
6
+ end
7
+ 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,4 @@
1
+ module BPL::Derivatives
2
+ class PdfDerivatives < ImageDerivatives
3
+ end
4
+ 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,7 @@
1
+ module BPL::Derivatives
2
+ class VideoDerivatives < Runner
3
+ def self.processor_class
4
+ Processors::Video::Processor
5
+ end
6
+ end
7
+ 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