hydra-derivatives 3.2.2 → 3.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 505c538dd6ded3a65f826eb0d93040bc4edf2a1e
4
- data.tar.gz: 0323253a69f02a91b84e07f546e42debd822ea7f
3
+ metadata.gz: b300c88a53fe0b8e79f9ace11145653e878f8614
4
+ data.tar.gz: dfe48e84c0bd64e74d77d3a4c9cb103f2aaee0dc
5
5
  SHA512:
6
- metadata.gz: c27070a785686b16bb37c60d0a30cf175da384e2818942efb3d7994ff0eacf2a61b84aa0f1189216d4242ed235bda6bda1a43001b3dcb7ad09efcb5a9a085254
7
- data.tar.gz: 453cec1e1d0b1f5940ad251f6271771e0f062f37e53342f80e31f783005b1a87615868d615e569389dcef1635e2332ef59e7a7f92048c93018e0a6e4f0876349
6
+ metadata.gz: 2f1f0183aae8af14487552ebf3c5f9453db4fa64c7f51fa9f1ac6bc4453cf58d3b9bf0a25c5296450c4006eb7b45b4fb6d6093f1cf46a7548d0a189854cfdb19
7
+ data.tar.gz: '058e7057eae73e19451e8d9926576305f85416bd1c3db0bdba279acc97bd629fa27d9ac6200dd56f84505a780d1371937e74f8eab970983a3276af1d3092fd27'
data/.travis.yml CHANGED
@@ -6,8 +6,8 @@ before_install:
6
6
  - gem install bundler
7
7
  rvm:
8
8
  - 2.2.5
9
- - 2.3.1
10
- - 2.4.0
9
+ - 2.3.4
10
+ - 2.4.1
11
11
  env:
12
12
  global:
13
13
  - NOKOGIRI_USE_SYSTEM_LIBRARIES=true
data/README.md CHANGED
@@ -52,6 +52,8 @@ Then when you call `obj.create_derivatives` two new files, 'thumbnail' and 'cont
52
52
 
53
53
  We recommend you run `obj.create_derivatives` in a background worker, because some derivative creation (especially videos) can take a long time.
54
54
 
