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 +7 -0
- data/.gitignore +21 -0
- data/README.md +2 -0
- data/bin/catalog +181 -0
- data/bin/catalog_assets +187 -0
- data/bin/fcp_xml_parser +41 -0
- data/bin/mig +44 -0
- data/bin/mig_http +52 -0
- data/bin/xml_processor +51 -0
- data/config/default/xml_processor_config +49 -0
- data/lib/axml.rb +59 -0
- data/lib/cli.rb +88 -0
- data/lib/final_cut_pro.rb +31 -0
- data/lib/final_cut_pro/sequence_processor.rb +135 -0
- data/lib/final_cut_pro/xml_parser.rb +15 -0
- data/lib/final_cut_pro/xml_parser/common.rb +121 -0
- data/lib/final_cut_pro/xml_parser/document.rb +18 -0
- data/lib/final_cut_pro/xml_parser/fcpxml/version_1.rb +28 -0
- data/lib/final_cut_pro/xml_parser/xmeml/version_5.rb +234 -0
- data/lib/itunes/xml_parser.rb +51 -0
- data/lib/media_processing_tool/publisher.rb +52 -0
- data/lib/media_processing_tool/xml_parser.rb +30 -0
- data/lib/media_processing_tool/xml_parser/document.rb +38 -0
- data/lib/media_processing_tool/xml_parser/identifier.rb +43 -0
- data/lib/media_processing_tool/xml_processor.rb +132 -0
- data/lib/mig.rb +158 -0
- data/lib/mig/http.rb +54 -0
- data/lib/mig/modules/common.rb +333 -0
- data/lib/mig/modules/exiftool.rb +26 -0
- data/lib/mig/modules/ffmpeg.rb +225 -0
- data/lib/mig/modules/media_type.rb +23 -0
- data/lib/mig/modules/mediainfo.rb +91 -0
- data/lib/timecode_methods.rb +108 -0
- data/lib/udam_utils/publish_map_processor.rb +710 -0
- metadata +111 -0
@@ -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", '"' => '"')
|
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
|