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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 297b7fe61e129519bdfe33fb90bf29d7b199ab64
4
+ data.tar.gz: 6127946e2a8d8865ca4846d183376e8d3fbecf68
5
+ SHA512:
6
+ metadata.gz: 0f8d9ce57c58f76c19f2336866da0be2f20ba887bc9b1edb1811857fa6aa223169cc5457eb7b5bb30c6f8bfaaaf67cf365299d4e05d2b95d67e4a9dda6c0420b
7
+ data.tar.gz: 96a77a2e6729efec6cd3dfafaa24b0e3a9313543c78ab967fbfa45bd1bf870a3274392f20d8017376bfc198896322bdb9b40f4496b7ce0f79205707bdaa4558e
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ bin/dev/
6
+ bin/*_option
7
+ coverage
8
+ dev
9
+ InstalledFiles
10
+ lib/bundler/man
11
+ pkg
12
+ rdoc
13
+ spec/reports
14
+ test/tmp
15
+ test/version_tmp
16
+ tmp
17
+
18
+ # YARD artifacts
19
+ .yardoc
20
+ _yardoc
21
+ doc/
data/README.md ADDED
@@ -0,0 +1,2 @@
1
+ media_processing_tool
2
+ =====================
data/bin/catalog ADDED
@@ -0,0 +1,181 @@
1
+ #!/usr/bin/env ruby
2
+ # A utility to create a CSV of the file properties.
3
+ #
4
+ # If process-xml-files is true only the xml files then the file paths inside of supported xml files will be parsed and the files will be
5
+ # analyzed and added to the output
6
+ #
7
+ lib_path = File.expand_path('../../lib', __FILE__)
8
+ $:.unshift(lib_path) if !$:.include?(lib_path) and File.exists?(lib_path)
9
+
10
+ require 'cgi'
11
+ require 'csv'
12
+ require 'json'
13
+ require 'logger'
14
+ require 'open3'
15
+ require 'optparse'
16
+ require 'shellwords'
17
+ require 'time'
18
+ require 'uri'
19
+
20
+ require 'mig/modules/ffmpeg'
21
+
22
+ DEFAULT_MEDIA_FILE_PATH_FIELD_NAME = 'Media File Path'
23
+
24
+ module Find
25
+
26
+ #
27
+ # Calls the associated block with the name of every file and directory listed
28
+ # as arguments, then recursively on their subdirectories, and so on.
29
+ #
30
+ # See the +Find+ module documentation for an example.
31
+ #
32
+ def find(*paths) # :yield: path
33
+ block_given? or return enum_for(__method__, *paths)
34
+
35
+ paths.collect!{|d| raise Errno::ENOENT unless File.exist?(d); d.dup}
36
+ while file = paths.shift
37
+ catch(:prune) do
38
+ begin
39
+ s = File.lstat(file)
40
+ rescue Errno::ENOENT, Errno::EACCES, Errno::ENOTDIR, Errno::ELOOP, Errno::ENAMETOOLONG
41
+ next
42
+ end
43
+ if s.directory? then
44
+ begin
45
+ fs = Dir.entries(file)
46
+ rescue Errno::ENOENT, Errno::EACCES, Errno::ENOTDIR, Errno::ELOOP, Errno::ENAMETOOLONG
47
+ next
48
+ end
49
+ fs.sort!
50
+ fs.reverse_each {|f|
51
+ next if f == "." or f == ".."
52
+ f = File.join(file, f)
53
+ paths.unshift f.untaint
54
+ }
55
+ else
56
+ yield file.dup.taint, s
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ #
63
+ # Skips the current file or directory, restarting the loop with the next
64
+ # entry. If the current file is a directory, that directory will not be
65
+ # recursively entered. Meaningful only within the block associated with
66
+ # Find::find.
67
+ #
68
+ # See the +Find+ module documentation for an example.
69
+ #
70
+ def prune
71
+ throw :prune
72
+ end
73
+
74
+ module_function :find, :prune
75
+ end
76
+
77
+ File::Stat.class_eval do
78
+
79
+ def to_hash
80
+ (self.methods - Object.methods - [__callee__]).each_with_object({}) { |meth, acc| acc[meth] = self.send(meth) if self.method(meth).arity == 0 }
81
+ end
82
+
83
+ end
84
+
85
+ options = { }
86
+ options_file_name = nil
87
+ op = OptionParser.new
88
+ op.on('--path PATH', 'The path to catalog') { |v| options[:path_to_catalog] = v }
89
+ op.on('--[no-]recursive', 'Determines if The directory tree will be recursive or not. If not then path can be a glob pattern.') { |v| options[:recursive] = v }
90
+ op.on('--[no-]process-xml-files', 'Media File Paths will be pulled from FCP xml files found using the path.') { |v| options[:process_xml_files] = v }
91
+ op.on('--destination-path PATH', 'The path and file name of the csv to create.') { |v| options[:destination_path] = v }
92
+ op.on('--help', 'Display this message.') { puts op; exit }
93
+ op.parse!(command_line_options = ARGV.dup)
94
+ op.parse!(ARGV.dup) if op.load(options_file_name)
95
+
96
+ @logger = Logger.new(STDOUT)
97
+ def logger; @logger end
98
+
99
+ @ffmpeg = FFMPEG.new
100
+ path_to_catalog = options[:path_to_catalog]
101
+ recursive = options[:recursive]
102
+ process_xml_files = options[:process_xml_files]
103
+ destination_path = options[:destination_path]
104
+ media_file_path_field_name = options[:media_file_path_field_name] || DEFAULT_MEDIA_FILE_PATH_FIELD_NAME
105
+
106
+ path_to_catalog ||= '/assets/**/*.mov'
107
+ destination_path ||= '/tmp/assets.csv'
108
+
109
+
110
+ def unescape_fcp_file_path_url(file_path_url)
111
+ URI(file_path_url).path.gsub(/(%(?:[2-9]|[A-F])(?:\d|[A-F]))/) { |v| CGI.unescape(v) }
112
+ end
113
+
114
+ def process_media_file_paths(media_file_paths, options = { })
115
+ xml_file_path = options[:xml_file_path]
116
+ xml_file_path_field_name = options[:xml_file_path_field_name] || 'XML File Path'
117
+
118
+ media_file_paths = media_file_paths.uniq
119
+ media_file_path_data = { }
120
+ media_file_path_count = media_file_paths.length
121
+ media_file_path_counter = 0
122
+ media_file_paths.each do |mfp|
123
+ media_file_path_counter += 1
124
+ logger.debug { "Processing Media File Path #{media_file_path_counter} of #{media_file_path_count}. '#{mfp}'" }
125
+ next unless File.exists?(mfp)
126
+ md = @ffmpeg.run(mfp)
127
+ md[xml_file_path_field_name] = xml_file_path if xml_file_path
128
+ md = md.delete_if { |k, v| %w(output command).include? k }
129
+ md.merge!(File.stat(mfp).to_hash)
130
+ media_file_path_data[mfp] = md
131
+ end
132
+ media_file_path_data
133
+ end
134
+
135
+ def get_media_file_paths_from_xml(xml_file_path)
136
+ _media_file_paths = [ ]
137
+ File.open(xml_file_path).read().scan(/<pathurl>(.*)<\/pathurl>/) { |file_path_url_ary| _media_file_paths << unescape_fcp_file_path_url(file_path_url_ary.first) }
138
+ _media_file_paths
139
+ end
140
+
141
+ def get_media_file_paths_from_xml_files(path_to_catalog)
142
+ media_file_paths = [ ]
143
+ media_file_paths_by_xml_file = { }
144
+ xml_file_paths = Dir.glob(path_to_catalog)
145
+ xml_file_path_count = xml_file_paths.length
146
+ xml_file_path_counter = 0
147
+ xml_file_paths.each do |xml_file_path|
148
+ xml_file_path_counter += 1
149
+ logger.debug { "Processing XML File #{xml_file_path_counter} of #{xml_file_path_count}. #{xml_file_path}" }
150
+ media_file_paths_from_xml = get_media_file_paths_from_xml(xml_file_path)
151
+ media_file_paths_by_xml_file[xml_file_path] = media_file_paths_from_xml
152
+ media_file_paths = media_file_paths + media_file_paths_from_xml
153
+ end
154
+ media_file_paths
155
+ end
156
+
157
+ def data_to_table(media_file_paths_with_data)
158
+
159
+ end
160
+
161
+ if process_xml_files
162
+ media_file_paths = get_media_file_paths_from_xml_files(path_to_catalog)
163
+ else
164
+ media_file_paths = recursive ? Find.find(path_to_catalog) : Dir.glob(path_to_catalog)
165
+ end
166
+
167
+ media_file_paths_with_data = process_media_file_paths(media_file_paths)
168
+
169
+
170
+ fields = { media_file_path_field_name => '' }
171
+ media_file_paths_with_data.each { |_, data| fields.merge!(data) }
172
+ empty_record = Hash[fields.map { |k,_| [k, ''] }]
173
+
174
+ CSV.open(destination_path, 'w') do |csv|
175
+ csv << fields.keys
176
+ media_file_paths_with_data.each do |media_file_path, data|
177
+ data[media_file_path_field_name] = media_file_path
178
+ csv << empty_record.merge(data).values
179
+ end
180
+ end
181
+ puts "Output written to: #{destination_path}"
@@ -0,0 +1,187 @@
1
+ #!/usr/bin/env ruby
2
+ # A utility to create a CSV of the file properties.
3
+ #
4
+ # If process-xml-files is true only the xml files then the file paths inside of supported xml files will be parsed and the files will be
5
+ # analyzed and added to the output
6
+ #
7
+ lib_path = File.expand_path('../../lib', __FILE__)
8
+ $:.unshift(lib_path) if !$:.include?(lib_path) and File.exists?(lib_path)
9
+ require 'rubygems'
10
+ require 'cgi'
11
+ require 'csv'
12
+ require 'find'
13
+ require 'json'
14
+ require 'logger'
15
+ require 'mig'
16
+ require 'open3'
17
+ require 'open3_backport' if RUBY_VERSION.start_with?('1.8.')
18
+ require 'optparse'
19
+ require 'pp'
20
+ require 'shellwords'
21
+ require 'time'
22
+ require 'uri'
23
+
24
+ #require 'mig/modules/ffmpeg'
25
+ module Find
26
+
27
+ #
28
+ # Calls the associated block with the name of every file and directory listed
29
+ # as arguments, then recursively on their subdirectories, and so on.
30
+ #
31
+ # See the +Find+ module documentation for an example.
32
+ #
33
+ def find(*paths) # :yield: path
34
+ block_given? or return enum_for(__method__, *paths)
35
+
36
+ paths.collect!{|d| raise Errno::ENOENT unless File.exist?(d); d.dup}
37
+ while file = paths.shift
38
+ catch(:prune) do
39
+
40
+ begin
41
+ s = File.lstat(file)
42
+ rescue Errno::ENOENT, Errno::EACCES, Errno::ENOTDIR, Errno::ELOOP, Errno::ENAMETOOLONG
43
+ next
44
+ end
45
+
46
+ yield file.dup.taint, s
47
+
48
+ if s.directory? then
49
+ begin
50
+ fs = Dir.entries(file)
51
+ rescue Errno::ENOENT, Errno::EACCES, Errno::ENOTDIR, Errno::ELOOP, Errno::ENAMETOOLONG
52
+ next
53
+ end
54
+ fs.sort!
55
+ fs.reverse_each {|f|
56
+ next if f == "." or f == ".."
57
+ f = File.join(file, f)
58
+ paths.unshift f.untaint
59
+ }
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ #
66
+ # Skips the current file or directory, restarting the loop with the next
67
+ # entry. If the current file is a directory, that directory will not be
68
+ # recursively entered. Meaningful only within the block associated with
69
+ # Find::find.
70
+ #
71
+ # See the +Find+ module documentation for an example.
72
+ #
73
+ def prune
74
+ throw :prune
75
+ end
76
+
77
+ module_function :find, :prune
78
+ end
79
+
80
+ DEFAULT_MEDIA_FILE_PATH_FIELD_NAME = 'Media File Path'
81
+
82
+ options = { }
83
+ options_file_name = nil
84
+ op = OptionParser.new
85
+ op.on('--path PATH', 'The path to catalog') { |v| options[:path_to_catalog] = v }
86
+ op.on('--[no-]recursive', 'Determines if The directory tree will be recursive or not. If not then path can be a glob pattern.') { |v| options[:recursive] = v }
87
+ op.on('--destination-path PATH', 'The path and file name of the csv to create.') { |v| options[:destination_path] = v }
88
+ op.on('--help', 'Display this message.') { puts op; exit }
89
+ op.parse!(ARGV.dup)
90
+ op.parse!(ARGV.dup) if op.load(options_file_name)
91
+
92
+ @logger = Logger.new(STDOUT)
93
+ def logger; @logger end
94
+
95
+ @mig = MediaInformationGatherer.new(options)
96
+ def mig; @mig end
97
+
98
+ @path_to_catalog = options[:path_to_catalog]
99
+ def path_to_catalog; @path_to_catalog end
100
+
101
+ @recursive = options[:recursive]
102
+ def recursive; @recursive end
103
+
104
+ @destination_path = options[:destination_path]
105
+ def destination_path; @destination_path end
106
+
107
+ @media_file_path_field_name = options[:media_file_path_field_name] || DEFAULT_MEDIA_FILE_PATH_FIELD_NAME
108
+ def media_file_path_field_name; @media_file_path_field_name end
109
+
110
+ # @path_to_catalog ||= '/assets/**/*.mov'
111
+ # @destination_path ||= '/tmp/assets.csv'
112
+
113
+ abort("Path is a required argument.\n#{op}") unless path_to_catalog
114
+ abort("Destination Path is a required argument.\n#{op}") unless destination_path
115
+
116
+ def unescape_fcp_file_path_url(file_path_url)
117
+ URI(file_path_url).path.gsub(/(%(?:[2-9]|[A-F])(?:\d|[A-F]))/) { |v| CGI.unescape(v) }
118
+ end
119
+
120
+ def process_media_file_path(media_file_path)
121
+ mds = mig.run(media_file_path)
122
+ md = mds[:common]
123
+ md
124
+ end
125
+
126
+ def process_media_file_paths(media_file_paths, options = { })
127
+ xml_file_path = options[:xml_file_path]
128
+ xml_file_path_field_name = options[:xml_file_path_field_name] || 'XML File Path'
129
+
130
+ media_file_paths = media_file_paths.uniq
131
+ media_file_path_data = { }
132
+ media_file_path_count = media_file_paths.length
133
+ media_file_path_counter = 0
134
+ attempt_counter = 0
135
+ media_file_paths.each do |mfp|
136
+ attempt_counter += 1
137
+ media_file_path_counter += 1
138
+ logger.debug { "Processing Media File Path #{media_file_path_counter} of #{media_file_path_count}. '#{mfp}'" }
139
+ #next unless File.exists?(mfp)
140
+ #md = @ffmpeg.run(mfp)
141
+ md = process_media_file_path(mfp)
142
+ unless (mfp == md[:file_path])
143
+ warn("File Path Mismatch A1. '#{mfp}' != '#{md[:file_path]}'")
144
+ media_file_path_counter -= 1
145
+ attempt_counter < 4 ? redo : warn("Failing After #{attempt_counter}")
146
+ end
147
+ md[xml_file_path_field_name] = xml_file_path if xml_file_path
148
+ media_file_path_data[mfp] = md
149
+ # unless (mfp == md[:file_path])
150
+ # warn("File Path Mismatch A2. '#{mfp}' != '#{md[:file_path]}'")
151
+ # media_file_path_counter -= 1
152
+ # redo
153
+ # end
154
+ attempt_counter = 0
155
+ end
156
+
157
+ media_file_path_data
158
+ end
159
+
160
+ def data_to_table(media_file_paths_with_data)
161
+
162
+ end
163
+
164
+ if recursive then
165
+ media_file_paths = [ ]
166
+ Find.find(path_to_catalog) { |fp, stat| media_file_paths << fp if stat.file?; puts fp }
167
+ else
168
+ media_file_paths = Dir.glob(path_to_catalog)
169
+ end
170
+ # require 'pp'
171
+ # abort("MFP Size: #{media_file_paths.length} #{PP.pp(media_file_paths, '')}")
172
+ media_file_paths_with_data = process_media_file_paths(media_file_paths)
173
+
174
+
175
+ fields = { media_file_path_field_name => '' }
176
+ media_file_paths_with_data.each { |_, data| fields.merge!(data) }
177
+ empty_record = Hash[fields.map { |k,_| [k, ''] }]
178
+
179
+ CSV.open(destination_path, 'w') do |csv|
180
+ csv << fields.keys
181
+ media_file_paths_with_data.each do |media_file_path, data|
182
+ data[media_file_path_field_name] = media_file_path
183
+ csv << empty_record.merge(data).values
184
+ end
185
+ end
186
+
187
+ puts "Output written to: #{destination_path}"
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift(File.expand_path(File.join(File.dirname(__FILE__), '../lib')))
3
+ require 'json'
4
+ require 'optparse'
5
+ require 'final_cut_pro/xml_parser'
6
+
7
+ options = { }
8
+
9
+ op = OptionParser.new
10
+ op.banner = "Usage: #{File.basename(__FILE__)} [options] filepath"
11
+ options[:pretty_print] = true
12
+ op.on('--[no-]pretty-print', '') { |v| options[:pretty_print] = v }
13
+ options[:output_files] = true
14
+ op.on('--[no-]files', '') { |v| options[:output_files] = v }
15
+ options[:output_sequences] = true
16
+ op.on('--[no-]sequences', '') { |v| options[:output_sequences] = v }
17
+ op.parse!
18
+
19
+ file_path = ARGV.shift
20
+ #file_path ||= '/Users/admin/Dropbox/work/bbc/examples/final_cut_pro/SX100117B-Football Samoa RE-EDIT.xml'
21
+ file_path ||= '/Users/admin/Dropbox/work/bbc/examples/final_cut_pro/multipleEdits-SameSource-NNAT069S Nigeria master EDIT.xml'
22
+ # file_path ||= '/Users/admin/Dropbox/work/bbc/examples/final_cut_pro/SX071209A_Kings of Congo.xml'
23
+ unless file_path
24
+ puts op
25
+ exit
26
+ end
27
+
28
+ # { files: { }, sequences: { } }
29
+ doc = FinalCutPro::XMLParser.parse(file_path)#.to_hash
30
+
31
+ output = { }
32
+ output[:files] = doc.files if options[:output_files]
33
+ output[:sequences] = doc.sequences if options[:output_sequences]
34
+
35
+ json_generate_options = { :max_nesting => 250 }
36
+ if options[:pretty_print]
37
+ puts JSON.pretty_generate(output, json_generate_options)
38
+ else
39
+ puts JSON.fast_generate(output, json_generate_options)
40
+ end
41
+
data/bin/mig ADDED
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env ruby
2
+ #lib_path = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ lib_path = File.expand_path('../../lib', __FILE__)
4
+ $:.unshift(lib_path) if !$:.include?(lib_path) and File.exists?(lib_path)
5
+
6
+ require 'rubygems'
7
+ require 'json'
8
+ require 'optparse'
9
+
10
+ require 'mig'
11
+
12
+ options = { }
13
+
14
+ op = OptionParser.new
15
+ #op.banner = "Usage: #{File.basename(__FILE__)} [options] filepath"
16
+ op.on('--ffmpeg-bin-path PATH', '') { |v| options[:ffmpeg_cmd_path] = v }
17
+ op.on('--mediainfo-bin-path PATH', '') { |v| options[:mediainfo_cmd_path] = v }
18
+ op.on('--exiftool-bin-path PATH', '') { |v| options[:exiftool_cmd_path] = v }
19
+ op.on('--[no-]pretty-print', '') { |v| options[:pretty_print] = v }
20
+ op.on('--help', 'Displays this message.') { puts op; exit }
21
+ op.load
22
+ op.parse!
23
+
24
+ logger = Logger.new(STDERR)
25
+ logger.level = Logger::ERROR
26
+ options[:logger] = logger
27
+
28
+ file_path = ARGV.shift
29
+ unless file_path
30
+ puts op
31
+ exit
32
+ end
33
+
34
+ mig = MediaInformationGatherer.new(options)
35
+ abort("File Not Found. #{file_path}") unless File.exist?(file_path)
36
+ output = mig.run(file_path)
37
+
38
+ json_options = { :allow_nan => true }
39
+
40
+ if options[:pretty_print]
41
+ puts JSON.pretty_generate(output, json_options)
42
+ else
43
+ puts JSON.fast_generate(output, json_options)
44
+ end