55
+ The ActiveEncode runner provides support for using external video transcoding services like Amazon Elastic Transcoder. See [ActiveEncode](https://github.com/samvera-labs/active_encode) for details on specific adapters supported.
56
+
55
57
  ## Configuration
56
58
 
57
59
  ### Retrieving from a basic container in Fedora
@@ -74,6 +76,7 @@ Hydra::Derivatives::Processors::Video::Processor.timeout = 10.minutes
74
76
  Hydra::Derivatives::Processors::Document.timeout = 5.minutes
75
77
  Hydra::Derivatives::Processors::Audio.timeout = 10.minutes
76
78
  Hydra::Derivatives::Processors::Image.timeout = 5.minutes
79
+ Hydra::Derivatives::Processors::ActiveEncode.timeout = 5.minutes
77
80
 
78
81
  ```
79
82
 
@@ -88,6 +91,28 @@ Hydra::Derivatives::Processors::Video::Processor.config.mkv.codec = '-vcodec ffv
88
91
  Hydra::Derivatives::Processors::Video::Processor.config.jpeg.codec = '-vcodec mjpeg'
89
92
  ```
90
93
 
94
+ ### Configuration for Audio/Video Processing with ActiveEncode
95
+
96
+ ```ruby
97
+ # Set the transcoding engine
98
+ ActiveEncode::Base.engine_adapter = :elastic_transcoder
99
+
100
+ # Sleep time (in seconds) to poll for status of encoding job
101
+ Hydra::Derivatives.active_encode_poll_time = 10
102
+
103
+ # If you want to use a different class for the source file service
104
+ Hydra::Derivatives::ActiveEncodeDerivatives.source_file_service = MyCustomSourceFileService
105
+
106
+ # If you want to use a different class for the output file service
107
+ Hydra::Derivatives::ActiveEncodeDerivatives.output_file_service = MyCustomOutputFileService
108
+ ```
109
+
110
+ Note: Please don't confuse these methods with the similar methods in the parent class:
111
+ `Hydra::Derivatives.source_file_service` and `Hydra::Derivatives.output_file_service`
112
+
113
+ For additional documentation on using ActiveEncode, see:
114
+ * [Using Amazon Elastic Transcoder](doc/amazon_elastic_transcoder.md)
115
+
91
116
  ### Additional Directives
92
117
 
93
118
  #### Layers
data/VERSION CHANGED
@@ -1 +1 @@
1
- 3.2.2
1
+ 3.3.0
@@ -0,0 +1,116 @@
1
+ # Create Audio and Video Derivatives using Amazon Elastic Transcoder
2
+
3
+ `hydra-derivatives` uses the
4
+ [active\_encode gem](https://github.com/projecthydra-labs/active_encode)
5
+ to allow you to use different encoding services.
6
+ These instructions are for Amazon's Elastic Transcoder service.
7
+
8
+ ## Prerequsites
9
+
10
+ ### Set up the Elastic Transcoder Pipeline
11
+
12
+ Set up a pipeline on AWS Elastic Transcoder that defines:
13
+
14
+ * input bucket
15
+ * bucket for transcoded files
16
+ * bucket for thumbnails
17
+
18
+ ### Configure AWS credentials
19
+
20
+ Optional: If you don't want to pass these values in your ruby code using `Aws.config`, you can set environment variables instead:
21
+
22
+ * AWS\_ACCESS\_KEY\_ID
23
+ * AWS\_SECRET\_ACCESS\_KEY
24
+ * AWS\_REGION
25
+
26
+ ### Install gems
27
+
28
+ Add to your `Gemfile`:
29
+
30
+ * aws-sdk
31
+
32
+ ### Configure initializer
33
+
34
+ In an initializer file such as `config/initializers/active_encode.rb`, make sure you have the following code:
35
+
36
+ ```ruby
37
+ # Use Amazon's Elastic Transcoder
38
+ ActiveEncode::Base.engine_adapter = :elastic_transcoder
39
+ ```
40
+
41
+ ## How to create derivatives (Multiple derivatives per Elastic Transcoder job)
42
+
43
+ ```ruby
44
+ # Access config for AWS
45
+ Aws.config[:access_key_id] = 'put your access key here'
46
+ Aws.config[:secret_access_key] = 'put your secret key here'
47
+ Aws.config[:region] = 'us-east-1'
48
+
49
+ # The pipeline that I set up in Elastic Transcoder
50
+ pipeline_id = '1490715200916-25b08y'
51
+
52
+ # The file "sample_data.mp4" has already been uploaded to the input bucket for my pipeline.
53
+ input_file = 'sample_data.mp4'
54
+
55
+ # Choose a name for the output files
56
+ base_file_name = 'output_file_17'
57
+
58
+ # Settings for a low-res video derivative using a preset for a 320x240 resolution mp4 file
59
+ low_res_video = { key: "#{base_file_name}.mp4", preset_id: '1351620000001-000061' }
60
+
61
+ # Settings for a flash video derivative
62
+ flash_video = { key: "#{base_file_name}.flv", preset_id: '1351620000001-100210' }
63
+
64
+ # Settings to send to the Elastic Transcoder job
65
+ job_settings = { pipeline_id: pipeline_id, output_key_prefix: "active_encode-demo_app/", outputs: [low_res_video, flash_video] }
66
+
67
+ # Run the encoding
68
+ Hydra::Derivatives::ActiveEncodeDerivatives.create(input_file, outputs: [job_settings])
69
+
70
+ # Note: Your rails console will not return to the prompt until the encoding is complete,
71
+ # so it might sit there for several minutes with no feedback.
72
+ # Use the AWS console to see the current status of the encoding.
73
+ ```
74
+
75
+ ## How to create derivatives (One derivative per Elastic Transcoder job)
76
+
77
+ If you want to run a separate Elastic Transcoder job for each derivative file, you could do something like this:
78
+
79
+ ```ruby
80
+ # Settings for a low-res video derivative using a preset for a 320x240 resolution mp4 file.
81
+ low_res_preset_id = '1351620000001-000061'
82
+ low_res_output_file = 'output_15.mp4'
83
+ low_res_video = { pipeline_id: pipeline_id, output_key_prefix: "active_encode-demo_app/", outputs: [{ key: low_res_output_file, preset_id: low_res_preset_id }] }
84
+
85
+ # Settings for a flash video derivative
86
+ flash_preset_id = '1351620000001-100210'
87
+ flash_output_file = 'output_15.flv'
88
+ flash_video = { pipeline_id: pipeline_id, output_key_prefix: "active_encode-demo_app/", outputs: [{ key: flash_output_file, preset_id: flash_preset_id }] }
89
+
90
+ Hydra::Derivatives::ActiveEncodeDerivatives.create(input_file, outputs: [low_res_video, flash_video])
91
+ ```
92
+
93
+ ## How to pass in a ruby object
94
+
95
+ If you want to pass in an `ActiveFedora::Base` object (or some other record) instead of just a String for the input file name, you need to set the `source` option to specify which method to call on your object to get the file name. For example:
96
+
97
+ ```ruby
98
+ # Some object that contains the source file name
99
+ class Video
100
+ attr_accessor :source_file_name
101
+ end
102
+
103
+ video_record = Video.new
104
+ video_record.source_file_name = 'sample_data.mp4'
105
+
106
+ Hydra::Derivatives::ActiveEncodeDerivatives.create(video_record, source: :source_file_name, outputs: [low_res_video])
107
+ ```
108
+
109
+ ## How to pass in a custom encode class
110
+
111
+ If you don't want to use the default encode class `::ActiveEncode::Base`, you can pass in `encode_class`:
112
+
113
+ ```ruby
114
+ Hydra::Derivatives::ActiveEncodeDerivatives.create(video_record, encode_class: MyCustomEncode, source: :source_file_name, outputs: [low_res_video])
115
+ ```
116
+
@@ -23,10 +23,12 @@ Gem::Specification.new do |spec|
23
23
  spec.add_development_dependency "solr_wrapper", "~> 0.4"
24
24
  spec.add_development_dependency 'fcrepo_wrapper', '~> 0.2'
25
25
 
26
- spec.add_dependency 'active-fedora', '>= 9.0', '< 12'
26
+ spec.add_dependency 'active-fedora', '>= 11.3.1', '< 12'
27
27
  spec.add_dependency 'mini_magick', '>= 3.2', '< 5'
28
28
  spec.add_dependency 'activesupport', '>= 4.0', '< 6'
29
29
  spec.add_dependency 'mime-types', '> 2.0', '< 4.0'
30
+ spec.add_dependency 'active_encode', '~>0.1'
31
+ spec.add_dependency 'addressable', '~>2.5'
30
32
  spec.add_dependency 'deprecation'
31
33
  end
32
34
 
@@ -11,6 +11,7 @@ module Hydra
11
11
  # Runners take a single input and produce one or more outputs
12
12
  # The runner typically accomplishes this by using one or more processors
13
13
  autoload_under 'runners' do
14
+ autoload :ActiveEncodeDerivatives
14
15
  autoload :AudioDerivatives
15
16
  autoload :DocumentDerivatives
16
17
  autoload :FullTextExtract
@@ -30,8 +31,10 @@ module Hydra
30
31
 
31
32
  autoload_under 'services' do
32
33
  autoload :RetrieveSourceFileService
34
+ autoload :RemoteSourceFile
33
35
  autoload :PersistOutputFileService
34
36
  autoload :PersistBasicContainedOutputFileService
37
+ autoload :PersistExternalFileOutputFileService
35
38
  autoload :TempfileService
36
39
  autoload :MimeTypeService
37
40
  end
@@ -48,7 +51,7 @@ module Hydra
48
51
  end
49
52
 
50
53
  CONFIG_METHODS = [:ffmpeg_path, :libreoffice_path, :temp_file_base, :fits_path, :kdu_compress_path,
51
- :kdu_compress_recipes, :enable_ffmpeg, :source_file_service, :output_file_service].freeze
54
+ :kdu_compress_recipes, :enable_ffmpeg, :source_file_service, :output_file_service, :active_encode_poll_time].freeze
52
55
  CONFIG_METHODS.each do |method|
53
56
  module_eval <<-RUBY
54
57
  def self.#{method}
@@ -5,7 +5,8 @@ module Hydra
5
5
  class Config
6
6
  attr_writer :ffmpeg_path, :libreoffice_path, :temp_file_base,
7
7
  :source_file_service, :output_file_service, :fits_path,
8
- :enable_ffmpeg, :kdu_compress_path, :kdu_compress_recipes
8
+ :enable_ffmpeg, :kdu_compress_path, :kdu_compress_recipes,
9
+ :active_encode_poll_time
9
10
 
10
11
  def ffmpeg_path
11
12
  @ffmpeg_path ||= 'ffmpeg'
@@ -72,6 +73,13 @@ module Hydra
72
73
  "Stiles={1024,1024}" ).gsub(/\s+/, " ").strip
