media_processing_tool 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.
@@ -0,0 +1,23 @@
1
+ require 'filemagic/ext'
2
+ class MediaInformationGatherer
3
+ class MediaType
4
+
5
+ # @params [Hash] options Not currently used
6
+ def initialize(options = {}); end # initialize
7
+
8
+ # @params [String] file_path The path to the file to scan
9
+ # @params [Hash] options Not currently used
10
+ # @return [Hash] Will contain :type, :subtype and any other attributes output during the call
11
+ def run(file_path, options = { })
12
+ media_type, charset = (File.mime_type(file_path) || '').split(';')
13
+ type, subtype = media_type.split('/') if media_type.is_a?(String)
14
+ output = { :type => type, :subtype => subtype }
15
+
16
+ param = charset.strip.split('=') if charset.is_a?(String)
17
+ output[param.first] = param.last if param.is_a?(Array)
18
+
19
+ output
20
+ end # run
21
+
22
+ end # MediaType
23
+ end
@@ -0,0 +1,91 @@
1
+ class MediaInformationGatherer
2
+ class Mediainfo
3
+
4
+ DEFAULT_EXECUTABLE_PATH = 'mediainfo'
5
+
6
+ def self.run(file_path, options = { })
7
+ new(options).run(file_path, options)
8
+ end
9
+
10
+ # @params [Hash] options
11
+ # @options options [String] :mediainfo_cmd_path
12
+ def initialize(options = { })
13
+ @mediainfo_cmd_path = options.fetch(:mediainfo_cmd_path, DEFAULT_EXECUTABLE_PATH)
14
+ end # initialize
15
+
16
+ # @params [String] file_path
17
+ # @params [Hash] options
18
+ def run(file_path, options = { })
19
+ command_line = "#{@mediainfo_cmd_path} '#{file_path}'"
20
+ output = `#{command_line}`
21
+
22
+ fix_encoding(output)
23
+
24
+ parse_output_to_hash(output)
25
+ end # run
26
+
27
+ # @param [String] output
28
+ def fix_encoding(output)
29
+ output[/test/] # Running a regexp on the string throws error if it's not UTF-8
30
+ rescue ArgumentError
31
+ output.force_encoding('ISO-8859-1')
32
+ end
33
+
34
+ # Takes the output from media info and creates a hash consisting of hashes of each 'section type'
35
+ # Known 'section types' are: General, Video, Audio, and Menu
36
+ #
37
+ # @param [String] output
38
+ def parse_output_to_hash(output)
39
+ # Add a hash that will provide a count of sections by type
40
+ mediainfo_hash = { 'output' => output, 'section_type_counts' => { 'audio' => 0, 'video' => 0 } }
41
+
42
+ section_name = nil
43
+ section_data = { }
44
+
45
+ output.each_line { |line|
46
+ data = line.chomp.split(':', 2)
47
+ case data.length
48
+ when 0; next # Nothing parsed on this line, goto the next
49
+ when 1
50
+ # No key:value pair so it looks like we have a new section being defined
51
+
52
+ # Add the previously parsed section
53
+ append_section(mediainfo_hash, section_name, section_data) unless section_name.nil? and section_data.empty?
54
+
55
+ section_name = data[0].strip
56
+ section_data = { }
57
+ when 2
58
+ # We have a key value pair, add it to this section
59
+ section_data[data[0].strip] = data[1].strip
60
+ end
61
+ }
62
+ # Append the last section we processed
63
+ append_section(mediainfo_hash, section_name, section_data)
64
+ end
65
+
66
+ # Appends parsed data to the main hash by section_name
67
+ #
68
+ # @param [Hash] mediainfo_hash
69
+ # @param [String] section_name
70
+ # @param [Hash] section_data
71
+ def append_section(mediainfo_hash, section_name, section_data)
72
+ if mediainfo_hash.has_key? section_name
73
+ mediainfo_hash[section_name] = [ mediainfo_hash[section_name] ] unless mediainfo_hash[section_name].is_a? Array
74
+ mediainfo_hash[section_name] << section_data
75
+ else
76
+ mediainfo_hash[section_name] = section_data
77
+ end
78
+
79
+ # Determine section type by taking the first word of the section name (ex: 'Audio #1' == 'audio', 'Video' == 'video')
80
+ section_type = section_name.split.first.downcase rescue section_name
81
+
82
+ # Increment section type count for this section type
83
+ mediainfo_hash['section_type_counts'][section_type] = 0 unless mediainfo_hash['section_type_counts'].has_key? section_type
84
+ mediainfo_hash['section_type_counts'][section_type] += 1
85
+
86
+ return mediainfo_hash
87
+ end # append_section
88
+
89
+ end # MediaInfo
90
+
91
+ end
@@ -0,0 +1,108 @@
1
+ module TimecodeMethods
2
+
3
+ # @param [Integer] time_base
4
+ # @param [Boolean] ntsc
5
+ def self.convert_time_base(time_base, ntsc)
6
+ fps = case time_base.to_f
7
+ when 24; ntsc ? 23.976 : 24.0
8
+ when 30; ntsc ? 29.97 : 30.0
9
+ when 60; ntsc ? 59.94 : 60.0
10
+ else; time_base.to_f
11
+ end
12
+ #puts "Time Base: #{time_base} NTSC: #{ntsc} FPS: #{fps}"
13
+ fps
14
+ end # convert_time_base
15
+ def convert_time_base(*args); self.convert_time_base(*args) end
16
+
17
+ def self.convert_frames_time_base(frames, frame_rate_from, frame_rate_to)
18
+ return 0 unless frame_rate_from and frame_rate_from > 0 and frame_rate_to and frame_rate_to > 0
19
+ frames * (frame_rate_to / frame_rate_from)
20
+ end # convert_frames_time_base
21
+ def convert_frames_time_base(*args); self.convert_frames_time_base(*args) end
22
+
23
+ def self.timecode_to_frames(timecode, fps = 25.0, drop_frame = false)
24
+ return 0 unless timecode and fps and fps > 0
25
+ hours, minutes, seconds, frames = timecode.split(':')
26
+ frames = frames.to_i
27
+ frames += seconds.to_i * fps
28
+ frames += (minutes.to_i * 60) * fps
29
+ frames += (hours.to_i * 3600) * fps
30
+
31
+ frames
32
+ end # timecode_to_frames
33
+ def timecode_to_frames(*args); self.timecode_to_frames(*args) end
34
+
35
+ def self.frames_to_timecode(frames, frame_rate = 25.0, drop_frame = false, drop_code_separator = ';')
36
+ return '00:00:00:00' unless frames and frames > 0 and frame_rate and frame_rate > 0
37
+ return frames_to_drop_frame_timecode(frames, frame_rate, drop_code_separator) if drop_frame
38
+ fps = frame_rate.to_f
39
+ seconds = frames.to_f / fps
40
+ remaining_frames = frames % fps
41
+
42
+ hours = seconds / 3600
43
+ seconds %= 3600
44
+
45
+ minutes = seconds / 60
46
+ seconds %= 60
47
+
48
+ sprintf('%02d:%02d:%02d:%02d', hours, minutes, seconds, remaining_frames)
49
+ end # frames_to_timecode
50
+ def frames_to_timecode(*args); self.frames_to_timecode(*args) end
51
+
52
+ def self.frames_to_drop_frame_timecode(frames, frame_rate, frame_separator = ';')
53
+ # FIXME FAILS TESTS
54
+
55
+ #?> frames_to_drop_frame_timecode(5395, 29.97)
56
+ #=> "00:02:59;29"
57
+ #frames_to_drop_frame_timecode(5396, 29.97)
58
+ #=> "00:03:00;00"
59
+ #?> frames_to_drop_frame_timecode(5397, 29.97)
60
+ #=> "00:03:00;01"
61
+
62
+
63
+ #?> frames_to_drop_frame_timecode(1800, 29.97)
64
+ #=> "00:01:00;02"
65
+
66
+ #?> frames_to_drop_frame_timecode(3600, 29.97)
67
+ #=> "00:02:00;04"
68
+
69
+ #?> frames_to_drop_frame_timecode(5400, 29.97)
70
+ #=> "00:03:00;06"
71
+
72
+ # when frames equals 1800 then timecode should be '00:01:00:00'
73
+ # when frames equals 3600 then timecode should be '00:02:02:00'
74
+ # when frames equals 5400 then timecode should be '00:03:02:00'
75
+ # when frames equals 18000 then timecode should be 00:10:00:00'
76
+ frame_rate = frame_rate.round(0)
77
+ frames = frames.to_i
78
+
79
+ skipped_frames = frames / (frame_rate * 60)
80
+ skipped_frames *= 2
81
+ added_frames = frames / (frame_rate * 600) #60 * 10
82
+ added_frames *= 2
83
+
84
+ frames += skipped_frames
85
+ frames -= added_frames
86
+
87
+ sec_frames = frame_rate
88
+ min_frames = 60 * sec_frames
89
+ hour_frames = 60 * min_frames
90
+
91
+ hour = frames / hour_frames
92
+ frames %= hour_frames
93
+
94
+ min = frames / min_frames
95
+ frames %= min_frames
96
+
97
+ sec = frames / sec_frames
98
+ frames %= sec_frames
99
+
100
+ # mystery off by 2 error
101
+ frames += 2 if hour > 2
102
+
103
+ drop = frames
104
+ return sprintf('%02d:%02d:%02d%s%02d', hour, min, sec, frame_separator, drop)
105
+ end # frames_to_drop_frame_timecode
106
+ def frames_to_drop_frame_timecode(*args); self.frames_to_drop_frame_timecode(*args) end
107
+
108
+ end # Timecode
@@ -0,0 +1,710 @@
1
+ $:.unshift(File.expand_path('../'))
2
+ require 'logger'
3
+ require 'open3'
4
+ require 'pp'
5
+ require 'shellwords'
6
+ #require 'udam_utils'
7
+
8
+
9
+ module UDAMUtils
10
+
11
+ class BasePublishMapProcessor
12
+
13
+ class << self
14
+ def publish_map=(value); @publish_map = value end # publish_map=
15
+ def publish_map; @publish_map end # publish_map
16
+
17
+ def process(params = { })
18
+ #new(params).process
19
+ end # self.process
20
+
21
+ # @param [Hash] hash
22
+ # @param [Symbol|String|Array<Symbol, String>] keys
23
+ # @param [Hash] options
24
+ # @option options [Any] :default The default value to return if none of the keys are found
25
+ # @option options [Boolean] :search_keys_as_string
26
+ def search_hash(hash, keys, options = { })
27
+ value = options[:default]
28
+ search_keys_as_string = options[:search_keys_as_string]
29
+ [*keys].each do |key|
30
+ value = hash[key] and break if hash.has_key?(key)
31
+ if search_keys_as_string
32
+ key = key.to_s
33
+ value = hash[key] and break if hash.has_key?(key)
34
+ end
35
+ end
36
+ value
37
+ end # search_keys
38
+
39
+ end # self
40
+
41
+ attr_accessor :logger
42
+
43
+ def initialize(params = {})
44
+ @logger = params[:logger] || Logger.new(params[:log_to] || STDOUT)
45
+ load_configuration_from_file(params)
46
+
47
+ @publish_maps ||= @publish_map || params[:publish_maps] || params[:publish_map]
48
+ raise RuntimeError, "Missing or Empty @publish_maps.\n Check your configuration in #{options[:config_file_path]}" unless (@publish_maps.is_a?(Array) and !@publish_maps.empty?)
49
+ end # initialize
50
+
51
+ # @param [Hash] params
52
+ # @option params [String] :config_file_path
53
+ def load_configuration_from_file(params = { })
54
+ params = params.dup
55
+
56
+ case params
57
+ when String
58
+ config_file_path = params
59
+ params = { }
60
+ when Hash
61
+ config_file_path = params[:config_file_path] || params[:configuration_file_path]
62
+ when Array
63
+ case params.first
64
+ when String
65
+ config_file_path = params.shift
66
+ params = params.first.is_a?(Hash) ? params.first : { }
67
+ when Hash; params = params.shift
68
+ else params = { }
69
+ end
70
+ end # case params
71
+ return false unless config_file_path
72
+ #raise ArgumentError, 'config_file_path is a required argument.' unless config_file_path
73
+
74
+ raise "Configuration File Not Found. '#{config_file_path}'" unless File.exists?(config_file_path)
75
+ logger.debug { "Loading Configuration From File. #{config_file_path}"}
76
+
77
+ #require config_file_path
78
+ eval(File.open(config_file_path, 'r').read)
79
+ end # load_configuration_from_file
80
+
81
+ # @param [Hash] hash
82
+ # @param [Symbol|String|Array<Symbol, String>] keys
83
+ # @param [Hash] options
84
+ # @option options [Any] :default The default value to return if none of the keys are found
85
+ # @option options [Boolean] :search_keys_as_string
86
+ def search_hash(hash, keys, options = { })
87
+ value = options[:default]
88
+ search_keys_as_string = options[:search_keys_as_string]
89
+ [*keys].each do |key|
90
+ value = hash[key] and break if hash.has_key?(key)
91
+ if search_keys_as_string
92
+ key = key.to_s
93
+ value = hash[key] and break if hash.has_key?(key)
94
+ end
95
+ end
96
+ value
97
+ end # search_keys
98
+
99
+ end # BasePublishMapProcessor
100
+
101
+
102
+ class WorkflowPublishMapProcessor < BasePublishMapProcessor
103
+
104
+ attr_accessor :event
105
+
106
+ # @param [Hash] params
107
+ # @option params [Object] :logger
108
+ # @option params [String] :uu_executable ([UDAMUtils.bin_dir]/uu)
109
+ # @option params [Boolean] :confirm_filtered_vents NOT CURRENTLY USED
110
+ # @option params [Hash] :publish_map
111
+ # @option params [Symbol, String] :event_id_field_name (:id)
112
+ # @option params [Symbol, String] :event_type_field_name (:type)
113
+ # @option params [Symbol, String] :entity_path_field_name (:path)
114
+ def initialize(params = { })
115
+ super(params)
116
+
117
+ logger.debug { "Initializing Workflow Publish Map Processor. #{params}" }
118
+
119
+ options = params
120
+ @uu_bin_dir = '/usr/bin' #|| UDAMUtils.get_bin_dir
121
+
122
+ @udam_utils_exec = options[:uu_executable] ||= File.join(@uu_bin_dir, 'uu')
123
+ raise "UDAM Utils Executable Not Found. File Not Found: '#{@udam_utils_exec}'" unless File.exist?(@udam_utils_exec)
124
+ logger.debug { "UDAM Utils Executable: #{@udam_utils_exec}" }
125
+
126
+ #@get_events_exec = options[:get_events_exec] ||= File.join(mre_bin_dir, 'get_events_with_metadata.rb')
127
+ #raise "Get Events Executable Not Found. File Not Found: '#{@get_events_exec}'" unless File.exist?(@get_events_exec)
128
+ #@logger.debug { "Get Events Exec: #{@get_events_exec}" }
129
+
130
+ #@confirm_event_exec = options[:confirm_event_exec] ||= File.join(mre_bin_dir, 'confirm_event.rb')
131
+ #raise "Confirm Events Executable Not Found. File Not Found: '#{@confirm_event_exec}'" unless File.exist?(@confirm_event_exec)
132
+ #@logger.debug { "Confirm Event Exec: #{@confirm_event_exec}" }
133
+
134
+ # Determines if events that are set to not be published still get confirmed by default
135
+ @confirm_filtered_events ||= params[:confirm_filtered_events] ||= false
136
+ logger.debug { "Confirm Filtered Events: #{@confirm_filtered_events}" }
137
+
138
+ @event_id_field_name ||= params[:event_id_field_name] || :id
139
+ @event_id_field_name = @event_id_field_name.to_sym if @event_id_field_name.is_a?(String)
140
+
141
+ @event_type_field_name ||= params[:event_type_field_name] || :type
142
+ @event_type_field_name = @event_type_field_name.to_sym if @event_type_field_name.is_a?(String)
143
+
144
+ @entity_field_name ||= params[:entity_field_name] || :entity
145
+ @entity_field_name = @entity_field_name.to_sym if @entity_field_name.is_a?(String)
146
+
147
+ @entity_path_field_name ||= params[:entity_path_field_name] || :path
148
+ @entity_path_field_name = @entity_path_field_name.to_sym if @entity_path_field_name.is_a?(String)
149
+ end # initialize
150
+
151
+ # @param [Hash] parameters
152
+ # @param [Hash] event
153
+ # @return [Hash]
154
+ def eval_workflow_parameters(parameters, event = @event)
155
+ workflow_parameter_values = { }
156
+ parameters.each { |pname, param|
157
+ logger.debug { "Processing Workflow Parameter: #{pname} -> #{param}" }
158
+ case param
159
+ when String
160
+ pv = param
161
+ eval_pv = true
162
+ when Hash
163
+ pv = param.fetch(:value, nil)
164
+ eval_pv = param.fetch(:eval, false)
165
+ else
166
+ pv = nil
167
+ eval_pv = false
168
+ end
169
+ begin
170
+ pv = eval(pv) if eval_pv && pv.is_a?(String)
171
+ logger.debug { "Processed Workflow Parameter: #{pname} -> #{pv}" }
172
+ workflow_parameter_values[pname] = pv
173
+ rescue => e
174
+ logger.error { "Failed to evaluate parameter. #{e.message}\nName: #{pname}\nValue: #{param}" }
175
+ end
176
+ }
177
+ workflow_parameter_values
178
+ end # eval_workflow_parameters
179
+
180
+ # @param [Hash] params
181
+ # @option params [Hash] :event
182
+ # @option params [Hash] :workflow A hash containing the :name and optionally the :parameters key for the workflow to execute
183
+ # @option params [String] :mq_connection_uri The connection URI to use when publishing the workflow.
184
+ # @return [Boolean]
185
+ def publish_to_workflow(params = { })
186
+ event = params.fetch(:event, @event)
187
+ workflow = params.fetch(:workflow, @publish_params.fetch(:workflow, nil))
188
+ mq_connection_uri = params.fetch(:mq_connection_uri, nil)
189
+
190
+ logger.debug { "Publishing To Workflow. Workflow: #{workflow}" }
191
+ unless (workflow_name = workflow.fetch(:name, false))
192
+ logger.error "No Workflow Name Specified. Event: #{event} Workflow: #{workflow}"
193
+ return false
194
+ end
195
+
196
+ workflow_parameters = workflow.fetch(:parameters, false)
197
+ workflow_parameter_values = eval_workflow_parameters(workflow_parameters, event) if workflow_parameters
198
+ workflow_parameter_values ||= { }
199
+
200
+ cmd_line = [ @udam_utils_exec, 'job', '--workflow', workflow_name, '--workflow-parameters', workflow_parameter_values.to_json]
201
+ cmd_line << '--mq-connection-uri' << mq_connection_uri if mq_connection_uri
202
+ cmd_line = cmd_line.shelljoin
203
+
204
+ logger.debug { "Publishing event using command line. #{cmd_line}" }
205
+ response = execute(cmd_line)
206
+ logger.debug { "Publish event command response: #{response}" }
207
+ response[:success]
208
+ end # publish_event_to_workflow
209
+
210
+ # @param [String] cmd_line The command line to execute
211
+ # @return [Hash] { "STDOUT" => [String], "STDERR" => [String], "STATUS" => [Object] }
212
+ def execute(cmd_line)
213
+ begin
214
+ stdout_str, stderr_str, status = Open3.capture3(cmd_line)
215
+ logger.error { "Error Executing #{cmd_line}. Stdout: #{stdout_str} Stderr: #{stderr_str}" } unless status.success?
216
+ return { :stdout => stdout_str, :stderr => stderr_str, :status => status, :success => status.success? }
217
+ rescue
218
+ logger.error { "Error Executing '#{cmd_line}'. Exception: #{$!} @ #{$@} STDOUT: '#{stdout_str}' STDERR: '#{stderr_str}' Status: #{status.inspect} " }
219
+ return { :stdout => stdout_str, :stderr => stderr_str, :status => status, :success => false }
220
+ end
221
+ end # execute
222
+
223
+ # @param [Hash] event
224
+ def parse_event(event = @event)
225
+ # Since the event came in and was converted from JSON all of the keys are strings instead of symbols
226
+
227
+ begin
228
+ @object = @event
229
+ @event_id = event[@event_id_field_name] || event[@event_id_field_name.to_s]
230
+ @event_type = event[@event_type_field_name] || event[@event_type_field_name.to_s]
231
+
232
+ entity = event.fetch(@entity_field_name, event)
233
+ @full_file_path = entity.fetch(@entity_path_field_name)
234
+
235
+ @metadata_sources = entity.fetch(:metadata_sources, { })
236
+ @exiftool = @metadata_sources[:exiftool] ||= { }
237
+ @mediainfo = @metadata_sources[:mediainfo] ||= { }
238
+ @ffmpeg = @metadata_sources[:ffmpeg] ||= { }
239
+ @filemagic = @metadata_sources[:filemagic] ||= { }
240
+ @media = @metadata_sources[:filemagic] ||= { }
241
+ @common_media_info = @metadata_sources[:common] ||= { }
242
+
243
+ #media = entity.fetch('media', { })
244
+ @media_type = @media[:type] || @media['type']
245
+ @media_subtype = @media[:subtype] || @media['subtype']
246
+ rescue => e
247
+ logger.error "Error parsing event.\n\tEvent: #{event.inspect}\n\n\tException #{e.message}\n\tBacktrace #{e.backtrace}"
248
+ raise
249
+ end
250
+ end # parse_event
251
+
252
+ # @param [Hash] event
253
+ def process_event(event = @event)
254
+ @event = event
255
+ logger.debug { "Processing Event: \n\n #{PP.pp(event, '')}" }
256
+ ignore_publish_error = false
257
+
258
+ parse_event(event) #rescue return
259
+
260
+ if match_found_in_publish_maps?
261
+
262
+ # Determines if the event is to be published
263
+ # Defaults to true so that that it doesn't have to be defined for every workflow map
264
+ map_publish_event = @publish_params.fetch(:publish_event, true)
265
+
266
+ # Determines if the event is to be confirmed
267
+ # Defaults to true so that that it doesn't have to be defined for every workflow map
268
+ map_confirm_event = @publish_params.fetch(:confirm_event, true)
269
+
270
+ # Determines if the event will still get confirmed if there is an error during publishing
271
+ ignore_publish_error = @publish_params.fetch(:ignore_publish_error, false)
272
+ else
273
+ map_publish_event = nil
274
+ map_confirm_event = nil
275
+ end
276
+
277
+ to_publish = map_publish_event
278
+ to_confirm = (map_confirm_event or @confirm_filtered_events)
279
+
280
+ if to_publish
281
+ publish_response = publish_event
282
+ publish_successful = publish_response[:success]
283
+ confirm_response = confirm_event(@event_id) if (publish_successful or ignore_publish_error) and map_confirm_event
284
+ elsif to_confirm
285
+ logger.debug { "Event is being confirmed but not published. Included Event: #{!map_publish_event.nil?} Map Publish Content: #{map_publish_event} Confirm Filtered Events: #{@confirm_filtered_events} Map Confirm Event: #{map_confirm_event} Event: #{event.inspect}"}
286
+ confirm_response = confirm_event(@event_id)
287
+ else
288
+ publish_successful = publish_response = confirm_successful = confirm_response = nil
289
+ end
290
+ confirm_successful = confirm_response[:success] if confirm_response
291
+
292
+ {
293
+ :to_publish => map_publish_event,
294
+ :to_confirm => to_confirm,
295
+ :published => (publish_successful or ignore_publish_error),
296
+ :confirmed => confirm_successful,
297
+ :publish_response => publish_response,
298
+ :confirm_response => confirm_response,
299
+ :success => (
300
+ (!to_publish or (to_publish and publish_successful)) and
301
+ (!to_confirm or (to_confirm and confirm_successful))
302
+ )
303
+ }
304
+ end # process_event
305
+
306
+ # # @param [Array(Hash)] events
307
+ # @param [Array(Hash)] events
308
+ def process_events(events)
309
+ @event = nil
310
+ events.each do |event|
311
+ @event = event
312
+ begin
313
+ process_event
314
+ rescue StandardError, ScriptError => e
315
+ logger.error "Error processing event.\n\tEvent: #{event.inspect}\n\n\tException #{e.inspect}"
316
+ end
317
+ end
318
+ end # process_events
319
+
320
+ # @param [String|Integer] event_id
321
+ # @param [Hash] event
322
+ # @param [Hash] params
323
+ # @option params [String|nil] :publish_event_exec
324
+ # @option params [Boolean|nil] (false) :eval_publish_event_exec
325
+ # @option params [String|nil] :publish_event_arguments
326
+ # @option params [Boolean|nil] (true) :eval_publish_event_exec
327
+ # @return [Boolean]
328
+ def publish_event(event = @event, params = @publish_params)
329
+
330
+ workflow = params.fetch(:workflow, false)
331
+ return publish_event_to_workflow(:workflow => workflow) if workflow
332
+
333
+ exec = params.fetch(:publish_event_exec, nil)
334
+ eval_publish_event_exec = params.fetch(:eval_publish_event_exec, false)
335
+
336
+ arguments = params.fetch(:publish_event_arguments, nil)
337
+ eval_publish_event_arguments = params.fetch(:eval_publish_event_arguments, true)
338
+
339
+ logger.debug { "Evaluating exec: #{exec}" } and exec = eval(exec) if eval_publish_event_exec and exec
340
+ logger.debug { "Evaluating arguments: #{arguments}" } and arguments = eval(arguments) if eval_publish_event_arguments and arguments
341
+ cmd_line = "#{exec} #{arguments}" if arguments
342
+ logger.debug { "Publishing event using command line. #{cmd_line}" }
343
+ response = execute(cmd_line)
344
+ logger.debug { "Publish event command response: #{response}" }
345
+ response
346
+ end # publish_event
347
+
348
+ # @param [Hash] event
349
+ # @param [Hash] workflow
350
+ # @return [Boolean]
351
+ def publish_event_to_workflow(params = { })
352
+ event = params.fetch(:event, @event)
353
+ workflow = params.fetch(:workflow, @publish_params.fetch(:workflow, nil))
354
+
355
+
356
+ logger.debug { "Publishing Event To Workflow. Workflow: #{workflow}" }
357
+ unless (workflow_name = workflow.fetch(:name, false))
358
+ logger.error "No Workflow Name Specified. Event: #{event} Workflow: #{workflow}"
359
+ return false
360
+ end
361
+
362
+ workflow_parameters = workflow.fetch(:parameters, false)
363
+ workflow_parameter_values = eval_workflow_parameters(workflow_parameters, event) if workflow_parameters
364
+ workflow_parameter_values ||= { }
365
+
366
+ cmd_line = [ @udam_utils_exec, 'job', '--workflow', workflow_name, '--workflow-parameters', workflow_parameter_values.to_json].shelljoin
367
+ logger.debug { "Publishing event using command line. #{cmd_line}" }
368
+ response = execute(cmd_line)
369
+ logger.debug { "Publish event command response: #{response}" }
370
+ response
371
+ end # publish_event_to_workflow
372
+
373
+
374
+ # @params [Hash] publish_maps
375
+ def match_found_in_publish_maps?(publish_maps = @publish_maps)
376
+ matched = false
377
+ publish_maps.each { |current_publish_map|
378
+ current_publish_map = { :type => :glob, :map => current_publish_map } if current_publish_map.is_a?(Array)
379
+
380
+ map_type = current_publish_map.fetch(:type, :unknown)
381
+ map = current_publish_map.fetch(:map, false)
382
+ logger.warn { "Mapping with no map detected. #{current_publish_map}"} and next unless map
383
+
384
+ case map_type
385
+ when :eval
386
+ matched = search_eval_publish_map(current_publish_map)
387
+ when :media_type
388
+ matched = search_media_type_publish_map(current_publish_map)
389
+ when :glob
390
+ matched = search_glob_publish_map(current_publish_map)
391
+ when :global
392
+ matched = search_global_publish_map(current_publish_map)
393
+ else
394
+ logger.warn { "Unknown map type '#{map_type}'."}
395
+ end
396
+ break if matched
397
+ }
398
+ matched
399
+ end # process_publish_maps
400
+
401
+ def init_publish_params(params_by_event_type)
402
+ return false unless params_by_event_type
403
+
404
+ @publish_params = params_by_event_type[@event_type.to_sym] || params_by_event_type[@event_type.to_s]
405
+ @publish_params ||= params_by_event_type[:any] || params_by_event_type['any']
406
+ @publish_params ||= params_by_event_type[:all] || params_by_event_type['all']
407
+ @publish_params
408
+ end
409
+
410
+ # @params [Hash] params
411
+ # @option params [Hash] :map
412
+ def search_eval_publish_map(params = { })
413
+ logger.debug { 'Starting Eval Search.' }
414
+ map = params.fetch(:map, false)
415
+ return false unless map
416
+
417
+ match_found = false
418
+ map.each { |expressions, map_params|
419
+ @logger.debug { "Testing expressions. #{expressions} #{map_params}" }
420
+ next unless init_publish_params(map_params)
421
+ [*expressions].each { |expression| logger.debug { "Matched expression: #{expression}" } and match_found = true and break if eval(expression) }
422
+ break if match_found
423
+ }
424
+ match_found
425
+ end # search_eval_publish_map
426
+
427
+ # @params [Hash] params
428
+ # @option params [Hash] :map
429
+ def search_media_type_publish_map(params = { })
430
+ logger.debug { 'Starting Media Type Search.' }
431
+ map = params.fetch(:map, false)
432
+ return false unless map
433
+
434
+ logger.warn("Asset media type is empty. #{@object}") and return false unless @media_type
435
+ logger.warn("Asset media subtype is empty. #{@object}") and return false unless @media_subtype
436
+
437
+ match_found = false
438
+ map.each { |media_types, media_subtypes_with_params|
439
+ [*media_types].each { |media_type|
440
+ next unless media_type.match(@media_type) || @media_type == '*'
441
+ media_subtypes_with_params.each { |media_subtypes, map_params|
442
+ next unless init_publish_params(map_params)
443
+ [*media_subtypes].each { |media_subtype|
444
+ logger.debug { "Matched media type: #{media_type.to_s}/#{media_subtype.to_s} -> #{@media_type}/#{@media_subtype}" } and match_found = true and break if (media_subtype.match(@media_subtype) || media_subtype == '*')
445
+ }
446
+ }
447
+ break if match_found
448
+ }
449
+ break if match_found
450
+ }
451
+ logger.debug { "Media Type Search Completed. Match Found: #{match_found}" }
452
+ match_found
453
+ end # search_media_type_publish_map
454
+
455
+ # @params [Hash] params
456
+ # @option params [Hash] :map
457
+ # @option params [Integer] :options
458
+ def search_glob_publish_map(params = { })
459
+ logger.debug { 'Starting Glob Search.' }
460
+ map = params.fetch(:map, false)
461
+ return false unless map
462
+
463
+ logger.debug { "MAP: #{map}" }
464
+
465
+ full_file_path = @full_file_path
466
+ logger.warn("Full file path is empty. #{@object}") and return false unless full_file_path
467
+
468
+ event_type = @event_type
469
+
470
+ match_found = false
471
+
472
+ default_glob_options = params.fetch(:options, 0)
473
+ default_glob_options = 0 unless default_glob_options.is_a? Integer
474
+
475
+ map.each { |patterns, map_params|
476
+ next unless init_publish_params(map_params)
477
+
478
+ if patterns.is_a?(Hash)
479
+ globs = patterns[:globs] || patterns[:glob] || { }
480
+ glob_options = patterns.fetch(:options, default_glob_options)
481
+ glob_options = 0 unless glob_options.is_a? Integer
482
+ else
483
+ globs = patterns
484
+ glob_options = default_glob_options
485
+ end
486
+
487
+ [*globs].each do |pattern|
488
+ logger.debug { "Testing #{full_file_path} against #{pattern} with options #{glob_options}" }
489
+ if File.fnmatch(pattern, full_file_path, glob_options)
490
+ logger.debug { "Matched pattern: #{full_file_path} -> #{pattern}" }
491
+ match_found = true
492
+ break
493
+ end
494
+ end
495
+ break if match_found
496
+ }
497
+ match_found
498
+ end # search_glob_publish_map
499
+
500
+ # Processes a catch all publish map
501
+ #
502
+ # { type: :global, map: { anything: all: { publish_event: false, confirm_event: false } } }
503
+ # Where :anything is ignored and :all can be anyone of :all, :created, :modified, :deleted
504
+ #
505
+ # @params [Hash] params
506
+ # @option params [Hash] :map
507
+ def search_global_publish_map(params = { })
508
+ logger.debug { 'Starting Global Search.' }
509
+ return false unless (map = params[:map])
510
+
511
+ match_found = false
512
+ map.each { |ignored, params_by_event_type|
513
+ next unless init_publish_params(params_by_event_type)
514
+ match_found = true
515
+ break
516
+ }
517
+ match_found
518
+ end # search_global_publish_map
519
+
520
+ def confirm_event(*args)
521
+ { :stdout => '', :stderr => '', :status => '', :success => true }
522
+ end # confirm_event
523
+
524
+ end # WorkflowPublishMapProcessor
525
+
526
+ class EventBasedPublishMapProcessor < WorkflowPublishMapProcessor
527
+
528
+ end # EventBasedPublishMapProcessor
529
+
530
+ class GenericPublishMapProcessor < WorkflowPublishMapProcessor
531
+
532
+ attr_accessor :object
533
+
534
+ def initialize(params = {})
535
+ super(params)
536
+ @full_file_path_field_name = params[:file_path_field_name]
537
+ end # initialize
538
+
539
+
540
+ def init_publish_params(params)
541
+ # We don't have events so the params are the params, there is not an 'event type' as a key in between
542
+ @publish_params = params
543
+ @publish_params
544
+ end # init_publish_params
545
+
546
+ def parse_object(object = @object, params = { })
547
+ logger.debug { "Parsing Object: #{PP.pp(object, '')}" }
548
+ begin
549
+ @full_file_path = object[@full_file_path_field_name]
550
+
551
+ @metadata_sources = object.fetch(:metadata_sources, { })
552
+ @exiftool = @metadata_sources[:exiftool] ||= { }
553
+ @mediainfo = @metadata_sources[:mediainfo] ||= { }
554
+ @ffmpeg = @metadata_sources[:ffmpeg] ||= { }
555
+ @filemagic = @metadata_sources[:filemagic] ||= { }
556
+ @media = @metadata_sources[:filemagic] ||= { }
557
+ @common_media_info = @metadata_sources[:common] ||= { }
558
+
559
+ #media = entity.fetch('media', { })
560
+ @media_type = @media[:type] || @media['type']
561
+ @media_subtype = @media[:subtype] || @media['subtype']
562
+ rescue => e
563
+ logger.error "Error parsing object.\n\tObject: #{object.inspect}\n\n\tException #{e.message}\n\tBacktrace #{e.backtrace}"
564
+ raise
565
+ end
566
+ end # parse_event
567
+
568
+
569
+ # @param [Array<Hash>] objects
570
+ def process_objects(objects, params = {})
571
+ results = [ ]
572
+ [*objects].each do |_object|
573
+ begin
574
+ results << process_object(params.merge(:object => _object))
575
+ rescue StandardError, ScriptError => e
576
+ logger.error "Error processing event.\n\tObject: #{_object.inspect}\n\n\tException #{e.inspect}"
577
+ results << { :success => false, :error => { :message => e.message }, :exception => { :message => e.message, :backtrace => e.backtrace }, :object => _object }
578
+ end
579
+ end
580
+ results
581
+ end # process_events
582
+
583
+ def process_object(params = { })
584
+ _object = params.has_key?(:object) ? params[:object] : nil
585
+ return _object.map { |o| process_object(params.merge(:object => o)) } if _object.is_a?(Array)
586
+ @object = _object
587
+
588
+ logger.debug { "Processing Object: \n\n #{PP.pp(object, '')}" }
589
+ parse_object
590
+ ignore_publish_error = false
591
+
592
+ #parse_event(event) #rescue return
593
+
594
+ if match_found_in_publish_maps?
595
+
596
+ # Determines if the event is to be published
597
+ # Defaults to true so that that it doesn't have to be defined for every workflow map
598
+ map_publish = @publish_params.fetch(:publish, true)
599
+
600
+ # Determines if the event is to be confirmed
601
+ # Defaults to true so that that it doesn't have to be defined for every workflow map
602
+ map_confirm = @publish_params.fetch(:confirm, true)
603
+
604
+ # Determines if the event will still get confirmed if there is an error during publishing
605
+ ignore_publish_error = @publish_params.fetch(:ignore_publish_error, false)
606
+ else
607
+ map_publish = nil
608
+ map_confirm = nil
609
+ end
610
+
611
+ to_publish = map_publish
612
+ to_confirm = (map_confirm or @confirm_filtered_objects)
613
+
614
+ if to_publish
615
+ publish_response = publish
616
+ publish_successful = publish_response[:success]
617
+ confirm_response = confirm(@object_id) if (publish_successful or ignore_publish_error) and map_confirm
618
+ elsif to_confirm
619
+ logger.debug { "Confirming but not published. Map Publish Content: #{map_publish} Confirm Filtered: #{@confirm_filtered_events} Map Confirm Event: #{map_confirm} Object: #{object.inspect}"}
620
+ confirm_response = confirm(@object_id)
621
+ else
622
+ publish_successful = publish_response = confirm_successful = confirm_response = nil
623
+ end
624
+ confirm_successful = confirm_response[:success] if confirm_response
625
+
626
+ {
627
+ :to_publish => to_publish,
628
+ :to_confirm => to_confirm,
629
+ :published => (publish_successful or ignore_publish_error),
630
+ :confirmed => confirm_successful,
631
+ :publish_response => publish_response,
632
+ :confirm_response => confirm_response,
633
+ :success => (
634
+ (!to_publish or (to_publish and publish_successful)) and
635
+ (!to_confirm or (to_confirm and confirm_successful))
636
+ )
637
+ }
638
+ rescue StandardError, ScriptError => e
639
+ logger.error "Error processing object.\n\tObject: #{object.inspect}\n\n\tException #{e.inspect}"
640
+ { :success => false, :error => { :message => e.message }, :exception => { :message => e.message, :backtrace => e.backtrace }, :object => object }
641
+ end # process_object
642
+
643
+ # @param [Hash] params
644
+ # @option params [Hash] :object
645
+ # @option params [Hash] :workflow
646
+ # @return [Boolean]
647
+ def publish_to_workflow(params = { })
648
+ object = params.fetch(:object, @object)
649
+ workflow = params[:workflow]
650
+ workflow ||= @publish_params[:workflow] if @publish_params.is_a?(Hash)
651
+
652
+ logger.debug { "Publishing To Workflow. Workflow: #{workflow}" }
653
+ unless (workflow_name = workflow.fetch(:name, false))
654
+ logger.error "No Workflow Name Specified. Object: #{object} Workflow: #{workflow}"
655
+ return false
656
+ end
657
+
658
+ workflow_parameters = workflow.fetch(:parameters, false)
659
+ workflow_parameter_values = eval_workflow_parameters(workflow_parameters, object) if workflow_parameters
660
+ workflow_parameter_values ||= { }
661
+
662
+ cmd_line = [ @udam_utils_exec, 'job', '--workflow', workflow_name, '--workflow-parameters', workflow_parameter_values.to_json].shelljoin
663
+ logger.debug { "Publishing event using command line. #{cmd_line}" }
664
+ response = execute(cmd_line)
665
+ logger.debug { "Publish event command response: #{response}" }
666
+ response
667
+ end # publish_to_workflow
668
+
669
+ # @param [String|Integer] event_id
670
+ # @param [Hash] object
671
+ # @param [Hash] params
672
+ # @option params [String|nil] :publish_event_exec
673
+ # @option params [Boolean|nil] (false) :eval_publish_event_exec
674
+ # @option params [String|nil] :publish_event_arguments
675
+ # @option params [Boolean|nil] (true) :eval_publish_event_exec
676
+ # @return [Boolean]
677
+ def publish(object = @object, params = @publish_params)
678
+
679
+ workflow = params.fetch(:workflow, false)
680
+ return publish_to_workflow(:workflow => workflow) if workflow
681
+
682
+ exec = search_hash(params, [:publish_executable, :publish_exec, :publish_event_exec])
683
+ eval_publish_exec = search_hash(params, [:eval_publish_executable, :eval_publish_exec, :eval_publish_event_exec])
684
+
685
+ arguments = search_hash(params, [:publish_arguments, :publish_event_arguments])
686
+ eval_publish_arguments = search_hash(params, [:eval_publish_arguments, :eval_publish_event_arguments], :default => true)
687
+
688
+ logger.debug { "Evaluating exec: #{exec}" } and (exec = eval(exec)) if eval_publish_exec and exec
689
+ logger.debug { "Evaluating arguments: #{arguments}" } and (arguments = eval(arguments)) if eval_publish_arguments and arguments
690
+
691
+ if exec
692
+ cmd_line = arguments ? "#{exec} #{arguments}" : exec
693
+ else
694
+ cmd_line = arguments
695
+ end
696
+
697
+ logger.debug { "Publishing using command line. #{cmd_line}" }
698
+ response = execute(cmd_line)
699
+ logger.debug { "Publish command response: #{response}" }
700
+ response
701
+ end # publish
702
+
703
+ def confirm(params = {})
704
+
705
+ end # confirm
706
+
707
+ end # GenericPublishMapProcessor
708
+
709
+ end # UDAMUtils
710
+