active_encode 0.4.1 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CODE_OF_CONDUCT.md +36 -0
- data/CONTRIBUTING.md +23 -21
- data/LICENSE +11 -199
- data/README.md +66 -24
- data/SUPPORT.md +5 -0
- data/active_encode.gemspec +2 -1
- data/app/controllers/active_encode/encode_record_controller.rb +12 -0
- data/config/routes.rb +4 -0
- data/lib/active_encode/engine_adapters/elastic_transcoder_adapter.rb +115 -39
- data/lib/active_encode/engine_adapters/ffmpeg_adapter.rb +5 -2
- data/lib/active_encode/version.rb +1 -1
- data/lib/file_locator.rb +94 -0
- data/spec/controllers/encode_record_controller_spec.rb +52 -0
- data/spec/integration/elastic_transcoder_adapter_spec.rb +44 -10
- data/spec/integration/ffmpeg_adapter_spec.rb +5 -1
- data/spec/routing/encode_record_controller_routing_spec.rb +9 -0
- data/spec/units/file_locator_spec.rb +128 -0
- metadata +32 -8
data/SUPPORT.md
ADDED
@@ -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.
|
data/active_encode.gemspec
CHANGED
@@ -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
|
data/config/routes.rb
ADDED
@@ -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
|
-
|
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:
|
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)
|
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),
|
59
|
+
encode = ActiveEncode::Base.new(convert_input(job), {})
|
41
60
|
encode.id = job.id
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
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, :
|
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
|
82
|
-
|
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
|
87
|
-
case
|
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
|
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.
|
109
|
-
|
110
|
-
|
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.
|
113
|
-
output.
|
114
|
-
output.
|
115
|
-
|
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.
|
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
|
-
|
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:
|
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),
|
data/lib/file_locator.rb
ADDED
@@ -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
|
-
"
|
30
|
+
"spec/fixtures/fireworks.mp4",
|
29
31
|
pipeline_id: "1471963629141-kmcocm",
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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
|