73
74
  }
74
75
  end
76
+
77
+ # The poll time (in seconds) that the active encode
78
+ # processor will sleep before it checks the status of an
79
+ # encoding job.
80
+ def active_encode_poll_time
81
+ @active_encode_poll_time ||= 10
82
+ end
75
83
  end
76
84
  end
77
85
  end
@@ -6,6 +6,7 @@ module Hydra::Derivatives
6
6
  autoload :Processor
7
7
  end
8
8
 
9
+ autoload :ActiveEncode
9
10
  autoload :Audio
10
11
  autoload :Document
11
12
  autoload :Ffmpeg
@@ -0,0 +1,56 @@
1
+ require 'active_encode'
2
+
3
+ module Hydra::Derivatives::Processors
4
+ class ActiveEncodeError < StandardError
5
+ def initialize(status, source_path, errors = [])
6
+ msg = "ActiveEncode status was \"#{status}\" for #{source_path}"
7
+ msg = "#{msg}: #{errors.join(' ; ')}" if errors.any?
8
+ super(msg)
9
+ end
10
+ end
11
+
12
+ class ActiveEncode < Processor
13
+ class_attribute :timeout
14
+ attr_accessor :encode_class
15
+ attr_reader :encode_job
16
+
17
+ def initialize(source_path, directives, opts = {})
18
+ super
19
+ @encode_class = opts.delete(:encode_class) || ::ActiveEncode::Base
20
+ end
21
+
22
+ def process
23
+ @encode_job = encode_class.create(source_path, directives)
24
+ timeout ? wait_for_encode_job_with_timeout : wait_for_encode_job
25
+ encode_job.output.each do |output|
26
+ output_file_service.call(output, directives)
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def wait_for_encode_job_with_timeout
33
+ Timeout.timeout(timeout) { wait_for_encode_job }
34
+ rescue Timeout::Error
35
+ cleanup_after_timeout
36
+ end
37
+
38
+ # Wait until the encoding job is finished. If the status
39
+ # is anything other than 'completed', raise an error.
40
+ def wait_for_encode_job
41
+ sleep Hydra::Derivatives.active_encode_poll_time while encode_job.reload.running?
42
+ raise ActiveEncodeError.new(encode_job.state, source_path, encode_job.errors) unless encode_job.completed?
43
+ end
44
+
45
+ # After a timeout error, try to cancel the encoding.
46
+ def cleanup_after_timeout
47
+ encode_job.cancel!
48
+ rescue => e
49
+ cancel_error = e
50
+ ensure
51
+ msg = "Unable to process ActiveEncode derivative: The command took longer than #{timeout} seconds to execute. Encoding will be cancelled."
52
+ msg = "#{msg} An error occurred while trying to cancel encoding: #{cancel_error}" if cancel_error
53
+ raise Hydra::Derivatives::TimeoutError, msg
54
+ end
55
+ end
56
+ end
@@ -3,7 +3,7 @@ module Hydra::Derivatives::Processors
3
3
  include ShellBasedProcessor
4
4
 
5
5
  def self.encode(path, format, outdir)
6
- execute "#{Hydra::Derivatives.libreoffice_path} --invisible --headless --convert-to #{format} --outdir #{outdir} #{path}"
6
+ execute "#{Hydra::Derivatives.libreoffice_path} --invisible --headless --convert-to #{format} --outdir #{outdir} #{Shellwords.escape(path)}"
7
7
  end
