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,15 @@
1
+ require 'final_cut_pro/xml_parser/document'
2
+ module FinalCutPro
3
+ class XMLParser
4
+
5
+ def self.load(xml, options = { })
6
+ return Document.load(xml, options = { })
7
+ end # self.load
8
+
9
+ def self.parse(xml, options = { })
10
+ doc = load(xml, options)
11
+ doc.parse
12
+ end # self.parse
13
+
14
+ end
15
+ end
@@ -0,0 +1,121 @@
1
+ require 'axml'
2
+ require 'cgi'
3
+ require 'uri'
4
+ require 'final_cut_pro'
5
+
6
+ module FinalCutPro
7
+ class XMLParser
8
+ class Common
9
+
10
+ attr_accessor :logger
11
+ attr_accessor :sequences, :files
12
+
13
+ def self.xml_as_document(xml, options = { })
14
+ AXML.xml_as_document(xml)
15
+ end # xml_as_document
16
+
17
+ def initialize(xml, options = { })
18
+ @logger = FinalCutPro.process_options_for_logger(options)
19
+ @xml_document = xml_as_document(xml)
20
+ end # initialize
21
+
22
+ def xml_as_document(xml)
23
+ self.class.xml_as_document(xml)
24
+ end # xml_as_document
25
+
26
+ def to_hash(keys_to_symbols = true)
27
+ rt = keys_to_symbols ? root_type.to_sym : root_type
28
+ { rt => xml_node_to_hash(root, keys_to_symbols) }
29
+ end # to_hash
30
+
31
+ def xml_node_to_hash(node, keys_to_symbols = true)
32
+ # If we are at the root of the document, start the hash
33
+ return node.content.to_s unless node.element?
34
+ result_hash = {}
35
+
36
+ # Add the attributes for the node to the hash
37
+ node.each_attr { |a| a_name = keys_to_symbols ? a.name.to_sym : a.name; result_hash[a_name] = a.value }
38
+ return result_hash.empty? ? nil : result_hash unless node.children?
39
+
40
+ node.each_child do |child|
41
+ result = xml_node_to_hash(child, keys_to_symbols)
42
+
43
+ if child.name == 'text' or child.cdata?
44
+ return result if !child.next? and !child.prev?
45
+ next
46
+ end
47
+
48
+ begin
49
+ key = keys_to_symbols ? child.name.to_sym : child.name
50
+ rescue
51
+ # FCP CDATA Fields usually fail to convert to a sym.
52
+ logger.error { "Error Converting #{child.name} to symbol.\nCHILD: #{child.inspect}\nNODE: #{node}" }
53
+ key = child.name
54
+ end
55
+ if result_hash[key]
56
+ # We don't want to overwrite a value for an existing key so the value needs to be an array
57
+ if result_hash[key].is_a?(Array)
58
+ result_hash[key] << result
59
+ else
60
+ # Create an array
61
+ result_hash[key] = [ result_hash[key], result ]
62
+ end
63
+ else
64
+ result_hash[key] = result
65
+ end
66
+ end
67
+ return result_hash
68
+ end # xml_node_to_hash
69
+
70
+ def xml_document
71
+ @xml_document
72
+ end # xml_document
73
+ alias :xmldoc :xml_document
74
+
75
+ def root
76
+ xml_document.root
77
+ end # root
78
+
79
+ # Gets the
80
+ def root_type
81
+ @root_type ||= root.name
82
+ end # type
83
+
84
+ def is_xmeml?
85
+ root_type == 'xmeml'
86
+ end # is_xmeml?
87
+
88
+ def is_fcpxml?
89
+ root_type == 'fcpxml'
90
+ end # is_fcpxml?
91
+
92
+
93
+ # The fcpxml or xmeml version
94
+ def version
95
+ @version ||= root.attributes['version']
96
+ end
97
+
98
+ # The tag inside of the xmeml or fcpxml tag
99
+ def top_level_container
100
+ @top_level_container ||= root.children.first.name
101
+ end #
102
+
103
+ def is_clip?
104
+ top_level_container == 'clip'
105
+ end # is_clip?
106
+
107
+ def is_project?
108
+ top_level_container == 'project'
109
+ end # is_project?
110
+
111
+ def is_sequence?
112
+ top_level_container == 'sequence'
113
+ end # is_sequence?
114
+
115
+ def sequences
116
+ @sequences ||= xml_document.find('//sequence')
117
+ end # sequences
118
+
119
+ end # Common
120
+ end # XMLParser
121
+ end # FinalCutPro
@@ -0,0 +1,18 @@
1
+ require 'final_cut_pro/xml_parser/common'
2
+ require 'final_cut_pro/xml_parser/xmeml/version_5'
3
+ require 'final_cut_pro/xml_parser/fcpxml/version_1'
4
+
5
+ module FinalCutPro
6
+ class XMLParser
7
+ class Document < Common
8
+
9
+ def self.load(xml, options = { })
10
+ doc = new(xml, options = { })
11
+ return XMEML::Version5.new(doc.xml_document, options = { }) if doc.is_xmeml?
12
+ return FCPXML::Version1.new(doc.xml_document, options = { }) if doc.is_fcpxml?
13
+ return false
14
+ end # self.load
15
+
16
+ end # Document
17
+ end # XMLParser
18
+ end # FinalCutPro
@@ -0,0 +1,28 @@
1
+ # https://developer.apple.com/library/mac/#documentation/FinalCutProX/Reference/FinalCutProXXMLFormat/FCPXMLDTDv1.1/FCPXMLDTDv1.1.html
2
+ require 'final_cut_pro/xml_parser/common'
3
+ module FinalCutPro
4
+ class XMLParser
5
+ class FCPXML
6
+ class Version1 < FinalCutPro::XMLParser::Common
7
+
8
+ def self.parse(xml, params = {})
9
+ parser = new(xml)
10
+ parser.parse(parser.xml_document, params)
11
+ end # self.parse
12
+
13
+ def parse(xml = @xml_document, options = { })
14
+ @files = parse_files(xml, options)
15
+ return self
16
+ end # parse
17
+
18
+ def parse_files(xml = @xml_document, options = { })
19
+ xml.find('//asset').map do |asset_node|
20
+ hash = xml_node_to_hash(asset_node)
21
+ hash.merge!({ :path_on_file_system => CGI::unescape(URI(hash[:src]).path) })
22
+ end
23
+ end # parse_files
24
+
25
+ end # Version1
26
+ end # FCPXML
27
+ end # XMLParser
28
+ end # FinalCutPro
@@ -0,0 +1,234 @@
1
+ # http://developer.apple.com/library/mac/#documentation/AppleApplications/Reference/FinalCutPro_XML/DTD/DTD.html#//apple_ref/doc/uid/TP30001157-BCIHDFGD
2
+ require 'final_cut_pro/xml_parser/common'
3
+ module FinalCutPro
4
+ class XMLParser
5
+ class XMEML
6
+ class Version5 < FinalCutPro::XMLParser::Common
7
+
8
+ def self.parse_clips(xml, options = { })
9
+ parser = new
10
+ parser.parse_clips(xml_as_document(xml, options))
11
+ end # self.parse_clips
12
+
13
+ def self.parse_sequences(xml, options = { })
14
+ parser = new
15
+ parser.parse_sequences(xml_as_document(xml, options))
16
+ end # self.parse_sequences
17
+
18
+ def self.parse_files(xml, options = { })
19
+ parser = new
20
+ parser.parse_files(xml, options)
21
+ end # self.parse_files
22
+
23
+ def get_project_name(node)
24
+ #@project_name = node.find('ancestor::project').first.find('./name').first.content.to_s
25
+ project_node = node.find_first('ancestor::project')
26
+ unless project_node.nil?
27
+ project_name_node = project_node.find_first('./name')
28
+ unless project_name_node.nil? or project_name_node.content.nil?
29
+ project_name = project_name_node.content.to_s
30
+ return project_name
31
+ end
32
+ end
33
+ end
34
+
35
+ def get_bin_path(node)
36
+ bin_path = []
37
+ node.find('ancestor::bin').each do |ancestor_node|
38
+ bin_path << ancestor_node.find('./name').first.content.to_s unless ancestor_node.find('./name').first.content.to_s.empty?
39
+ end
40
+ return '' if bin_path.empty?
41
+ return "/#{bin_path.join('/')}/"
42
+ end
43
+
44
+ def get_clip_hash_by_id(doc, id)
45
+ #puts "ID: #{id}"
46
+ id = id.dup.gsub(/["\s]/, ' ' => "\\s", '"' => '&quot;')
47
+ clip = doc.find("//clip[@id=\"#{id}\"]").first
48
+ return { } unless clip
49
+ return xml_node_to_hash(clip) || { }
50
+ end
51
+
52
+ def get_clipitem_hash(node)
53
+ return xml_node_to_hash(node.parent)
54
+ end
55
+
56
+ def get_file_clipitems(node, file_id = nil)
57
+
58
+ end
59
+
60
+ def get_all_metadata(node)
61
+ metadata = {}
62
+ node.find('descendant::metadata|metadata').each do |metadata_node|
63
+ metadata[metadata_node.find('./key').first.content.to_s] = metadata_node.find('./value').first.content.to_s unless metadata_node.find('./key').first.nil? or metadata_node.find('./key').first.empty? or metadata_node.find('./value').first.nil? or metadata_node.find('./value').first.empty?
64
+ end
65
+ return metadata
66
+ end
67
+
68
+ def get_all_labels(node)
69
+ labels = []
70
+ node.find('ancestor::bin/labels|ancestor::clip/labels|ancestor::sequence/labels|ancestor::clipitem/labels|ancestor::generatoritem/labels').each do |labels_node|
71
+ parent = labels_node.parent
72
+ first_from = parent.find('./name|@id').first
73
+
74
+ # Name field responds to .content where as id attribute value is found with .value
75
+ from_name_or_id = first_from.respond_to?(:content) ? first_from.content : first_from.value
76
+
77
+ label_set = {}
78
+ label_set[:from] = { parent.name.to_s => from_name_or_id }
79
+ label_set[:labels] = []
80
+
81
+ labels_node.find('./*').each do |label_node_item|
82
+ label_node_item_content = label_node_item.content
83
+ label_set[:labels] << {label_node_item.name.to_s => label_node_item_content.to_s} unless label_node_item_content.nil? or label_node_item_content.empty?
84
+ end
85
+ labels << label_set
86
+ end
87
+ return labels
88
+ end
89
+
90
+ def get_all_comments(node)
91
+ comments = []
92
+ node.find('ancestor::bin/comments|ancestor::clip/comments|ancestor::sequence/comments|ancestor::clipitem/comments|ancestor::generatoritem/comments').each do |comments_node|
93
+ parent = comments_node.parent
94
+ first_from = parent.find('./name|@id').first
95
+
96
+ # Name field responds to .content where as id attribute value is found with .value
97
+ from_name_or_id = first_from.respond_to?(:content) ? first_from.content : first_from.value
98
+
99
+ comment_set = {}
100
+ comment_set[:from] = { parent.name.to_s => from_name_or_id }
101
+ comment_set[:comments] = []
102
+ comments_node.find('./*').each do |comment_node_item|
103
+ comment_set[:comments] << {comment_node_item.name.to_s => comment_node_item.content.to_s} unless comment_node_item.content.nil? or comment_node_item.content.empty?
104
+ end
105
+ comments << comment_set
106
+ end
107
+ return comments
108
+ end
109
+
110
+ def get_logging_info(node)
111
+ logginginfos = []
112
+ node.find('ancestor::clip/logginginfo|ancestor::sequence/logginginfo|ancestor::clipitem/logginginfo|ancestor::generatoritem/logginginfo').each do |logginginfo_node|
113
+ parent = logginginfo_node.parent
114
+ first_from = parent.find('./name|@id').first
115
+
116
+ # Name field responds to .content where as id attribute value is found with .value
117
+ from_name_or_id = first_from.respond_to?(:content) ? first_from.content : first_from.value
118
+
119
+ logginginfo_set = {}
120
+ logginginfo_set[:from] = { parent.name.to_s => from_name_or_id }
121
+ logginginfo_set[:logginginfo] = {}
122
+ logginginfo_node.find('./*').each do |logginginfo_node_item|
123
+ logginginfo_set[:logginginfo][logginginfo_node_item.name.to_s] = logginginfo_node_item.content.to_s unless logginginfo_node_item.content.nil? or logginginfo_node_item.content.empty?
124
+ end
125
+ logginginfos << logginginfo_set
126
+ end
127
+ return logginginfos
128
+ end
129
+
130
+ def get_files_as_nodes(doc)
131
+ doc.find('//file[pathurl]')
132
+ end # get_files_as_nodes
133
+
134
+ def build_file_hash_hash(params = {})
135
+ _files = { }
136
+ key = params[:key_on] || :id
137
+ files_array = build_file_hash_array(@xml_document)
138
+ files_array.each {|file| _files[file[key]] = file }
139
+ _files
140
+ end # build_file_hash_hash
141
+
142
+ def build_file_hash_array(doc, options = { })
143
+ _files = []
144
+
145
+ nodes = get_files_as_nodes(doc)
146
+ total_files = nodes.count
147
+
148
+ counter = 0
149
+
150
+ nodes.each do |node|
151
+ counter += 1
152
+ logger.debug { "Processing file #{counter} of #{total_files}" }
153
+ file_hash = build_file_hash(node)
154
+ next unless file_hash
155
+ logger.debug { "file_hash => #{file_hash}" }
156
+ _files << file_hash
157
+ end
158
+
159
+ return _files
160
+ end
161
+
162
+ def build_file_hash(node)
163
+ file_id = node.attributes.get_attribute('id').value.to_s
164
+ logger.debug { "Processing File with an ID of: #{file_id}" }
165
+
166
+ path_url_element = node.find('./pathurl').first
167
+ unless !path_url_element.nil?
168
+ logger.debug { 'Skipping File Node. No pathurl attribute found.' }
169
+ return false
170
+ end
171
+
172
+ file_name = node.find('./name').first.content.to_s # Should equal the base name from the path url
173
+ file_path_url = path_url_element.content.to_s
174
+ file_hash = {
175
+ :id => file_id,
176
+ :name => file_name,
177
+ :path_url => file_path_url
178
+ }
179
+
180
+ # a plus (+) would be converted to a space using CGI.unescape so we only escape %## encoded values from the pathurl
181
+ #file_hash[:path_on_file_system] = CGI::unescape(URI(file_hash[:path_url]).path)
182
+ file_hash[:path_on_file_system] = URI(file_path_url).path.gsub(/(%(?:[2-9]|[A-F])(?:\d|[A-F]))/) { |v| CGI.unescape(v) }
183
+ file_hash[:file_node_full] = xml_node_to_hash node
184
+ file_hash[:project_name] = get_project_name node
185
+ file_hash[:bin_path] = get_bin_path node
186
+ file_hash[:clipitem_node_full] = get_clipitem_hash node
187
+
188
+ file_hash[:clipitems] = get_file_clipitems(node, file_id)
189
+
190
+ master_clip_id = file_hash[:clipitem_node_full][:masterclipid]
191
+ file_hash[:masterclip_node_full] = (master_clip_id.nil? or master_clip_id.empty?) ? { } : get_clip_hash_by_id(node.doc, master_clip_id)
192
+ file_hash[:metadata] = get_all_metadata node
193
+ file_hash[:labels] = get_all_labels node
194
+ file_hash[:comments] = get_all_comments node
195
+ file_hash[:logginginfo] = get_logging_info node
196
+ file_hash
197
+ end
198
+
199
+ def parse(xml = @xml_document, options = { })
200
+ xml_doc = self.class.xml_as_document(xml)
201
+ @files = parse_files(xml_doc, options)
202
+ @sequences = parse_sequences(xml_doc, options)
203
+ @clips = parse_clips(xml_doc)
204
+ return self
205
+ end # parse
206
+
207
+ def parse_files(xml = @xml_document, options = { })
208
+ build_file_hash_array(self.class.xml_as_document(xml), options = { }) || [ ]
209
+ end # parse_files
210
+
211
+ def parse_clips(xml = @xml_document, options = { })
212
+ self.class.xml_as_document(xml).find('//clip').map do |clip_node|
213
+ # 'clipitem[(not(enabled) or enabled[text()="TRUE"]) and ancestor::video/track[enabled[text()="TRUE"]]]'
214
+ # enabled tag may be missing on some clipitems such as those that link to other clipitems. This could cause a problem following links.
215
+ clip_items = clip_node.find('clipitem[(not(enabled) or enabled[text()="TRUE"]) and ../../../../media/video/track[enabled[text()="TRUE"]]]').map do |clip_item_node|
216
+ xml_node_to_hash(clip_item_node)
217
+ end
218
+ xml_node_to_hash(clip_node).merge({ :clip_items => clip_items })
219
+ end
220
+ end # parse_clips
221
+
222
+ def parse_sequences(xml = @xml_document, options = { })
223
+ self.class.xml_as_document(xml).find('//sequence').map do |sequence_node|
224
+ clip_items = sequence_node.find('media/video/track/clipitem[not(enabled) or enabled[text()="TRUE"]]').map do |clip_item_node|
225
+ xml_node_to_hash(clip_item_node)
226
+ end
227
+ xml_node_to_hash(sequence_node).merge({ :clip_items => clip_items })
228
+ end
229
+ end # parse_sequences
230
+
231
+ end # Version5
232
+ end # XMEML
233
+ end # XMLParser
234
+ end # FinalCutPro
@@ -0,0 +1,51 @@
1
+ require 'cgi'
2
+ require 'plist'
3
+ require 'uri'
4
+ module ITunes
5
+
6
+ class XMLParser
7
+
8
+ def self.parse(xml, params = {})
9
+ new(params.merge(:file => xml))
10
+ end # self.parse
11
+
12
+ attr_reader :parsed
13
+
14
+ def initialize(params = {})
15
+ @file_name = params[:file]
16
+ parse if @file_name
17
+ end # initialize
18
+
19
+ def parse(params = {})
20
+ @parsed = Plist.parse_xml(@file_name)
21
+ end # parse
22
+
23
+ def tracks
24
+ @tracks ||= parsed['Tracks']
25
+ end # tracks
26
+
27
+ def files
28
+ @files ||= begin
29
+ _files = [ ]
30
+ tracks.each do |id, track|
31
+ _files << track.merge(:path_on_file_system => CGI.unescape(URI(track['Location']).path))
32
+ end
33
+ _files
34
+ end
35
+ end # files
36
+
37
+ # Performs additional processing to each tracks fields
38
+ # @param [Hash] _tracks
39
+ def process_tracks(_tracks = @tracks)
40
+ _tracks.dup.each do |id, track|
41
+ add_path_to_track(id, track)
42
+ end
43
+ end # process_tracks
44
+
45
+ def add_path_to_track(id, track)
46
+ tracks[id]['Path'] = CGI.unescape(URI.parse(track['Location']).path)
47
+ end # add_path_to_track
48
+
49
+ end # XMLParser
50
+
51
+ end # ITunes