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.
- 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
|