8
8
 
9
9
  # Converts the document to the format specified in the directives hash.
@@ -14,7 +14,7 @@ module Hydra::Derivatives::Processors
14
14
  def encode(path, options, output_file)
15
15
  inopts = options[INPUT_OPTIONS] ||= "-y"
16
16
  outopts = options[OUTPUT_OPTIONS] ||= ""
17
- execute "#{Hydra::Derivatives.ffmpeg_path} #{inopts} -i \"#{path}\" #{outopts} #{output_file}"
17
+ execute "#{Hydra::Derivatives.ffmpeg_path} #{inopts} -i #{Shellwords.escape(path)} #{outopts} #{output_file}"
18
18
  end
19
19
  end
20
20
  end
@@ -75,7 +75,7 @@ module Hydra::Derivatives::Processors
75
75
 
76
76
  def encode(path, recipe, output_file)
77
77
  kdu_compress = Hydra::Derivatives.kdu_compress_path
78
- execute "#{kdu_compress} -quiet -i #{path} -o #{output_file} #{recipe}"
78
+ execute "#{kdu_compress} -quiet -i #{Shellwords.escape(path)} -o #{output_file} #{recipe}"
79
79
  end
80
80
 
81
81
  def tmp_file(ext)
@@ -0,0 +1,44 @@
1
+ module Hydra::Derivatives
2
+ class ActiveEncodeDerivatives < Runner
3
+ # @param [String, ActiveFedora::Base] object_or_filename source file name (or path), or an object that has a method that will return the file name
4
+ # @param [Hash] options options to pass to the encoder
5
+ # @option options [Symbol] :source a method that can be called on the object to retrieve the source file's name
6
+ # @option options [Symbol] :encode_class class name of the encode object (usually a subclass of ::ActiveEncode::Base)
7
+ # @options options [Array] :outputs a list of desired outputs
8
+ def self.create(object_or_filename, options)
9
+ processor_opts = processor_options(options)
10
+ source_file(object_or_filename, options) do |file_name|
11
+ transform_directives(options.delete(:outputs)).each do |instructions|
12
+ processor = processor_class.new(file_name, instructions, processor_opts)
13
+ processor.process
14
+ end
15
+ end
16
+ end
17
+
18
+ # Use the source service configured for this class or default to the remote file service
19
+ def self.source_file_service
20
+ @source_file_service || RemoteSourceFile
21
+ end
22
+
23
+ # Use the output service configured for this class or default to the external file service
24
+ def self.output_file_service
25
+ @output_file_service || PersistExternalFileOutputFileService
26
+ end
27
+
28
+ def self.processor_class
29
+ Processors::ActiveEncode
30
+ end
31
+
32
+ class << self
33
+ private
34
+
35
+ # Build an options hash specifically for the processor isolated from the runner options
36
+ def processor_options(options)
37
+ opts = { output_file_service: output_file_service }
38
+ encode_class = options.delete(:encode_class)
39
+ opts = opts.merge(encode_class: encode_class) if encode_class
40
+ opts
41
+ end
42
+ end
43
+ end
44
+ end
@@ -9,31 +9,55 @@ module Hydra::Derivatives
9
9
  #
10
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
11
  #
12
- # @param [#read] stream the data to be persisted
12
+ # @param [IO,String] content the data to be persisted
13
13
  # @param [Hash] directives directions which can be used to determine where to persist to.
14
14
  # @option directives [String] url This can determine the path of the object.
15
- def self.call(stream, directives)
16
- file = Hydra::Derivatives::IoDecorator.new(stream, new_mime_type(directives.fetch(:format)))
17
- o_name = determine_original_name(file)
18
- m_type = determine_mime_type(file)
19
- uri = URI(directives.fetch(:url))
20
- raise ArgumentError, "#{uri} is not an http uri" unless uri.scheme == 'http'
21
- remote_file = ActiveFedora::File.new(uri.to_s)
15
+ # @option directives [String] format The file extension (e.g. 'jpg')
16
+ def self.call(content, directives)
17
+ file = io(content, directives)
18
+ remote_file = retrieve_remote_file(directives)
22
19
  remote_file.content = file
23
- remote_file.mime_type = m_type
24
- remote_file.original_name = o_name
20
+ remote_file.mime_type = determine_mime_type(file)
21
+ remote_file.original_name = determine_original_name(file)
25
22
  remote_file.save
26
23
  end
27
24
 
28
- def self.new_mime_type(format)
29
- case format
25
+ # Override this implementation if you need a remote file from a different location
26
+ # @return [ActiveFedora::File]
27
+ def self.retrieve_remote_file(directives)
28
+ uri = URI(directives.fetch(:url))
29
+ raise ArgumentError, "#{uri} is not an http uri" unless uri.scheme == 'http'
30
+ ActiveFedora::File.new(uri.to_s)
31
+ end
32
+ private_class_method :retrieve_remote_file
33
+
34
+ def self.io(content, directives)
35
+ Hydra::Derivatives::IoDecorator.new(content, new_mime_type(directives.fetch(:format), charset(content)))
36
+ end
37
+ private_class_method :io
38
+
39
+ def self.new_mime_type(extension, charset = nil)
40
+ fmt = mime_format(extension)
41
+ fmt += "; charset=#{charset}" if charset
42
+ fmt
43
+ end
44
+
45
+ # Strings (from FullText) have encoding. Retrieve it
46
+ def self.charset(content)
47
+ content.encoding.name if content.respond_to?(:encoding)
48
+ end
49
+ private_class_method :charset
50
+
51
+ def self.mime_format(extension)
52
+ case extension
30
53
  when 'mp4'
