active_encode 0.8.2 → 1.0.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 +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 +421 -217
- 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/integration/ffmpeg_adapter_spec.rb +50 -1
- data/spec/integration/matterhorn_adapter_spec.rb +1 -2
- data/spec/integration/media_convert_adapter_spec.rb +91 -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 +50 -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)
|