active_encode 0.4.1 → 0.5.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.
@@ -0,0 +1,5 @@
1
+ If you would like to report an issue, first search [the list of issues](https://github.com/samvera-labs/active_encode/issues/) to see if someone else has already reported it, and then feel free to [create a new issue](https://github.com/samvera-labs/active_encode/issues/new).
2
+
3
+ If you have questions or need help, please email [the Samvera community tech list](https://groups.google.com/forum/#!forum/samvera-tech) or stop by the #dev channel in [the Samvera community Slack team](https://wiki.duraspace.org/pages/viewpage.action?pageId=87460391#Getintouch!-Slack).
4
+
5
+ You can learn more about the various Samvera communication channels on the [Get in touch!](https://wiki.duraspace.org/pages/viewpage.action?pageId=87460391) wiki page.
@@ -21,10 +21,11 @@ Gem::Specification.new do |spec|
21
21
 
22
22
  spec.add_dependency "rails"
23
23
 
24
+ spec.add_development_dependency "aws-sdk"
24
25
  spec.add_development_dependency "bundler"
25
26
  spec.add_development_dependency "coveralls"
26
27
  spec.add_development_dependency "database_cleaner"
27
- spec.add_development_dependency "engine_cart"
28
+ spec.add_development_dependency "engine_cart", "~> 2.2"
28
29
  spec.add_development_dependency "rake"
29
30
  spec.add_development_dependency "rspec"
30
31
  spec.add_development_dependency "rspec-its"
@@ -0,0 +1,12 @@
1
+ module ActiveEncode
2
+ class EncodeRecordController < ActionController::Base
3
+ rescue_from ActiveRecord::RecordNotFound do |e|
4
+ render json: { message: e.message }, status: :not_found
5
+ end
6
+
7
+ def show
8
+ @encode_record = ActiveEncode::EncodeRecord.find(params[:id])
9
+ render json: @encode_record.raw_object, status: :ok
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+ ActiveEncode::Engine.routes.draw do
3
+ resources :encode_record, only: [:show]
4
+ end
@@ -1,12 +1,27 @@
1
+ require 'addressable/uri'
2
+ require 'aws-sdk'
3
+ require 'file_locator'
4
+
1
5
  module ActiveEncode
2
6
  module EngineAdapters
3
7
  class ElasticTranscoderAdapter
4
- # TODO: add a stub for an input helper (supplied by an initializer) that transforms encode.input into a zencoder accepted url
8
+
9
+ JOB_STATES = {
10
+ "Submitted" => :running, "Progressing" => :running, "Canceled" => :cancelled,
11
+ "Error" => :failed, "Complete" => :completed
12
+ }
13
+
14
+ # Require options to include :pipeline_id, :masterfile_bucket and :outputs
15
+ # Example :outputs value:
16
+ # [{ key: "quality-low/hls/fireworks", preset_id: '1494429796844-aza6zh', segment_duration: '2' },
17
+ # { key: "quality-medium/hls/fireworks", preset_id: '1494429797061-kvg9ki', segment_duration: '2' },
18
+ # { key: "quality-high/hls/fireworks", preset_id: '1494429797265-9xi831', segment_duration: '2' }]
5
19
  def create(input_url, options = {})
20
+ s3_key = copy_to_input_bucket input_url, options[:masterfile_bucket]
6
21
  job = client.create_job(
7
- input: { key: input_url },
22
+ input: { key: s3_key },
8
23
  pipeline_id: options[:pipeline_id],
9
- output_key_prefix: options[:output_key_prefix],
24
+ output_key_prefix: options[:output_key_prefix] || "#{SecureRandom.uuid}/",
10
25
  outputs: options[:outputs],
11
26
  user_metadata: options[:user_metadata]
12
27
  ).job
@@ -31,28 +46,33 @@ module ActiveEncode
31
46
  @client ||= Aws::ElasticTranscoder::Client.new
32
47
  end
33
48
 
49
+ def s3client
50
+ Aws::S3::Client.new
51
+ end
52
+
34
53
  def get_job_details(job_id)
35
- client.read_job(id: job_id).job
54
+ client.read_job(id: job_id)&.job
36
55
  end
37
56
 
38
57
  def build_encode(job)
39
58
  return nil if job.nil?
40
- encode = ActiveEncode::Base.new(convert_input(job), convert_options(job))
59
+ encode = ActiveEncode::Base.new(convert_input(job), {})
41
60
  encode.id = job.id
42
- encode.state = convert_state(job)
43
- encode.current_operations = convert_current_operations(job)
44
- encode.percent_complete = convert_percent_complete(job)
45
- encode.created_at = convert_time(job.timing["submit_time_millis"])
46
- encode.updated_at = convert_time(job.timing["finish_time_millis"] || job.timing["start_time_millis"]) || encode.created_at
47
- encode.output = convert_output(job)
48
- encode.errors = convert_errors(job)
49
-
50
- encode.input.id = job.input.key
61
+ encode.state = JOB_STATES[job.status]
62
+ encode.current_operations = []
63
+ encode.percent_complete = convert_percent_complete(job)
64
+ encode.created_at = convert_time(job.timing["submit_time_millis"])
65
+ encode.updated_at = convert_time(job.timing["finish_time_millis"]) || convert_time(job.timing["start_time_millis"]) || encode.created_at
66
+
67
+ encode.output = convert_output(job)
68
+ encode.errors = job.outputs.select { |o| o.status == "Error" }.collect(&:status_detail).compact
69
+
51
70
  tech_md = convert_tech_metadata(job.input.detected_properties)
52
- [:width, :height, :frame_rate, :duration, :checksum, :audio_codec, :video_codec,
53
- :audio_bitrate, :video_bitrate, :file_size].each do |field|
71
+ [:width, :height, :frame_rate, :duration, :file_size].each do |field|
54
72
  encode.input.send("#{field}=", tech_md[field])
55
73
  end
74
+
75
+ encode.input.id = job.id
56
76
  encode.input.state = encode.state
57
77
  encode.input.created_at = encode.created_at
58
78
  encode.input.updated_at = encode.updated_at
@@ -65,6 +85,11 @@ module ActiveEncode
65
85
  Time.at(time_millis / 1000)
66
86
  end
67
87
 
88
+ def convert_bitrate(rate)
89
+ return nil if rate.nil?
90
+ (rate.to_f * 1024).to_s
91
+ end
92
+
68
93
  def convert_state(job)
69
94
  case job.status
70
95
  when "Submitted", "Progressing" # Should there be a queued state?
@@ -78,13 +103,12 @@ module ActiveEncode
78
103
  end
79
104
  end
80
105
 
81
- def convert_current_operations(_job)
82
- current_ops = []
83
- current_ops
106
+ def convert_percent_complete(job)
107
+ job.outputs.inject(0) { |sum, output| sum + output_percentage(output) } / job.outputs.length
84
108
  end
85
109
 
86
- def convert_percent_complete(job)
87
- case job.status
110
+ def output_percentage(output)
111
+ case output.status
88
112
  when "Submitted"
89
113
  10
90
114
  when "Progressing", "Canceled", "Error"
@@ -100,29 +124,67 @@ module ActiveEncode
100
124
  job.input
101
125
  end
102
126
 
103
- def convert_options(_job_details)
104
- {}
127
+ def copy_to_input_bucket input_url, bucket
128
+ case Addressable::URI.parse(input_url).scheme
129
+ when nil,'file'
130
+ upload_to_s3 input_url, bucket
131
+ when 's3'
132
+ check_s3_bucket input_url, bucket
133
+ end
134
+ end
135
+
136
+ def check_s3_bucket input_url, source_bucket
137
+ # logger.info("Checking `#{input_url}'")
138
+ s3_object = FileLocator::S3File.new(input_url).object
139
+ if s3_object.bucket_name == source_bucket
140
+ # logger.info("Already in bucket `#{source_bucket}'")
141
+ s3_object.key
142
+ else
143
+ s3_key = File.join(SecureRandom.uuid,s3_object.key)
144
+ # logger.info("Copying to `#{source_bucket}/#{input_url}'")
145
+ target = Aws::S3::Object.new(bucket_name: source_bucket, key: input_url)
146
+ target.copy_from(s3_object, multipart_copy: s3_object.size > 15728640) # 15.megabytes
147
+ s3_key
148
+ end
149
+ end
150
+
151
+ def upload_to_s3 input_url, source_bucket
152
+ original_input = input_url
153
+ bucket = Aws::S3::Resource.new(client: s3client).bucket(source_bucket)
154
+ filename = FileLocator.new(input_url).location
155
+ s3_key = File.join(SecureRandom.uuid,File.basename(filename))
156
+ # logger.info("Copying `#{original_input}' to `#{source_bucket}/#{input_url}'")
157
+ obj = bucket.object(s3_key)
158
+ obj.upload_file filename
159
+
160
+ s3_key
161
+ end
162
+
163
+ def read_preset(id)
164
+ client.read_preset(id: id).preset
105
165
  end
106
166
 
107
167
  def convert_output(job)
108
- job.outputs.collect do |o|
109
- # It is assumed that the first part of the output key can be used to label the output
110
- # e.g. "quality-medium/somepath/filename.flv"
168
+ pipeline = client.read_pipeline(id: job.pipeline_id).pipeline
169
+ job.outputs.collect do |joutput|
170
+ preset = read_preset(joutput.preset_id)
171
+ extension = preset.container == 'ts' ? '.m3u8' : ''
172
+ tech_md = convert_tech_metadata(joutput, preset).merge({
173
+ managed: false,
174
+ id: joutput.id,
175
+ label: joutput.key.split("/", 2).first,
176
+ url: "s3://#{pipeline.output_bucket}/#{job.output_key_prefix}#{joutput.key}#{extension}"
177
+ })
178
+
111
179
  output = ActiveEncode::Output.new
112
- output.id = o.id
113
- output.label = o.key.split("/", 2).first
114
- output.url = job.output_key_prefix + o.key
115
- # TODO: If HLS is considered distinct from this output then it should be a different output
116
- # TODO: If HLS is not considered distinct from this output then this should be handled by a method on a ActiveEncode::Base subclass or consuming client
117
- # extras[:hls_url] = url + ".m3u8" if url.include?("/hls/") # TODO: find a better way to signal hls
118
- tech_md = convert_tech_metadata(o)
180
+ output.state = convert_state(joutput)
181
+ output.created_at = convert_time(job.timing["submit_time_millis"])
182
+ output.updated_at = convert_time(job.timing["finish_time_millis"] || job.timing["start_time_millis"]) || output.created_at
183
+
119
184
  [:width, :height, :frame_rate, :duration, :checksum, :audio_codec, :video_codec,
120
- :audio_bitrate, :video_bitrate, :file_size].each do |field|
185
+ :audio_bitrate, :video_bitrate, :file_size, :label, :url, :id].each do |field|
121
186
  output.send("#{field}=", tech_md[field])
122
187
  end
123
- output.state = convert_state(o)
124
- output.created_at = convert_time(job.timing["submit_time_millis"])
125
- output.updated_at = convert_time(job.timing["finish_time_millis"] || job.timing["start_time_millis"]) || output.created_at
126
188
 
127
189
  output
128
190
  end
@@ -132,12 +194,13 @@ module ActiveEncode
132
194
  job.outputs.select { |o| o.status == "Error" }.collect(&:status_detail).compact
133
195
  end
134
196
 
135
- def convert_tech_metadata(props)
136
- return {} if props.blank?
197
+ def convert_tech_metadata(props, preset=nil)
198
+ return {} if props.nil? || props.empty?
137
199
  metadata_fields = {
138
200
  file_size: { key: :file_size, method: :itself },
139
201
  duration_millis: { key: :duration, method: :to_i },
140
202
  frame_rate: { key: :frame_rate, method: :to_i },
203
+ segment_duration: { key: :segment_duration, method: :itself },
141
204
  width: { key: :width, method: :itself },
142
205
  height: { key: :height, method: :itself }
143
206
  }
@@ -149,6 +212,19 @@ module ActiveEncode
149
212
  next if conversion.nil?
150
213
  metadata[conversion[:key]] = value.send(conversion[:method])
151
214
  end
215
+
216
+ unless preset.nil?
217
+ audio = preset.audio
218
+ video = preset.video
219
+ metadata.merge!({
220
+ audio_codec: audio&.codec,
221
+ audio_channels: audio&.channels,
222
+ audio_bitrate: convert_bitrate(audio&.bit_rate),
223
+ video_codec: video&.codec,
224
+ video_bitrate: convert_bitrate(video&.bit_rate)
225
+ })
226
+ end
227
+
152
228
  metadata
153
229
  end
154
230
  end
@@ -191,7 +191,8 @@ private
191
191
  if data.blank?
192
192
  1
193
193
  else
194
- (progress_value("out_time_ms=", data).to_i * 0.0001 / encode.input.duration).round
194
+ progress_in_milliseconds = progress_value("out_time_ms=", data).to_i / 1000.0
195
+ (progress_in_milliseconds / encode.input.duration * 100).round
195
196
  end
196
197
  end
197
198
 
@@ -219,11 +220,13 @@ private
219
220
  def get_tech_metadata file_path
220
221
  doc = Nokogiri::XML File.read(file_path)
221
222
  doc.remove_namespaces!
223
+ duration = get_xpath_text(doc, '//Duration/text()', :to_f)
224
+ duration = duration * 1000 unless duration.nil? # Convert to milliseconds
222
225
  { url: get_xpath_text(doc, '//media/@ref', :to_s),
223
226
  width: get_xpath_text(doc, '//Width/text()', :to_f),
224
227
  height: get_xpath_text(doc, '//Height/text()', :to_f),
225
228
  frame_rate: get_xpath_text(doc, '//FrameRate/text()', :to_f),
226
- duration: get_xpath_text(doc, '//Duration/text()', :to_f),
229
+ duration: duration,
227
230
  file_size: get_xpath_text(doc, '//FileSize/text()', :to_i),
228
231
  audio_codec: get_xpath_text(doc, '//track[@type="Audio"]/CodecID/text()', :to_s),
229
232
  audio_bitrate: get_xpath_text(doc, '//track[@type="Audio"]/BitRate/text()', :to_i),
@@ -1,3 +1,3 @@
1
1
  module ActiveEncode
2
- VERSION = '0.4.1'.freeze
2
+ VERSION = '0.5.0'.freeze
3
3
  end
@@ -0,0 +1,94 @@
1
+ require 'addressable/uri'
2
+ require 'aws-sdk'
3
+
4
+ class FileLocator
5
+ attr_reader :source
6
+
7
+ class S3File
8
+ attr_reader :bucket, :key
9
+
10
+ def initialize(uri)
11
+ uri = Addressable::URI.parse(uri)
12
+ @bucket = URI.decode(uri.host)
13
+ @key = URI.decode(uri.path).sub(%r(^/*(.+)/*$),'\1')
14
+ end
15
+
16
+ def object
17
+ @object ||= Aws::S3::Object.new(bucket_name: bucket, key: key)
18
+ end
19
+ end
20
+
21
+ def initialize(source)
22
+ @source = source
23
+ end
24
+
25
+ def uri
26
+ if @uri.nil?
27
+ if source.is_a? File
28
+ @uri = Addressable::URI.parse("file://#{URI.encode(File.expand_path(source))}")
29
+ else
30
+ encoded_source = source
31
+ begin
32
+ @uri = Addressable::URI.parse(encoded_source)
33
+ rescue URI::InvalidURIError
34
+ if encoded_source == source
35
+ encoded_source = URI.encode(encoded_source)
36
+ retry
37
+ else
38
+ raise
39
+ end
40
+ end
41
+
42
+ if @uri.scheme.nil?
43
+ @uri = Addressable::URI.parse("file://#{URI.encode(File.expand_path(source))}")
44
+ end
45
+ end
46
+ end
47
+ @uri
48
+ end
49
+
50
+ def location
51
+ case uri.scheme
52
+ when 's3'
53
+ S3File.new(uri).object.presigned_url(:get)
54
+ when 'file'
55
+ URI.decode(uri.path)
56
+ else
57
+ @uri.to_s
58
+ end
59
+ end
60
+
61
+ def exist?
62
+ case uri.scheme
63
+ when 's3'
64
+ S3File.new(uri).object.exists?
65
+ when 'file'
66
+ File.exist?(location)
67
+ else
68
+ false
69
+ end
70
+ end
71
+ alias_method :exists?, :exist?
72
+
73
+ def reader
74
+ case uri.scheme
75
+ when 's3'
76
+ S3File.new(uri).object.get.body
77
+ when 'file'
78
+ File.open(location,'r')
79
+ else
80
+ Kernel::open(uri.to_s, 'r')
81
+ end
82
+ end
83
+
84
+ def attachment
85
+ case uri.scheme
86
+ when 's3'
87
+ uri
88
+ when 'file'
89
+ File.open(location,'r')
90
+ else
91
+ location
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,52 @@
1
+ require 'rails_helper'
2
+
3
+ describe ActiveEncode::EncodeRecordController, type: :controller, db_clean: true do
4
+ routes { ActiveEncode::Engine.routes }
5
+
6
+ let(:encode_record) { ActiveEncode::EncodeRecord.create(id: 1, global_id: "app://ActiveEncode/Encode/1", state: "running", adapter: "ffmpeg", title: "Test", raw_object: raw_object)}
7
+ let(:raw_object) do
8
+ "{\"input\":{\"url\":\"file:///Users/cjcolvar/Documents/Code/samvera/active_encode/spec/fixtures/fireworks.mp4\",\"width\":960.0,\"height\":540.0,\"frame_rate\":29.671,\"duration\":6024,\"file_size\":1629578,\"audio_codec\":\"mp4a-40-2\",\"video_codec\":\"avc1\",\"audio_bitrate\":69737,\"video_bitrate\":2092780,\"created_at\":\"2018-12-17T16:54:50.401-05:00\",\"updated_at\":\"2018-12-17T16:54:50.401-05:00\",\"id\":\"8156\"},\"options\":{},\"id\":\"35efa965-ec51-409d-9495-2ae9669adbcc\",\"output\":[{\"url\":\"file:///Users/cjcolvar/Documents/Code/samvera/active_encode/.internal_test_app/encodes/35efa965-ec51-409d-9495-2ae9669adbcc/outputs/fireworks-low.mp4\",\"label\":\"low\",\"id\":\"8156-low\",\"created_at\":\"2018-12-17T16:54:50.401-05:00\",\"updated_at\":\"2018-12-17T16:54:59.169-05:00\",\"width\":640.0,\"height\":480.0,\"frame_rate\":29.671,\"duration\":6038,\"file_size\":905987,\"audio_codec\":\"mp4a-40-2\",\"video_codec\":\"avc1\",\"audio_bitrate\":72000,\"video_bitrate\":1126859},{\"url\":\"file:///Users/cjcolvar/Documents/Code/samvera/active_encode/.internal_test_app/encodes/35efa965-ec51-409d-9495-2ae9669adbcc/outputs/fireworks-high.mp4\",\"label\":\"high\",\"id\":\"8156-high\",\"created_at\":\"2018-12-17T16:54:50.401-05:00\",\"updated_at\":\"2018-12-17T16:54:59.169-05:00\",\"width\":1280.0,\"height\":720.0,\"frame_rate\":29.671,\"duration\":6038,\"file_size\":2102027,\"audio_codec\":\"mp4a-40-2\",\"video_codec\":\"avc1\",\"audio_bitrate\":72000,\"video_bitrate\":2721866}],\"state\":\"completed\",\"errors\":[],\"created_at\":\"2018-12-17T16:54:50.401-05:00\",\"updated_at\":\"2018-12-17T16:54:59.169-05:00\",\"current_operations\":[],\"percent_complete\":100,\"global_id\":{\"uri\":\"gid://ActiveEncode/Encode/35efa965-ec51-409d-9495-2ae9669adbcc\"}}"
9
+ end
10
+
11
+ before do
12
+ encode_record
13
+ end
14
+
15
+ describe 'GET show' do
16
+ before do
17
+ get :show, params: { id: record_id }
18
+ end
19
+
20
+ context 'when record exists' do
21
+ let(:record_id) { 1 }
22
+
23
+ it "responds with a 200 status code" do
24
+ expect(response.status).to eq 200
25
+ end
26
+
27
+ it "responds with JSON" do
28
+ expect(response.content_type).to eq "application/json"
29
+ end
30
+
31
+ it "returns the encode record's raw json object" do
32
+ expect(response.body).to eq raw_object
33
+ end
34
+ end
35
+
36
+ context 'when record does not exist' do
37
+ let(:record_id) { "non-existant" }
38
+
39
+ it "responds with a 404 status code" do
40
+ expect(response.status).to eq 404
41
+ end
42
+
43
+ it "responds with JSON" do
44
+ expect(response.content_type).to eq "application/json"
45
+ end
46
+
47
+ it "returns the encode record's raw json object" do
48
+ expect(response.body).to eq "{\"message\":\"Couldn't find ActiveEncode::EncodeRecord with 'id'=#{record_id}\"}"
49
+ end
50
+ end
51
+ end
52
+ end
@@ -12,9 +12,11 @@ describe ActiveEncode::EngineAdapters::ElasticTranscoderAdapter do
12
12
  end
13
13
 
14
14
  let(:client) { Aws::ElasticTranscoder::Client.new(stub_responses: true) }
15
+ let(:s3client) { Aws::S3::Client.new(stub_responses: true) }
15
16
 
16
17
  before do
17
18
  allow(Aws::ElasticTranscoder::Client).to receive(:new).and_return(client)
19
+ allow(Aws::S3::Client).to receive(:new).and_return(s3client)
18
20
  end
19
21
 
20
22
  let(:created_job) do
@@ -25,14 +27,15 @@ describe ActiveEncode::EngineAdapters::ElasticTranscoderAdapter do
25
27
  client.stub_responses(:create_job, Aws::ElasticTranscoder::Types::ReadJobResponse.new(job: j))
26
28
 
27
29
  ActiveEncode::Base.create(
28
- "somefile.mp4",
30
+ "spec/fixtures/fireworks.mp4",
29
31
  pipeline_id: "1471963629141-kmcocm",
30
- output_key_prefix: "elastic-transcoder-samples/output/hls/",
31
- outputs: [{
32
- key: 'hls0400k/' + "e8fe80f5b7063b12d567b90c0bdf6322116bba11ac458fe9d62921644159fe4a",
33
- preset_id: "1351620000001-200050",
34
- segment_duration: "2"
35
- }])
32
+ masterfile_bucket: "BucketName",
33
+ output_key_prefix: "elastic-transcoder-samples/output/hls/",
34
+ outputs: [{
35
+ key: 'hls0400k/' + "e8fe80f5bsomefilesource_bucket7063b12d567b90c0bdf6322116bba11ac458fe9d62921644159fe4a",
36
+ preset_id: "1351620000001-200050",
37
+ segment_duration: "2",
38
+ }])
36
39
  end
37
40
 
38
41
  let(:running_job) do
@@ -90,14 +93,14 @@ describe ActiveEncode::EngineAdapters::ElasticTranscoderAdapter do
90
93
  ActiveEncode::Base.find('failed-id')
91
94
  end
92
95
 
93
- let(:completed_output) { [{ id: "2", url: "elastic-transcoder-samples/output/hls/hls0400k/e8fe80f5b7063b12d567b90c0bdf6322116bba11ac458fe9d62921644159fe4a", label: "hls0400k", :width=>400, :height=>224, :frame_rate=>25, :file_size=>6901104, :duration=>117353 }] }
96
+ let(:completed_output) { [{ id: "2", url: "s3://BucketName/elastic-transcoder-samples/output/hls/hls0400k/e8fe80f5b7063b12d567b90c0bdf6322116bba11ac458fe9d62921644159fe4a", label: "hls0400k", :width=>400, :height=>224, :frame_rate=>25, :file_size=>6901104, :duration=>117353 }] }
94
97
  let(:completed_tech_metadata) { { :width=>1280, :height=>720, :frame_rate=>25, :file_size=>21069678, :duration=>117312 } }
95
98
  let(:failed_tech_metadata) { {} }
96
99
 
97
100
  it_behaves_like "an ActiveEncode::EngineAdapter"
98
101
 
99
102
  describe "#create" do
100
- let(:create_output) { [{ id: "2", url: "elastic-transcoder-samples/output/hls/hls0400k/e8fe80f5b7063b12d567b90c0bdf6322116bba11ac458fe9d62921644159fe4a", label: "hls0400k" }] }
103
+ let(:create_output) { [{ id: "2", url: "s3://BucketName/elastic-transcoder-samples/output/hls/hls0400k/e8fe80f5b7063b12d567b90c0bdf6322116bba11ac458fe9d62921644159fe4a", label: "hls0400k" }] }
101
104
 
102
105
  subject { created_job }
103
106
 
@@ -114,7 +117,7 @@ describe ActiveEncode::EngineAdapters::ElasticTranscoderAdapter do
114
117
 
115
118
  describe "#find" do
116
119
  context "a running encode" do
117
- let(:running_output) { [{ id: "2", url: "elastic-transcoder-samples/output/hls/hls0400k/e8fe80f5b7063b12d567b90c0bdf6322116bba11ac458fe9d62921644159fe4a", label: "hls0400k" }] }
120
+ let(:running_output) { [{ id: "2", url: "s3://BucketName/elastic-transcoder-samples/output/hls/hls0400k/e8fe80f5b7063b12d567b90c0bdf6322116bba11ac458fe9d62921644159fe4a", label: "hls0400k" }] }
118
121
  let(:running_tech_metadata) { {:width=>1280, :height=>720, :frame_rate=>25, :file_size=>21069678, :duration=>117312} }
119
122
 
120
123
  subject { running_job }
@@ -133,4 +136,35 @@ describe ActiveEncode::EngineAdapters::ElasticTranscoderAdapter do
133
136
  end
134
137
  end
135
138
  end
139
+
140
+ describe "#check_s3_bucket" do
141
+ context "when file exists in masterfile_bucket" do
142
+ let(:input_url) { "s3://bucket1/file.mp4" }
143
+ let(:source_bucket) { "bucket1" }
144
+
145
+ it "just returns the key" do
146
+ # TODO: move these bucket helpers out to a service class so we don't have to test private methods
147
+ expect(described_class.new.send(:check_s3_bucket, input_url, source_bucket)).to eq "file.mp4"
148
+ end
149
+ end
150
+
151
+ context "when file is in another bucket" do
152
+ let(:input_url) { "s3://bucket1/file.mp4" }
153
+ let(:source_bucket) { "bucket2" }
154
+
155
+ it "copies to masterfile_bucket" do
156
+ # TODO: move these bucket helpers out to a service class so we don't have to test private methods
157
+ allow(SecureRandom).to receive(:uuid).and_return("randomstring")
158
+ expect(described_class.new.send(:check_s3_bucket, input_url, source_bucket)).to eq "randomstring/file.mp4"
159
+ end
160
+ end
161
+ end
162
+
163
+ describe "#output_percentage" do
164
+ let(:output) { double(ActiveEncode::Output, status: "Random status") }
165
+
166
+ it "returns 0 for any other status" do
167
+ expect(described_class.new.send(:output_percentage, output)).to eq 0
168
+ end
169
+ end
136
170
  end