31
54
  'video/mp4' # default is application/mp4
32
55
  when 'webm'
33
56
  'video/webm' # default is audio/webm
34
57
  else
35
- MIME::Types.type_for(format).first.to_s
58
+ MIME::Types.type_for(extension).first.to_s
36
59
  end
37
60
  end
61
+ private_class_method :mime_format
38
62
  end
39
63
  end
@@ -0,0 +1,20 @@
1
+ require 'addressable'
2
+
3
+ module Hydra::Derivatives
4
+ class PersistExternalFileOutputFileService < PersistOutputFileService
5
+ # Persists a new file at specified location that points to external content
6
+ # @param [Hash] output information about the external derivative file
7
+ # @option output [String] url the location of the external content
8
+ # @param [Hash] directives directions which can be used to determine where to persist to.
9
+ # @option directives [String] url This can determine the path of the object.
10
+ def self.call(output, directives)
11
+ external_file = ActiveFedora::File.new(directives[:url])
12
+ # TODO: Replace the following two lines with the shorter call to #external_url once active_fedora/pull/1234 is merged
13
+ external_file.content = ''
14
+ external_file.mime_type = "message/external-body; access-type=URL; URL=\"#{output[:url]}\""
15
+ # external_file.external_url = output[:url]
16
+ external_file.original_name = Addressable::URI.parse(output[:url]).path.split('/').last
17
+ external_file.save
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,18 @@
1
+ # For the case where the source file is a remote file, and we
2
+ # don't want to download the file locally, just return the
3
+ # file name or file path (or whatever we need to pass to the
4
+ # encoding service so that it can find the file).
5
+
6
+ module Hydra::Derivatives
7
+ class RemoteSourceFile
8
+ # Finds the file name of the remote source file.
9
+ # @param [String, ActiveFedora::Base] object file name, or an object that has a method that will return the file name
10
+ # @param [Hash] options
11
+ # @option options [Symbol] :source a method that can be called on the object to retrieve the source file's name
12
+ # @yield [String] the file name
13
+ def self.call(object, options, &_block)
14
+ source_name = options.fetch(:source, :to_s)
15
+ yield(object.send(source_name))
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,132 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hydra::Derivatives::Processors::ActiveEncode do
4
+ let(:file_path) { File.join(fixture_path, 'videoshort.mp4') }
5
+ let(:directives) { { url: '12345/derivative' } }
6
+ let(:output_file_service) { Hydra::Derivatives::PersistExternalFileOutputFileService }
7
+ let(:options) { { output_file_service: output_file_service } }
8
+ let(:processor) { described_class.new(file_path, directives, options) }
9
+
10
+ describe '#process' do
11
+ subject { processor.process }
12
+
13
+ # Mock out the actual encoding, just pretend that the
14
+ # encode finished and returned a certain status.
15
+ let(:failed_status) { false }
16
+ let(:cancelled_status) { false }
17
+ let(:completed_status) { false }
18
+ let(:state) { :completed }
19
+ let(:errors) { [] }
20
+ let(:external_url) { 'http://www.example.com/external/content' }
21
+ let(:output) { [{ url: external_url }] }
22
+ let(:encode_job_double) do
23
+ enc = double('encode_job',
24
+ state: state,
25
+ errors: errors,
26
+ output: output,
27
+ running?: false,
28
+ completed?: completed_status,
29
+ failed?: failed_status,
30
+ cancelled?: cancelled_status)
31
+ allow(enc).to receive(:reload).and_return(enc)
32
+ enc
33
+ end
34
+
35
+ context 'with a custom encode class' do
36
+ before do
37
+ class TestEncode < ::ActiveEncode::Base; end
38
+
39
+ # For this spec we don't care what happens with output,
40
+ # so stub it out to speed up the spec.
41
+ allow(output_file_service).to receive(:call)
42
+ end
43
+
44
+ after { Object.send(:remove_const, :TestEncode) }
45
+
46
+ let(:completed_status) { true }
47
+ let(:state) { :completed }
48
+ let(:options) do
49
+ {
50
+ encode_class: TestEncode,
51
+ output_file_service: output_file_service
52
+ }
53
+ end
54
+
55
+ it 'uses the configured encode class' do
56
+ expect(TestEncode).to receive(:create).and_return(encode_job_double)
57
+ subject
58
+ end
59
+ end
60
+
61
+ context 'when the encoding failed' do
62
+ let(:state) { :failed }
63
+ let(:failed_status) { true }
64
+ let(:errors) { ['error 1', 'error 2'] }
65
+
66
+ before do
67
+ # Don't really encode the file during specs
68
+ allow(::ActiveEncode::Base).to receive(:create).and_return(encode_job_double)
69
+ end
70
+
71
+ it 'raises an exception' do
72
+ expect { subject }.to raise_error(Hydra::Derivatives::Processors::ActiveEncodeError, "ActiveEncode status was \"failed\" for #{file_path}: error 1 ; error 2")
73
+ end
74
+ end
75
+
76
+ context 'when the encoding was cancelled' do
77
+ let(:state) { :cancelled }
78
+ let(:cancelled_status) { true }
79
+
80
+ before do
81
+ # Don't really encode the file during specs
82
+ allow(::ActiveEncode::Base).to receive(:create).and_return(encode_job_double)
83
+ end
84
+
85
+ it 'raises an exception' do
86
+ expect { subject }.to raise_error(Hydra::Derivatives::Processors::ActiveEncodeError, "ActiveEncode status was \"cancelled\" for #{file_path}")
87
+ end
88
+ end
89
+
90
+ context 'when the timeout is set' do
91
+ before do
92
+ processor.timeout = 0.01
93
+ allow(processor).to receive(:wait_for_encode_job) { sleep 0.1 }
94
+ end
95
+
96
+ it 'raises a timeout exception' do
97
+ msg = "Unable to process ActiveEncode derivative: The command took longer than 0.01 seconds to execute. Encoding will be cancelled."
98
+ expect { processor.process }.to raise_error Hydra::Derivatives::TimeoutError, msg
99
+ end
100
+ end
101
+
102
+ context 'when the timeout is not set' do
103
+ before do
104
+ processor.timeout = nil
105
+ # Don't really encode the file during specs
106
+ allow(::ActiveEncode::Base).to receive(:create).and_return(encode_job_double)
107
+ end
108
+
109
+ it 'processes the encoding without a timeout' do
110
+ expect(processor).not_to receive(:wait_for_encode_job_with_timeout)
111
+ expect(processor).to receive(:wait_for_encode_job).once
112
+ processor.process
113
+ end
114
+ end
115
+
116
+ context 'when error occurs during timeout cleanup' do
117
+ let(:error) { StandardError.new('some error message') }
118
+
119
+ before do
120
+ processor.timeout = 0.01
121
+ allow(processor).to receive(:wait_for_encode_job) { sleep 0.1 }
122
+ allow(::ActiveEncode::Base).to receive(:create).and_return(encode_job_double)
123
+ allow(encode_job_double).to receive(:cancel!).and_raise(error)
124
+ end
125
+
126
+ it 'doesnt lose the timeout error, but adds the new error message' do
127
+ msg = "Unable to process ActiveEncode derivative: The command took longer than 0.01 seconds to execute. Encoding will be cancelled. An error occurred while trying to cancel encoding: some error message"
128
+ expect { processor.process }.to raise_error Hydra::Derivatives::TimeoutError, msg
129
+ end
130
+ end
131
+ end
132
+ end
@@ -7,7 +7,10 @@ describe Hydra::Derivatives::Processors::FullText do
7
7
 
