active_encode 1.2.3 → 2.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.
@@ -35,8 +35,8 @@ module ActiveEncode
35
35
  # @param output_detail_settings [Aws::MediaConvert::Types::OutputDetail]
36
36
  def tech_metadata_from_settings(output_url:, output_settings:, output_detail_settings:)
37
37
  {
38
- width: output_detail_settings.video_details.width_in_px,
39
- height: output_detail_settings.video_details.height_in_px,
38
+ width: output_detail_settings.video_details&.width_in_px,
39
+ height: output_detail_settings.video_details&.height_in_px,
40
40
  frame_rate: extract_video_frame_rate(output_settings),
41
41
  duration: output_detail_settings.duration_in_ms,
42
42
  audio_codec: extract_audio_codec(output_settings),
@@ -44,7 +44,6 @@ module ActiveEncode
44
44
  audio_bitrate: extract_audio_bitrate(output_settings),
45
45
  video_bitrate: extract_video_bitrate(output_settings),
46
46
  url: output_url,
47
- label: (output_url ? File.basename(output_url) : output_settings.name_modifier),
48
47
  suffix: output_settings.name_modifier
49
48
  }
50
49
  end
@@ -61,11 +60,33 @@ module ActiveEncode
61
60
  audio_bitrate: extract_audio_bitrate(settings),
62
61
  video_bitrate: extract_video_bitrate(settings),
63
62
  url: url,
64
- label: File.basename(url),
65
63
  suffix: settings.name_modifier
66
64
  }
67
65
  end
68
66
 
67
+ def tech_metadata_from_probe(url:, probe_response:, output_settings: nil)
68
+ tech_md = { url: url, suffix: output_settings&.name_modifier }
69
+ return tech_md unless probe_response
70
+
71
+ # Need to determine which track has video/audio
72
+ video_track = probe_response.container.tracks&.find { |track| track.track_type == "video" }
73
+ audio_track = probe_response.container.tracks&.find { |track| track.track_type == "audio" }
74
+ frame_rate = (video_track.video_properties.frame_rate.numerator / video_track.video_properties.frame_rate.denominator.to_f).round(2) if video_track
75
+ duration = probe_response.container.duration * 1000 if probe_response.container.duration.present?
76
+
77
+ tech_md.merge({
78
+ width: video_track&.video_properties&.width,
79
+ height: video_track&.video_properties&.height,
80
+ frame_rate: frame_rate,
81
+ duration: duration, # milliseconds
82
+ audio_codec: audio_track&.codec,
83
+ video_codec: video_track&.codec,
84
+ audio_bitrate: audio_track&.audio_properties&.bit_rate,
85
+ video_bitrate: video_track&.video_properties&.bit_rate,
86
+ file_size: probe_response.metadata.file_size
87
+ })
88
+ end
89
+
69
90
  # constructs an `s3:` output URL from the MediaConvert job params, the same
70
91
  # way MediaConvert will.
71
92
  #
@@ -110,27 +131,27 @@ module ActiveEncode
110
131
  end
111
132
 
112
133
  def extract_audio_codec(settings)
113
- settings.audio_descriptions.first.codec_settings.codec
114
- rescue
115
- nil
134
+ settings.audio_descriptions&.first&.codec_settings&.codec
116
135
  end
117
136
 
118
137
  def extract_audio_codec_settings(settings)
119
- codec_key = AUDIO_SETTINGS[extract_audio_codec(settings)]
138
+ codec = extract_audio_codec(settings)
139
+ return nil if codec.nil?
140
+
141
+ codec_key = AUDIO_SETTINGS[codec]
120
142
  settings.audio_descriptions.first.codec_settings[codec_key]
121
143
  end
122
144
 
123
145
  def extract_video_codec(settings)
124
- settings.video_description.codec_settings.codec
125
- rescue
126
- nil
146
+ settings.video_description&.codec_settings&.codec
127
147
  end
128
148
 
129
149
  def extract_video_codec_settings(settings)
130
- codec_key = VIDEO_SETTINGS[extract_video_codec(settings)]
150
+ codec = extract_video_codec(settings)
151
+ return nil if codec.nil?
152
+
153
+ codec_key = VIDEO_SETTINGS[codec]
131
154
  settings.video_description.codec_settings[codec_key]
