active_encode 0.8.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.circleci/config.yml +26 -17
- data/.rubocop.yml +7 -3
- data/.rubocop_todo.yml +8 -1
- data/CONTRIBUTING.md +42 -12
- data/Gemfile +11 -11
- data/README.md +64 -10
- data/active_encode.gemspec +2 -4
- data/app/controllers/active_encode/encode_record_controller.rb +1 -1
- data/app/jobs/active_encode/polling_job.rb +1 -1
- data/app/models/active_encode/encode_record.rb +1 -1
- data/guides/media_convert_adapter.md +208 -0
- data/lib/active_encode/base.rb +1 -1
- data/lib/active_encode/core.rb +14 -14
- data/lib/active_encode/engine_adapter.rb +13 -13
- data/lib/active_encode/engine_adapters/elastic_transcoder_adapter.rb +158 -158
- data/lib/active_encode/engine_adapters/ffmpeg_adapter.rb +14 -3
- data/lib/active_encode/engine_adapters/matterhorn_adapter.rb +204 -202
- data/lib/active_encode/engine_adapters/media_convert_adapter.rb +435 -203
- data/lib/active_encode/engine_adapters/media_convert_output.rb +67 -5
- data/lib/active_encode/engine_adapters/pass_through_adapter.rb +3 -3
- data/lib/active_encode/engine_adapters/zencoder_adapter.rb +114 -114
- data/lib/active_encode/errors.rb +1 -1
- data/lib/active_encode/persistence.rb +19 -19
- data/lib/active_encode/version.rb +1 -1
- data/lib/file_locator.rb +6 -6
- data/spec/fixtures/ffmpeg/cancelled-id/exit_status.code +1 -0
- data/spec/fixtures/ffmpeg/completed-id/exit_status.code +1 -0
- data/spec/fixtures/ffmpeg/completed-with-warnings-id/error.log +3 -0
- data/spec/fixtures/ffmpeg/completed-with-warnings-id/exit_status.code +1 -0
- data/spec/fixtures/ffmpeg/completed-with-warnings-id/input_metadata +102 -0
- data/spec/fixtures/ffmpeg/completed-with-warnings-id/output_metadata-high +90 -0
- data/spec/fixtures/ffmpeg/completed-with-warnings-id/output_metadata-low +90 -0
- data/spec/fixtures/ffmpeg/completed-with-warnings-id/pid +1 -0
- data/spec/fixtures/ffmpeg/completed-with-warnings-id/progress +11 -0
- data/spec/fixtures/ffmpeg/completed-with-warnings-id/video-high.mp4 +0 -0
- data/spec/fixtures/ffmpeg/completed-with-warnings-id/video-low.mp4 +0 -0
- data/spec/fixtures/ffmpeg/failed-id/exit_status.code +1 -0
- data/spec/fixtures/media_convert/job_completed_empty_detail.json +1 -0
- data/spec/integration/ffmpeg_adapter_spec.rb +50 -1
- data/spec/integration/matterhorn_adapter_spec.rb +1 -2
- data/spec/integration/media_convert_adapter_spec.rb +144 -0
- data/spec/integration/pass_through_adapter_spec.rb +2 -2
- data/spec/integration/zencoder_adapter_spec.rb +3 -3
- data/spec/units/core_spec.rb +1 -1
- data/spec/units/file_locator_spec.rb +3 -3
- data/spec/units/status_spec.rb +1 -1
- metadata +52 -19
@@ -2,6 +2,7 @@
|
|
2
2
|
require 'fileutils'
|
3
3
|
require 'nokogiri'
|
4
4
|
require 'shellwords'
|
5
|
+
require 'addressable/uri'
|
5
6
|
|
6
7
|
module ActiveEncode
|
7
8
|
module EngineAdapters
|
@@ -14,7 +15,7 @@ module ActiveEncode
|
|
14
15
|
# Decode file uris for ffmpeg (mediainfo works either way)
|
15
16
|
case input_url
|
16
17
|
when /^file\:\/\/\//
|
17
|
-
input_url = URI.
|
18
|
+
input_url = Addressable::URI.unencode(input_url)
|
18
19
|
when /^s3\:\/\//
|
19
20
|
require 'file_locator'
|
20
21
|
|
@@ -63,6 +64,9 @@ module ActiveEncode
|
|
63
64
|
|
64
65
|
# Run the ffmpeg command and save its pid
|
65
66
|
command = ffmpeg_command(input_url, new_encode.id, options)
|
67
|
+
# Capture the exit status in a file in order to differentiate warning output in stderr between real process failure
|
68
|
+
exit_status_file = working_path("exit_status.code", new_encode.id)
|
69
|
+
command = "#{command}; echo $? > #{exit_status_file}"
|
66
70
|
pid = Process.spawn(command, err: working_path('error.log', new_encode.id))
|
67
71
|
File.open(working_path("pid", new_encode.id), 'w') { |file| file.write pid }
|
68
72
|
new_encode.input.id = pid
|
@@ -73,7 +77,7 @@ module ActiveEncode
|
|
73
77
|
new_encode.percent_complete = 1
|
74
78
|
new_encode.errors = [e.full_message]
|
75
79
|
write_errors new_encode
|
76
|
-
|
80
|
+
new_encode
|
77
81
|
ensure
|
78
82
|
# Prevent zombie process
|
79
83
|
Process.detach(pid) if pid.present?
|
@@ -96,7 +100,8 @@ module ActiveEncode
|
|
96
100
|
encode.current_operations = []
|
97
101
|
encode.created_at, encode.updated_at = get_times encode.id
|
98
102
|
encode.errors = read_errors(id)
|
99
|
-
|
103
|
+
exit_code = read_exit_code(id)
|
104
|
+
if exit_code.present? && exit_code != 0 && exit_code != 143
|
100
105
|
encode.state = :failed
|
101
106
|
elsif running? pid
|
102
107
|
encode.state = :running
|
@@ -171,6 +176,12 @@ module ActiveEncode
|
|
171
176
|
end
|
172
177
|
end
|
173
178
|
|
179
|
+
def read_exit_code(id)
|
180
|
+
exit_path = working_path("exit_status.code", id)
|
181
|
+
exit_status = File.read(exit_path) if File.file? exit_path
|
182
|
+
exit_status.present? ? exit_status.to_i : nil # process still running
|
183
|
+
end
|
184
|
+
|
174
185
|
def build_input(encode)
|
175
186
|
input = ActiveEncode::Input.new
|
176
187
|
metadata = get_tech_metadata(working_path("input_metadata", encode.id))
|
@@ -23,238 +23,240 @@ module ActiveEncode
|
|
23
23
|
|
24
24
|
private
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
end
|
32
|
-
|
33
|
-
workflow_om ||= begin
|
34
|
-
Rubyhorn.client.get_stopped_workflow(id)
|
35
|
-
rescue
|
36
|
-
nil
|
37
|
-
end
|
38
|
-
|
39
|
-
get_workflow(workflow_om)
|
26
|
+
def fetch_workflow(id)
|
27
|
+
workflow_om = begin
|
28
|
+
Rubyhorn.client.instance_xml(id)
|
29
|
+
rescue Rubyhorn::RestClient::Exceptions::HTTPNotFound
|
30
|
+
nil
|
40
31
|
end
|
41
32
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
else
|
47
|
-
workflow_om.ng_xml
|
48
|
-
end
|
33
|
+
workflow_om ||= begin
|
34
|
+
Rubyhorn.client.get_stopped_workflow(id)
|
35
|
+
rescue
|
36
|
+
nil
|
49
37
|
end
|
50
38
|
|
51
|
-
|
52
|
-
|
53
|
-
input_url = convert_input(workflow)
|
54
|
-
input_url = get_workflow_title(workflow) if input_url.blank?
|
55
|
-
encode = ActiveEncode::Base.new(input_url, convert_options(workflow))
|
56
|
-
encode.id = convert_id(workflow)
|
57
|
-
encode.state = convert_state(workflow)
|
58
|
-
encode.current_operations = convert_current_operations(workflow)
|
59
|
-
encode.percent_complete = calculate_percent_complete(workflow)
|
60
|
-
encode.created_at = convert_created_at(workflow)
|
61
|
-
encode.updated_at = convert_updated_at(workflow) || encode.created_at
|
62
|
-
encode.output = convert_output(workflow, encode.options)
|
63
|
-
encode.errors = convert_errors(workflow)
|
64
|
-
|
65
|
-
encode.input.id = "presenter/source"
|
66
|
-
encode.input.state = encode.state
|
67
|
-
encode.input.created_at = encode.created_at
|
68
|
-
encode.input.updated_at = encode.updated_at
|
69
|
-
tech_md = convert_tech_metadata(workflow)
|
70
|
-
[:width, :height, :duration, :frame_rate, :checksum, :audio_codec, :video_codec,
|
71
|
-
:audio_bitrate, :video_bitrate].each do |field|
|
72
|
-
encode.input.send("#{field}=", tech_md[field])
|
73
|
-
end
|
39
|
+
get_workflow(workflow_om)
|
40
|
+
end
|
74
41
|
|
75
|
-
|
42
|
+
def get_workflow(workflow_om)
|
43
|
+
return nil if workflow_om.nil?
|
44
|
+
if workflow_om.ng_xml.is_a? Nokogiri::XML::Document
|
45
|
+
workflow_om.ng_xml.remove_namespaces!.root
|
46
|
+
else
|
47
|
+
workflow_om.ng_xml
|
76
48
|
end
|
49
|
+
end
|
77
50
|
|
78
|
-
|
79
|
-
|
51
|
+
def build_encode(workflow)
|
52
|
+
return nil if workflow.nil?
|
53
|
+
input_url = convert_input(workflow)
|
54
|
+
input_url = get_workflow_title(workflow) if input_url.blank?
|
55
|
+
encode = ActiveEncode::Base.new(input_url, convert_options(workflow))
|
56
|
+
encode.id = convert_id(workflow)
|
57
|
+
encode.state = convert_state(workflow)
|
58
|
+
encode.current_operations = convert_current_operations(workflow)
|
59
|
+
encode.percent_complete = calculate_percent_complete(workflow)
|
60
|
+
encode.created_at = convert_created_at(workflow)
|
61
|
+
encode.updated_at = convert_updated_at(workflow) || encode.created_at
|
62
|
+
encode.output = convert_output(workflow, encode.options)
|
63
|
+
encode.errors = convert_errors(workflow)
|
64
|
+
|
65
|
+
encode.input.id = "presenter/source"
|
66
|
+
encode.input.state = encode.state
|
67
|
+
encode.input.created_at = encode.created_at
|
68
|
+
encode.input.updated_at = encode.updated_at
|
69
|
+
tech_md = convert_tech_metadata(workflow)
|
70
|
+
[:width, :height, :duration, :frame_rate, :checksum, :audio_codec, :video_codec,
|
71
|
+
:audio_bitrate, :video_bitrate].each do |field|
|
72
|
+
encode.input.send("#{field}=", tech_md[field])
|
80
73
|
end
|
81
74
|
|
82
|
-
|
83
|
-
|
84
|
-
end
|
75
|
+
encode
|
76
|
+
end
|
85
77
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
:running
|
90
|
-
when "STOPPED"
|
91
|
-
:cancelled
|
92
|
-
when "FAILED"
|
93
|
-
workflow.xpath('//operation[@state="FAILED"]').empty? ? :cancelled : :failed
|
94
|
-
when "SUCCEEDED", "SKIPPED" # Should there be a errored state?
|
95
|
-
:completed
|
96
|
-
end
|
97
|
-
end
|
78
|
+
def convert_id(workflow)
|
79
|
+
workflow.attribute('id').to_s
|
80
|
+
end
|
98
81
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
end
|
82
|
+
def get_workflow_state(workflow)
|
83
|
+
workflow.attribute('state').to_s
|
84
|
+
end
|
103
85
|
|
104
|
-
|
105
|
-
|
86
|
+
def convert_state(workflow)
|
87
|
+
case get_workflow_state(workflow)
|
88
|
+
when "INSTANTIATED", "RUNNING" # Should there be a queued state?
|
89
|
+
:running
|
90
|
+
when "STOPPED"
|
91
|
+
:cancelled
|
92
|
+
when "FAILED"
|
93
|
+
workflow.xpath('//operation[@state="FAILED"]').empty? ? :cancelled : :failed
|
94
|
+
when "SUCCEEDED", "SKIPPED" # Should there be a errored state?
|
95
|
+
:completed
|
106
96
|
end
|
97
|
+
end
|
107
98
|
|
108
|
-
|
109
|
-
|
110
|
-
|
99
|
+
def convert_input(workflow)
|
100
|
+
# Need to do anything else since this is a MH url? and this disappears when a workflow is cleaned up
|
101
|
+
workflow.xpath('mediapackage/media/track[@type="presenter/source"]/url/text()').to_s.strip
|
102
|
+
end
|
103
|
+
|
104
|
+
def get_workflow_title(workflow)
|
105
|
+
workflow.xpath('mediapackage/title/text()').to_s.strip
|
106
|
+
end
|
111
107
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
[:width, :height, :frame_rate, :duration, :checksum, :audio_codec, :video_codec,
|
125
|
-
:audio_bitrate, :video_bitrate, :file_size].each do |field|
|
126
|
-
output.send("#{field}=", tech_md[field])
|
127
|
-
end
|
128
|
-
|
129
|
-
output.state = :completed
|
130
|
-
output.created_at = convert_output_created_at(track, workflow)
|
131
|
-
output.updated_at = convert_output_updated_at(track, workflow)
|
132
|
-
|
133
|
-
outputs << output
|
108
|
+
def convert_tech_metadata(workflow)
|
109
|
+
convert_track_metadata(workflow.xpath('//track[@type="presenter/source"]').first)
|
110
|
+
end
|
111
|
+
|
112
|
+
def convert_output(workflow, options)
|
113
|
+
outputs = []
|
114
|
+
workflow.xpath('//track[@type="presenter/delivery" and tags/tag[text()="streaming"]]').each do |track|
|
115
|
+
output = ActiveEncode::Output.new
|
116
|
+
output.label = track.xpath('tags/tag[starts-with(text(),"quality")]/text()').to_s
|
117
|
+
output.url = track.at("url/text()").to_s
|
118
|
+
if output.url.start_with? "rtmp"
|
119
|
+
output.url = File.join(options[:stream_base], MatterhornRtmpUrl.parse(output.url).to_path) if options[:stream_base]
|
134
120
|
end
|
135
|
-
|
136
|
-
end
|
121
|
+
output.id = track.at("@id").to_s
|
137
122
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
123
|
+
tech_md = convert_track_metadata(track)
|
124
|
+
[:width, :height, :frame_rate, :duration, :checksum, :audio_codec, :video_codec,
|
125
|
+
:audio_bitrate, :video_bitrate, :file_size].each do |field|
|
126
|
+
output.send("#{field}=", tech_md[field])
|
127
|
+
end
|
142
128
|
|
143
|
-
|
144
|
-
|
145
|
-
|
129
|
+
output.state = :completed
|
130
|
+
output.created_at = convert_output_created_at(track, workflow)
|
131
|
+
output.updated_at = convert_output_updated_at(track, workflow)
|
146
132
|
|
147
|
-
|
148
|
-
created_at = workflow.xpath('mediapackage/@start').last.to_s
|
149
|
-
created_at.present? ? Time.parse(created_at).utc : nil
|
133
|
+
outputs << output
|
150
134
|
end
|
135
|
+
outputs
|
136
|
+
end
|
151
137
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
138
|
+
def convert_current_operations(workflow)
|
139
|
+
current_op = workflow.xpath('//operation[@state!="INSTANTIATED"]/@description').last.to_s
|
140
|
+
current_op.present? ? [current_op] : []
|
141
|
+
end
|
156
142
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
created_at.present? ? Time.at(created_at.to_i / 1000.0).utc : nil
|
161
|
-
end
|
143
|
+
def convert_errors(workflow)
|
144
|
+
workflow.xpath('//errors/error/text()').map(&:to_s)
|
145
|
+
end
|
162
146
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
end
|
147
|
+
def convert_created_at(workflow)
|
148
|
+
created_at = workflow.xpath('mediapackage/@start').last.to_s
|
149
|
+
created_at.present? ? Time.parse(created_at).utc : nil
|
150
|
+
end
|
168
151
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
options
|
174
|
-
end
|
152
|
+
def convert_updated_at(workflow)
|
153
|
+
updated_at = workflow.xpath('//operation[@state!="INSTANTIATED"]/completed/text()').last.to_s
|
154
|
+
updated_at.present? ? Time.strptime(updated_at, "%Q") : nil
|
155
|
+
end
|
175
156
|
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
metadata[:duration] = track.at("duration/text()").to_s.to_i if track.at('duration')
|
182
|
-
if track.at('audio')
|
183
|
-
metadata[:audio_codec] = track.at("audio/encoder/@type").to_s
|
184
|
-
metadata[:audio_channels] = track.at("audio/channels/text()").to_s
|
185
|
-
metadata[:audio_bitrate] = track.at("audio/bitrate/text()").to_s.to_f
|
186
|
-
end
|
187
|
-
if track.at('video')
|
188
|
-
metadata[:video_codec] = track.at("video/encoder/@type").to_s
|
189
|
-
metadata[:video_bitrate] = track.at("video/bitrate/text()").to_s.to_f
|
190
|
-
metadata[:frame_rate] = track.at("video/framerate/text()").to_s.to_f
|
191
|
-
metadata[:width] = track.at("video/resolution/text()").to_s.split('x')[0].to_i
|
192
|
-
metadata[:height] = track.at("video/resolution/text()").to_s.split('x')[1].to_i
|
193
|
-
end
|
194
|
-
metadata
|
195
|
-
end
|
157
|
+
def convert_output_created_at(track, workflow)
|
158
|
+
quality = track.xpath('tags/tag[starts-with(text(),"quality")]/text()').to_s
|
159
|
+
created_at = workflow.xpath("//operation[@id=\"compose\"][configurations/configuration[@key=\"target-tags\" and contains(text(), \"#{quality}\")]]/started/text()").to_s
|
160
|
+
created_at.present? ? Time.at(created_at.to_i / 1000.0).utc : nil
|
161
|
+
end
|
196
162
|
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
end
|
163
|
+
def convert_output_updated_at(track, workflow)
|
164
|
+
quality = track.xpath('tags/tag[starts-with(text(),"quality")]/text()').to_s
|
165
|
+
updated_at = workflow.xpath("//operation[@id=\"compose\"][configurations/configuration[@key=\"target-tags\" and contains(text(), \"#{quality}\")]]/completed/text()").to_s
|
166
|
+
updated_at.present? ? Time.at(updated_at.to_i / 1000.0).utc : nil
|
167
|
+
end
|
203
168
|
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
((totals[:other].to_f / total_other_operations) * completed_other_operations)
|
169
|
+
def convert_options(workflow)
|
170
|
+
options = {}
|
171
|
+
options[:preset] = workflow.xpath('template/text()').to_s
|
172
|
+
if workflow.xpath('//properties/property[@key="avalon.stream_base"]/text()').present?
|
173
|
+
options[:stream_base] = workflow.xpath('//properties/property[@key="avalon.stream_base"]/text()').to_s
|
174
|
+
end # this is avalon-felix specific
|
175
|
+
options
|
176
|
+
end
|
177
|
+
|
178
|
+
def convert_track_metadata(track)
|
179
|
+
return {} if track.nil?
|
180
|
+
metadata = {}
|
181
|
+
# metadata[:mime_type] = track.at("mimetype/text()").to_s if track.at('mimetype')
|
182
|
+
metadata[:checksum] = track.at("checksum/text()").to_s.strip if track.at('checksum')
|
183
|
+
metadata[:duration] = track.at("duration/text()").to_s.to_i if track.at('duration')
|
184
|
+
if track.at('audio')
|
185
|
+
metadata[:audio_codec] = track.at("audio/encoder/@type").to_s
|
186
|
+
metadata[:audio_channels] = track.at("audio/channels/text()").to_s
|
187
|
+
metadata[:audio_bitrate] = track.at("audio/bitrate/text()").to_s.to_f
|
224
188
|
end
|
189
|
+
if track.at('video')
|
190
|
+
metadata[:video_codec] = track.at("video/encoder/@type").to_s
|
191
|
+
metadata[:video_bitrate] = track.at("video/bitrate/text()").to_s.to_f
|
192
|
+
metadata[:frame_rate] = track.at("video/framerate/text()").to_s.to_f
|
193
|
+
metadata[:width] = track.at("video/resolution/text()").to_s.split('x')[0].to_i
|
194
|
+
metadata[:height] = track.at("video/resolution/text()").to_s.split('x')[1].to_i
|
195
|
+
end
|
196
|
+
metadata
|
197
|
+
end
|
225
198
|
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
199
|
+
def get_media_package(workflow)
|
200
|
+
mp = workflow.xpath('//mediapackage')
|
201
|
+
first_node = mp.first
|
202
|
+
first_node['xmlns'] = 'http://mediapackage.opencastproject.org'
|
203
|
+
mp
|
204
|
+
end
|
205
|
+
|
206
|
+
def calculate_percent_complete(workflow)
|
207
|
+
totals = {
|
208
|
+
transcode: 70,
|
209
|
+
distribution: 20,
|
210
|
+
other: 10
|
211
|
+
}
|
212
|
+
|
213
|
+
completed_transcode_operations = workflow.xpath('//operation[@id="compose" and (@state="SUCCEEDED" or @state="SKIPPED")]').size
|
214
|
+
total_transcode_operations = workflow.xpath('//operation[@id="compose"]').size
|
215
|
+
total_transcode_operations = 1 if total_transcode_operations.zero?
|
216
|
+
completed_distribution_operations = workflow.xpath('//operation[starts-with(@id,"distribute") and (@state="SUCCEEDED" or @state="SKIPPED")]').size
|
217
|
+
total_distribution_operations = workflow.xpath('//operation[starts-with(@id,"distribute")]').size
|
218
|
+
total_distribution_operations = 1 if total_distribution_operations.zero?
|
219
|
+
completed_other_operations = workflow.xpath('//operation[@id!="compose" and not(starts-with(@id,"distribute")) and (@state="SUCCEEDED" or @state="SKIPPED")]').size
|
220
|
+
total_other_operations = workflow.xpath('//operation[@id!="compose" and not(starts-with(@id,"distribute"))]').size
|
221
|
+
total_other_operations = 1 if total_other_operations.zero?
|
222
|
+
|
223
|
+
((totals[:transcode].to_f / total_transcode_operations) * completed_transcode_operations) +
|
224
|
+
((totals[:distribution].to_f / total_distribution_operations) * completed_distribution_operations) +
|
225
|
+
((totals[:other].to_f / total_other_operations) * completed_other_operations)
|
226
|
+
end
|
227
|
+
|
228
|
+
def create_multiple_files(input, workflow_id)
|
229
|
+
# Create empty media package xml document
|
230
|
+
mp = Rubyhorn.client.createMediaPackage
|
231
|
+
|
232
|
+
# Next line associates workflow title to avalon via masterfile pid
|
233
|
+
title = File.basename(input.values.first)
|
234
|
+
dc = Nokogiri::XML('<dublincore xmlns="http://www.opencastproject.org/xsd/1.0/dublincore/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><dcterms:title>' + title + '</dcterms:title></dublincore>')
|
235
|
+
mp = Rubyhorn.client.addDCCatalog('mediaPackage' => mp.to_xml, 'dublinCore' => dc.to_xml, 'flavor' => 'dublincore/episode')
|
236
|
+
|
237
|
+
# Add quality levels - repeated for each supplied file url
|
238
|
+
input.each_pair do |quality, url|
|
239
|
+
mp = Rubyhorn.client.addTrack('mediaPackage' => mp.to_xml, 'url' => url, 'flavor' => DEFAULT_ARGS['flavor'])
|
240
|
+
# Rewrite track to include quality tag
|
241
|
+
# Get the empty tags element under the newly added track
|
242
|
+
tags = mp.xpath('//xmlns:track/xmlns:tags[not(node())]', 'xmlns' => 'http://mediapackage.opencastproject.org').first
|
243
|
+
quality_tag = Nokogiri::XML::Node.new 'tag', mp
|
244
|
+
quality_tag.content = quality
|
245
|
+
tags.add_child quality_tag
|
246
|
+
end
|
247
|
+
# Finally ingest the media package
|
248
|
+
begin
|
249
|
+
Rubyhorn.client.start("definitionId" => workflow_id, "mediapackage" => mp.to_xml)
|
250
|
+
rescue Rubyhorn::RestClient::Exceptions::HTTPBadRequest
|
251
|
+
# make this two calls...one to get the workflow definition xml and then the second to submit it along with the mediapackage to start...due to unsolved issue with some MH installs
|
246
252
|
begin
|
247
|
-
Rubyhorn.client.
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
workflow_definition_xml = Rubyhorn.client.definition_xml(workflow_id)
|
252
|
-
Rubyhorn.client.start("definition" => workflow_definition_xml, "mediapackage" => mp.to_xml)
|
253
|
-
rescue Rubyhorn::RestClient::Exceptions::HTTPNotFound
|
254
|
-
raise StandardError, "Unable to start workflow"
|
255
|
-
end
|
253
|
+
workflow_definition_xml = Rubyhorn.client.definition_xml(workflow_id)
|
254
|
+
Rubyhorn.client.start("definition" => workflow_definition_xml, "mediapackage" => mp.to_xml)
|
255
|
+
rescue Rubyhorn::RestClient::Exceptions::HTTPNotFound
|
256
|
+
raise StandardError, "Unable to start workflow"
|
256
257
|
end
|
257
|
-
|
258
|
+
end
|
259
|
+
end
|
258
260
|
end
|
259
261
|
|
260
262
|
class MatterhornRtmpUrl
|
@@ -268,7 +270,7 @@ module ActiveEncode
|
|
268
270
|
/(?<stream_id>[^\/]+) # stream_id (b3d5663d-53f1-4f7d-b7be-b52fd5ca50a3)
|
269
271
|
/(?<filename>.+?) # filename (MVI_0057)
|
270
272
|
(?:\.(?<extension>.+))?$ # extension (mp4)
|
271
|
-
}x
|
273
|
+
}x.freeze
|
272
274
|
|
273
275
|
# @param [MatchData] match_data
|
274
276
|
def initialize(match_data)
|