8
8
  describe "#process" do
9
9
  it 'extracts fulltext and stores the results' do
10
- expect(processor.output_file_service).to receive(:call).with(/Project Charter for E-Content Delivery Platform Review/, directives)
10
+ expect(processor.output_file_service).to receive(:call) do |first, second|
11
+ expect(first).to match(/Project Charter for E-Content Delivery Platform Review/)
12
+ expect(second).to eq directives
13
+ end
11
14
  processor.process
12
15
  end
13
16
  end
@@ -0,0 +1,38 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hydra::Derivatives::ActiveEncodeDerivatives do
4
+ context '.create' do
5
+ before do
6
+ class TestVideo < ActiveFedora::Base
7
+ attr_accessor :remote_file_name
8
+ end
9
+ end
10
+
11
+ after { Object.send(:remove_const, :TestVideo) }
12
+
13
+ let(:file_path) { 'some/path/to/my_video.mp4' }
14
+ let(:video_record) { TestVideo.new(remote_file_name: file_path) }
15
+ let(:options) { { source: :remote_file_name, outputs: [low_res_video] } }
16
+ let(:low_res_video) { { some_key: 'some options to pass to my encoder service' } }
17
+ let(:processor) { double('processor') }
18
+
19
+ it 'calls the processor with the right arguments' do
20
+ expect(Hydra::Derivatives::Processors::ActiveEncode).to receive(:new).with(file_path, low_res_video, output_file_service: Hydra::Derivatives::PersistExternalFileOutputFileService).and_return(processor)
21
+ expect(processor).to receive(:process)
22
+ described_class.create(video_record, options)
23
+ end
24
+
25
+ context 'with a custom encode class' do
26
+ before { class TestEncode < ::ActiveEncode::Base; end }
27
+ after { Object.send(:remove_const, :TestEncode) }
28
+
29
+ let(:options) { { encode_class: TestEncode, source: :remote_file_name, outputs: [low_res_video] } }
30
+
31
+ it 'calls the processor with the right arguments' do
32
+ expect(Hydra::Derivatives::Processors::ActiveEncode).to receive(:new).with(file_path, low_res_video, output_file_service: Hydra::Derivatives::PersistExternalFileOutputFileService, encode_class: TestEncode).and_return(processor)
33
+ expect(processor).to receive(:process)
34
+ described_class.create(video_record, options)
35
+ end
36
+ end
37
+ end
38
+ end
@@ -17,11 +17,24 @@ describe Hydra::Derivatives::PersistBasicContainedOutputFileService do
17
17
  # not be sufficient.
18
18
  context "when file is basic contained (default assumption)" do
19
19
  let(:object) { BasicContainerObject.create }