132
- rescue
133
- nil
134
155
  end
135
156
 
136
157
  def extract_audio_bitrate(settings)
@@ -234,13 +234,18 @@ module ActiveEncode
234
234
  doc.remove_namespaces!
235
235
  duration = get_xpath_text(doc, '//Duration/text()', :to_f)
236
236
  duration *= 1000 unless duration.nil? # Convert to milliseconds
237
+ audio_codec = get_xpath_text(doc, '//track[@type="Audio"]/CodecID/text()', :to_s)
238
+ if get_xpath_text(doc, '//track[@type="Audio"]/Format/text()', :to_s) == "MPEG Audio" &&
239
+ get_xpath_text(doc, '//track[@type="Audio"]/Format_Profile/text()', :to_s) == "Layer 3"
240
+ audio_codec ||= "mp3"
241
+ end
237
242
  { url: get_xpath_text(doc, '//media/@ref', :to_s),
238
243
  width: get_xpath_text(doc, '//Width/text()', :to_f),
239
244
  height: get_xpath_text(doc, '//Height/text()', :to_f),
240
245
  frame_rate: get_xpath_text(doc, '//FrameRate/text()', :to_f),
241
246
  duration: duration,
242
247
  file_size: get_xpath_text(doc, '//FileSize/text()', :to_i),
243
- audio_codec: get_xpath_text(doc, '//track[@type="Audio"]/CodecID/text()', :to_s),
248
+ audio_codec: audio_codec,
244
249
  audio_bitrate: get_xpath_text(doc, '//track[@type="Audio"]/BitRate/text()', :to_i),
245
250
  video_codec: get_xpath_text(doc, '//track[@type="Video"]/CodecID/text()', :to_s),
246
251
  video_bitrate: get_xpath_text(doc, '//track[@type="Video"]/BitRate/text()', :to_i) }
@@ -9,8 +9,6 @@ module ActiveEncode
9
9
  module EngineAdapters
10
10
  extend ActiveSupport::Autoload
11
11
 
12
- autoload :MatterhornAdapter
13
- autoload :ZencoderAdapter
14
12
  autoload :ElasticTranscoderAdapter
15
13
  autoload :TestAdapter
16
14
  autoload :FfmpegAdapter
@@ -10,7 +10,8 @@ module ActiveEncode
10
10
  filepath = input_url.is_a?(URI::HTTP) ? input_url.path : input_url
11
11
  # Replace special characters with underscores and remove excess periods.
12
12
  # This removes the extension before processing so it is safe to delete all detected periods.
13
- File.basename(filepath, File.extname(filepath)).gsub(/[^0-9A-Za-z.\-\/]/, '_').delete('.')
13
+ # This explicitly handles percent encoded spaces.
14
+ File.basename(filepath, File.extname(filepath)).gsub(/%20/, "_").gsub(/[^0-9A-Za-z.\-\/]/, '_').delete('.')
14
15
  end
15
16
 
16
17
  def sanitize_filename(input_url)
@@ -24,7 +25,7 @@ module ActiveEncode
24
25
  when /^file\:\/\/\//