20
- let(:stream) { StringIO.new("fake file content") }
21
- it "persists the file to the specified destination on the given object" do
22
- described_class.call(stream, format: 'jpg', url: "#{object.uri}/the_derivative_name")
23
- expect(object.send(destination_name.to_sym).content).to eq("fake file content")
24
- expect(object.send(destination_name.to_sym).content_changed?).to eq false
20
+ let(:content) { StringIO.new("fake file content") }
21
+ let(:resource) { object.public_send(destination_name.to_sym) }
22
+ context "and the content is a stream" do
23
+ it "persists the file to the specified destination on the given object" do
24
+ described_class.call(content, format: 'jpg', url: "#{object.uri}/the_derivative_name")
25
+ expect(resource.content).to start_with("fake file content")
26
+ expect(resource.content_changed?).to eq false
27
+ expect(resource.mime_type).to eq 'image/jpeg'
28
+ end
29
+ end
30
+
31
+ context "and content is a string" do
32
+ let(:content) { "fake file content - ÅÄÖ" }
33
+ it "persists the file to the specified destination on the given object" do
34
+ described_class.call(content, format: 'txt', url: "#{object.uri}/the_derivative_name")
35
+ expect(resource.content).to eq("fake file content - ÅÄÖ")
36
+ expect(resource.mime_type).to eq 'text/plain;charset=UTF-8'
37
+ end
25
38
  end
26
39
  end
27
40
  end
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hydra::Derivatives::PersistExternalFileOutputFileService do
4
+ before do
5
+ class ExternalDerivativeContainerObject < ActiveFedora::Base
6
+ has_subresource "external_derivative"
7
+ end
8
+ end
9
+ after do
10
+ Object.send(:remove_const, :ExternalDerivativeContainerObject)
11
+ end
12
+
13
+ let(:object) { ExternalDerivativeContainerObject.create }
14
+ let(:directives) { { url: "#{object.uri}/external_derivative" } }
15
+ let(:external_url) { 'http://www.example.com/external/content' }
16
+ let(:output) { { url: external_url } }
17
+ let(:destination_name) { 'external_derivative' }
18
+
19
+ describe '.call' do
20
+ it "persists the external file to the specified destination on the given object" do
21
+ described_class.call(output, directives)
22
+ expect(object.send(destination_name.to_sym).mime_type).to eq "message/external-body;access-type=URL;url=\"http://www.example.com/external/content\""
23
+ expect(object.send(destination_name.to_sym).content).to eq ''
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+
3
+ class TestObject < ActiveFedora::Base
4
+ attr_accessor :source_file_name
5
+ end
6
+
7
+ describe Hydra::Derivatives::RemoteSourceFile do
8
+ describe '.call' do
9
+ let(:file_name) { 'my_source_file.mp4' }
10
+
11
+ context 'when you pass in a String file name' do
12
+ let(:input_obj) { file_name }
13
+ let(:options) { Hash.new }
14
+
15
+ it 'it yields the file name' do
16
+ expect do |blk|
17
+ described_class.call(input_obj, options, &blk)
18
+ end.to yield_with_args(file_name)
19
+ end
20
+ end
21
+
22
+ context 'when you pass in an ActiveFedora::Base object ' do
23
+ let(:input_obj) { TestObject.new(source_file_name: file_name) }
24
+ let(:options) { { source: :source_file_name } }
25
+
26
+ it 'it yields the file name' do
27
+ expect do |blk|
28
+ described_class.call(input_obj, options, &blk)
29
+ end.to yield_with_args(file_name)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -41,4 +41,10 @@ describe "the configuration" do
41
41
  subject.source_file_service = source_file_service
42
42
  expect(subject.source_file_service).to eq(source_file_service)
43
43
  end
44
+
45
+ it "lets you set the poll time for ActiveEncode jobs" do
46
+ expect(subject.active_encode_poll_time).to eq 10
47
+ subject.active_encode_poll_time = 15
48
+ expect(subject.active_encode_poll_time).to eq 15
49
+ end
44
50
  end
@@ -53,6 +53,10 @@ describe Hydra::Derivatives do
53
53
  subject.kdu_compress_path = '/usr/local/bin/kdu_compress'
54
54
  subject.reset_config!
55
55
  expect(subject.kdu_compress_path).to eq('kdu_compress')
56
+
57
+ subject.active_encode_poll_time = 2
58
+ subject.reset_config!
59
+ expect(subject.active_encode_poll_time).to eq 10
56
60
  end
57
61
  end
58
62
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hydra-derivatives
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.2.2
4
+ version: 3.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Coyne
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-04-07 00:00:00.000000000 Z
11
+ date: 2017-06-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -86,7 +86,7 @@ dependencies:
86
86
  requirements:
87
87
  - - ">="
88
88
  - !ruby/object:Gem::Version
89
- version: '9.0'
89
+ version: 11.3.1
90
90
  - - "<"
91
91
  - !ruby/object:Gem::Version
92
92
  version: '12'
@@ -96,7 +96,7 @@ dependencies:
96
96
  requirements:
97
97
  - - ">="
98
98
  - !ruby/object:Gem::Version
99
- version: '9.0'
99
+ version: 11.3.1
100
100
  - - "<"
101
101
  - !ruby/object:Gem::Version
102
102
  version: '12'
@@ -160,6 +160,34 @@ dependencies:
160
160
  - - "<"
161
161
  - !ruby/object:Gem::Version
162
162
  version: '4.0'
163
+ - !ruby/object:Gem::Dependency
164
+ name: active_encode
165
+ requirement: !ruby/object:Gem::Requirement
166
+ requirements:
167
+ - - "~>"
168
+ - !ruby/object:Gem::Version
169
+ version: '0.1'
170
+ type: :runtime
171
+ prerelease: false
172
+ version_requirements: !ruby/object:Gem::Requirement
173
+ requirements:
174
+ - - "~>"
175
+ - !ruby/object:Gem::Version
176
+ version: '0.1'
177
+ - !ruby/object:Gem::Dependency
178
+ name: addressable
179
+ requirement: !ruby/object:Gem::Requirement
180
+ requirements:
181
+ - - "~>"
182
+ - !ruby/object:Gem::Version
183
+ version: '2.5'
184
+ type: :runtime
185
+ prerelease: false
186
+ version_requirements: !ruby/object:Gem::Requirement
187
+ requirements:
188
+ - - "~>"
189
+ - !ruby/object:Gem::Version
190
+ version: '2.5'
163
191
  - !ruby/object:Gem::Dependency
164
192
  name: deprecation
165
193
  requirement: !ruby/object:Gem::Requirement
@@ -195,6 +223,7 @@ files:
195
223
  - TODO
196
224
  - VERSION
197
225
  - config/jetty.yml
226
+ - doc/amazon_elastic_transcoder.md
198
227
  - hydra-derivatives.gemspec
199
228
  - lib/color_profiles/license.txt
200
229
  - lib/color_profiles/sRGB_IEC61966-2-1_no_black_scaling.icc
@@ -204,6 +233,7 @@ files:
204
233
  - lib/hydra/derivatives/io_decorator.rb
205
234
  - lib/hydra/derivatives/logger.rb
206
235
  - lib/hydra/derivatives/processors.rb
236
+ - lib/hydra/derivatives/processors/active_encode.rb
207
237
  - lib/hydra/derivatives/processors/audio.rb
208
238
  - lib/hydra/derivatives/processors/document.rb
209
239
  - lib/hydra/derivatives/processors/ffmpeg.rb
@@ -216,6 +246,7 @@ files:
216
246
  - lib/hydra/derivatives/processors/video.rb
217
247
  - lib/hydra/derivatives/processors/video/config.rb
218
248
  - lib/hydra/derivatives/processors/video/processor.rb
249
+ - lib/hydra/derivatives/runners/active_encode_derivatives.rb
219
250
  - lib/hydra/derivatives/runners/audio_derivatives.rb
220
251
  - lib/hydra/derivatives/runners/document_derivatives.rb
221
252
  - lib/hydra/derivatives/runners/full_text_extract.rb
@@ -227,7 +258,9 @@ files:
227
258
  - lib/hydra/derivatives/services/capability_service.rb
228
259
  - lib/hydra/derivatives/services/mime_type_service.rb
229
260
  - lib/hydra/derivatives/services/persist_basic_contained_output_file_service.rb
261
+ - lib/hydra/derivatives/services/persist_external_file_output_file_service.rb
230
262
  - lib/hydra/derivatives/services/persist_output_file_service.rb
263
+ - lib/hydra/derivatives/services/remote_source_file.rb
231
264
  - lib/hydra/derivatives/services/retrieve_source_file_service.rb
232
265
  - lib/hydra/derivatives/services/tempfile_service.rb
233
266
  - solr/config/_rest_managed.json
@@ -261,6 +294,7 @@ files:
261
294
  - spec/fixtures/test.xls
262
295
  - spec/fixtures/test.xlsx
263
296
  - spec/fixtures/world.png
297
+ - spec/processors/active_encode_spec.rb
264
298
  - spec/processors/document_spec.rb
265
299
  - spec/processors/full_text_spec.rb
266
300
  - spec/processors/image_spec.rb
@@ -268,8 +302,11 @@ files:
268
302
  - spec/processors/processor_spec.rb
269
303
  - spec/processors/shell_based_processor_spec.rb
270
304
  - spec/processors/video_spec.rb
305
+ - spec/runners/active_encode_derivatives_spec.rb
271
306
  - spec/services/audio_derivatives_spec.rb
272
307
  - spec/services/persist_basic_contained_output_file_service_spec.rb
308
+ - spec/services/persist_external_file_output_file_service_spec.rb
309
+ - spec/services/remote_source_file_spec.rb
273
310
  - spec/services/retrieve_source_file_service_spec.rb
274
311
  - spec/services/tempfile_service_spec.rb
275
312
  - spec/spec_helper.rb
@@ -299,7 +336,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
299
336
  version: '0'
300
337
  requirements: []
301
338
  rubyforge_project:
302
- rubygems_version: 2.6.10
339
+ rubygems_version: 2.6.12
303
340
  signing_key:
304
341
  specification_version: 4
305
342
  summary: Derivative generation plugin for hydra
@@ -319,6 +356,7 @@ test_files:
319
356
  - spec/fixtures/test.xls
320
357
  - spec/fixtures/test.xlsx
321
358
  - spec/fixtures/world.png
359
+ - spec/processors/active_encode_spec.rb
322
360
  - spec/processors/document_spec.rb
323
361
  - spec/processors/full_text_spec.rb
324
362
  - spec/processors/image_spec.rb
@@ -326,8 +364,11 @@ test_files:
326
364
  - spec/processors/processor_spec.rb
327
365
  - spec/processors/shell_based_processor_spec.rb
328
366
  - spec/processors/video_spec.rb
367
+ - spec/runners/active_encode_derivatives_spec.rb
329
368
  - spec/services/audio_derivatives_spec.rb
330
369
  - spec/services/persist_basic_contained_output_file_service_spec.rb
370
+ - spec/services/persist_external_file_output_file_service_spec.rb
371
+ - spec/services/remote_source_file_spec.rb
331
372
  - spec/services/retrieve_source_file_service_spec.rb
332
373
  - spec/services/tempfile_service_spec.rb
333
374
  - spec/spec_helper.rb