25
26
  input_url.to_s.gsub(/file:\/\//, '')
26
27
  when /^s3\:\/\//
27
- input_url.to_s.gsub(/#{Addressable::URI.parse(input_url).normalized_site}/, '')
28
+ input_url.to_s.gsub(/#{Addressable::URI.parse(input_url).site}/, '')
28
29
  when /^https?:\/\//
29
30
  input_url
30
31
  end
@@ -22,13 +22,25 @@ module ActiveEncode
22
22
  attr_accessor :video_codec
23
23
  attr_accessor :audio_bitrate
24
24
  attr_accessor :video_bitrate
25
+
26
+ # Array of hashes
27
+ attr_accessor :subtitles
28
+ attr_accessor :format
29
+ attr_accessor :language
25
30
  end
26
31
 
27
32
  def assign_tech_metadata(metadata)
28
33
  [:width, :height, :frame_rate, :duration, :file_size, :checksum,
29
- :audio_codec, :video_codec, :audio_bitrate, :video_bitrate].each do |field|
34
+ :audio_codec, :video_codec, :audio_bitrate, :video_bitrate, :subtitles,
35
+ :format, :language].each do |field|
30
36
  send("#{field}=", metadata[field]) if metadata.key?(field)
31
37
  end
32
38
  end
39
+
40
+ def tech_metadata
41
+ [:width, :height, :frame_rate, :duration, :file_size, :checksum,
42
+ :audio_codec, :video_codec, :audio_bitrate, :video_bitrate, :subtitles,
43
+ :format, :language].index_with { |field| send(field) }
44
+ end
33
45
  end
34
46
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module ActiveEncode
3
- VERSION = '1.2.3'
3
+ VERSION = '2.0.0'
4
4
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_encode
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.3
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Klein, Chris Colvard, Phuong Dinh
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2024-11-13 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: rails
@@ -38,6 +37,20 @@ dependencies:
38
37
  - - "~>"
39
38
  - !ruby/object:Gem::Version
40
39
  version: '2.8'
40
+ - !ruby/object:Gem::Dependency
41
+ name: retriable
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
41
54
  - !ruby/object:Gem::Dependency
42
55
  name: aws-sdk-cloudwatchevents
43
56
  requirement: !ruby/object:Gem::Requirement
@@ -66,6 +79,20 @@ dependencies:
66
79
  - - ">="
67
80
  - !ruby/object:Gem::Version
68
81
  version: '0'
82
+ - !ruby/object:Gem::Dependency
83
+ name: aws-sdk-core
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "<="
87
+ - !ruby/object:Gem::Version
88
+ version: 3.220.0
89
+ type: :development
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "<="
94
+ - !ruby/object:Gem::Version
95
+ version: 3.220.0
69
96
  - !ruby/object:Gem::Dependency
70
97
  name: aws-sdk-elastictranscoder
71
98
  requirement: !ruby/object:Gem::Requirement
@@ -86,14 +113,14 @@ dependencies:
86
113
  requirements:
87
114
  - - ">="
88
115
  - !ruby/object:Gem::Version
89
- version: '0'
116
+ version: 1.157.0
90
117
  type: :development
91
118
  prerelease: false
92
119
  version_requirements: !ruby/object:Gem::Requirement
93
120
  requirements:
94
121
  - - ">="
95
122
  - !ruby/object:Gem::Version
96
- version: '0'
123
+ version: 1.157.0
97
124
  - !ruby/object:Gem::Dependency
98
125
  name: aws-sdk-s3
99
126
  requirement: !ruby/object:Gem::Requirement
@@ -241,7 +268,7 @@ dependencies:
241
268
  - !ruby/object:Gem::Version
242
269
  version: '0'
243
270
  description: This gem provides an interface to transcoding services such as Ffmpeg,
244
- Amazon Elastic Transcoder, or Zencoder.
271
+ Amazon Elastic Transcoder, or Amazon Elemental MediaConvert.
245
272
  email:
246
273
  - mbklein@gmail.com, chris.colvard@gmail.com, phuongdh@gmail.com
247
274
  executables: []
@@ -279,12 +306,10 @@ files:
279
306
  - lib/active_encode/engine_adapters/elastic_transcoder_adapter.rb
280
307
  - lib/active_encode/engine_adapters/ffmpeg_adapter.rb
281
308
  - lib/active_encode/engine_adapters/ffmpeg_adapter/cleaner.rb
282
- - lib/active_encode/engine_adapters/matterhorn_adapter.rb
283
309
  - lib/active_encode/engine_adapters/media_convert_adapter.rb
284
310
  - lib/active_encode/engine_adapters/media_convert_output.rb
285
311
  - lib/active_encode/engine_adapters/pass_through_adapter.rb
286
312
  - lib/active_encode/engine_adapters/test_adapter.rb
287
- - lib/active_encode/engine_adapters/zencoder_adapter.rb
288
313
  - lib/active_encode/errors.rb
289
314
  - lib/active_encode/filename_sanitizer.rb
290
315
  - lib/active_encode/global_id.rb
@@ -303,7 +328,6 @@ licenses:
303
328
  - Apache-2.0
304
329
  metadata:
305
330
  rubygems_mfa_required: 'true'
306
- post_install_message:
307
331
  rdoc_options: []
308
332
  require_paths:
309
333
  - lib
@@ -318,8 +342,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
318
342
  - !ruby/object:Gem::Version
319
343
  version: '0'
320
344
  requirements: []
321
- rubygems_version: 3.5.16
322
- signing_key:
345
+ rubygems_version: 3.6.9
323
346
  specification_version: 4
324
347
  summary: Declare encode job classes that can be run by a variety of encoding services
325
348
  test_files: []
@@ -1,300 +0,0 @@
1
- # frozen_string_literal: true
2
- require 'rubyhorn'
3
-
4
- module ActiveEncode
5
- module EngineAdapters
6
- class MatterhornAdapter
7
- DEFAULT_ARGS = { 'flavor' => 'presenter/source' }.freeze
8
-
9
- def create(input_url, options = {})
10
- workflow_id = options[:preset] || "full"
11
- workflow_om = Rubyhorn.client.addMediaPackageWithUrl(DEFAULT_ARGS.merge('workflow' => workflow_id, 'url' => input_url, 'filename' => File.basename(input_url), 'title' => File.basename(input_url)))
12
- build_encode(get_workflow(workflow_om))
13
- end
14
-
15
- def find(id, _opts = {})
16
- build_encode(fetch_workflow(id))
17
- end
18
-
19
- def cancel(id)
20
- workflow_om = Rubyhorn.client.stop(id)
21
- build_encode(get_workflow(workflow_om))
22
- end
23
-
24
- private
25
-
26
- def fetch_workflow(id)
27
- workflow_om = begin
28
- Rubyhorn.client.instance_xml(id)
29
- rescue Rubyhorn::RestClient::Exceptions::HTTPNotFound
30
- nil
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)
40
- end
41
-
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
48
- end
49
- end
50
-
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])
73
- end
74
-
75
- encode
76
- end
77
-
78
- def convert_id(workflow)
79
- workflow.attribute('id').to_s
80
- end
81
-
82
- def get_workflow_state(workflow)
83
- workflow.attribute('state').to_s
84
- end
85
-
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
96
- end
97
- end
98
-
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
107
-
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]
120
- end
121
- output.id = track.at("@id").to_s
122
-
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
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
134
- end
135
- outputs
136
- end
137
-
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
142
-
143
- def convert_errors(workflow)
144
- workflow.xpath('//errors/error/text()').map(&:to_s)
145
- end
146
-
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
151
-
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
156
-
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
162
-
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
168
-
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
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
198
-
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
252
- begin
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"
257
- end
258
- end
259
- end
260
- end
261
-
262
- class MatterhornRtmpUrl
263
- class_attribute :members
264
- self.members = %i[application prefix media_id stream_id filename extension]
265
- attr_accessor(*members)
266
- REGEX = %r{^
267
- /(?<application>.+) # application (avalon)
268
- /(?:(?<prefix>.+):)? # prefix (mp4:)
269
- (?<media_id>[^\/]+) # media_id (98285a5b-603a-4a14-acc0-20e37a3514bb)
270
- /(?<stream_id>[^\/]+) # stream_id (b3d5663d-53f1-4f7d-b7be-b52fd5ca50a3)
271
- /(?<filename>.+?) # filename (MVI_0057)
272
- (?:\.(?<extension>.+))?$ # extension (mp4)
273
- }x.freeze
274
-
275
- # @param [MatchData] match_data
276
- def initialize(match_data)
277
- self.class.members.each do |key|
278
- send("#{key}=", match_data[key])
279
- end
280
- end
281
-
282
- def self.parse(url_string)
283
- # Example input: /avalon/mp4:98285a5b-603a-4a14-acc0-20e37a3514bb/b3d5663d-53f1-4f7d-b7be-b52fd5ca50a3/MVI_0057.mp4
284
-
285
- uri = URI.parse(url_string)
286
- match_data = REGEX.match(uri.path)
287
- MatterhornRtmpUrl.new match_data
288
- end
289
-
290
- alias _binding binding
291
- def binding
292
- _binding
293
- end
294
-
295
- def to_path
296
- File.join(media_id, stream_id, "#{filename}.#{extension || prefix}")
297
- end
298
- end
299
- end
300
- end