I have an Apple Music Library output file that looks like this:
<key>6871</key>
<dict>
<key>Track ID</key><integer>6871</integer>
<key>Name</key><string>12 Wake Up Call</string>
<key>Artist</key><string>Rebelution</string>
<key>Album Artist</key><string>Rebelution</string>
<key>Grouping</key><string>AllMusic</string>
<key>Kind</key><string>Apple Music AAC audio file</string>
<key>Size</key><integer>6178208</integer>
<key>Total Time</key><integer>257332</integer>
<key>Year</key><integer>2009</integer>
<key>Date Modified</key><date>2011-11-22T23:32:45Z</date>
<key>Date Added</key><date>2011-12-14T23:30:26Z</date>
<key>Bit Rate</key><integer>256</integer>
<key>Sample Rate</key><integer>44100</integer>
<key>Play Count</key><integer>101</integer>
<key>Play Date</key><integer>3717804040</integer>
<key>Play Date UTC</key><date>2021-10-23T07:20:40Z</date>
<key>Skip Count</key><integer>10</integer>
<key>Skip Date</key><date>2020-09-16T14:39:31Z</date>
<key>Rating</key><integer>60</integer>
<key>Album Rating</key><integer>60</integer>
<key>Album Rating Computed</key><true/>
<key>Normalization</key><integer>1699</integer>
<key>Artwork Count</key><integer>1</integer>
<key>Persistent ID</key><string>56B43C03AFF476E5</string>
<key>Track Type</key><string>Remote</string>
<key>Apple Music</key><true/>
</dict>
I am trying to make this easier to store in a database (I don't understand SQL, but that's the end goal). For now I am adding and looking up "entries" in an excel sheet. I am able to manipulate the XML file manually by pasting it into a workbook, then I have to use ablebits and vlookups and a bunch of other time consuming operations which I paste into a new text file. End goal of this question is to get my "XML" file to look like this:
<key>5056</key>
<dict>
<TrackID>5056</TrackID>
<Name>Heart Like a Lion</Name>
<Artist>Rebelution</Artist>
<AlbumArtist>Rebelution</AlbumArtist>
<Composer>Eric Ariel Rachmany, Marley D. Williams, Rourke Carey & Wesley Dallas Finley</Composer>
<Album>Courage to Grow</Album>
<Grouping>LIBRARY</Grouping>
<Genre>Reggae</Genre>z
<Kind>Apple Music AAC audio file</Kind>
<Size>11679958</Size>
<TotalTime>338413</TotalTime>
<DiscNumber>1</DiscNumber>
<DiscCount>1</DiscCount>
<TrackNumber>2</TrackNumber>
<TrackCount>12</TrackCount>
<Year>2007</Year>
<DateModified>2021-11-10T08:29:23Z</DateModified>
<DateAdded>2021-11-10T08:29:23Z</DateAdded>
<BitRate>256</BitRate>
<SampleRate>44100</SampleRate>
<PlayCount>8</PlayCount>
<PlayDate>3747937611</PlayDate>
<PlayDateUTC>2022-10-07T01:46:51Z</PlayDateUTC>
<ReleaseDate>2007-06-08T12:00:00Z</ReleaseDate>
<Rating>100</Rating>
<AlbumRating>60</AlbumRating>
<AlbumRatingComputed></AlbumRatingComputed>
<ArtworkCount>1</ArtworkCount>
<SortAlbum>Courage to Grow</SortAlbum>
<SortArtist>Rebelution</SortArtist>
<SortName>Heart Like a Lion</SortName>
<PersistentID>AD1A6E4E78F9C79D</PersistentID>
<TrackType>Remote</TrackType>
<AppleMusic></AppleMusic>
</dict>
Anything will help, this has become more time consuming and difficult than I thought.
Im also open to alternative routes... I just want to backup my metadata because I lost it once (recovered it manually as mentioned above), but I also have some good ideas for making playlists based on timestamps of metadata values.
Oh side note... Im also open to using another language if that's easier. I have minimal background in code and have been teaching myself AppleScript since my scrips are mostly interacting with Apple stuff.
Thanks!
AppleScriptObjC can be used to access the various Cocoa frameworks, for example to read a plist/xml file into an NSDictionary (similar to a record), where the various keys can be accessed programmatically, and for utilities such as date formatting, list sorting, etc.
There is an NSXMLNode class that can be used to create the elements, but in this case manually converting the dictionary keys isn't quite as wordy.
The following script creates a plain XML file from an Apple Music Library export. It extracts the specified key items into a track element and uses the track ID as an element attribute:
use framework "Foundation" -- for the AppleScriptObjC bits
use scripting additions
# the dictionary keys to extract (use an empty list {} for everything):
property keyNames : {"Name", "Kind", "Size", "Total Time", "Date Added", "Track Type", "Location"}
property keepSet : missing value -- this will be an NSSet of the keys
property indent : " " -- formatting
on run -- create an XML file for track data from an exported Music Library plist/XML file
if keyNames is not in {"", {}, missing value} then set keepSet to current application's NSMutableSet's setWithArray:(keyNames as list)
set fileURL to current application's NSURL's fileURLWithPath:(POSIX path of (choose file of type {"com.apple.property-list", "public.xml"} with prompt "Choose the Music Library export file to process:"))
set fileData to current application's NSData's dataWithContentsOfURL:fileURL
try -- read file data (source XML file needs to be in Apple's property list format)
set plist to (current application's NSPropertyListSerialization's propertyListWithData:fileData options:(current application's NSPropertyListMutableContainersAndLeaves) format:(missing value) |error|:(missing value))
if plist is missing value then error "The chosen file is not an Apple plist/XML file."
set trackDict to (plist's valueForKey:"Tracks") -- dictionary of tracks
if trackDict is missing value then error "The chosen file does not have a 'Tracks' key in the root directory."
on error errmess
display alert "Script Error" message errmess
error number -128 -- cancel
end try
set theResult to ""
repeat with trackItem in trackDict's allKeys()
set trackKeyPath to "Tracks." & (trackItem as text) -- dictionary for individual track key
set theResult to theResult & addWrapper(trackItem as text, (XMLtext from (plist's valueForKeyPath:trackKeyPath)))
end repeat
writeToFile((choose file name default name "Converted Library.xml"), addWrapper(missing value, theResult))
end run
# return XML text from simple key/value pairs of a dictionary
on XMLtext from dictionary
set XMLElements to {}
set candidate to current application's NSMutableSet's setWithArray:(dictionary's allKeys())
if keepSet is not missing value then candidate's intersectSet:keepSet -- remove other keys
repeat with keyItem in candidate's allObjects()
try
set theItem to (dictionary's valueForKey:keyItem)
set theValue to theItem as text -- test
on error errmess -- can't coerce object to text
set theClass to current application's NSStringFromClass(theItem's class) as text
if theClass contains "Date" then -- format NSDate
set theValue to (current application's NSISO8601DateFormatter's alloc's init()'s stringFromDate:theItem) as text
else -- something needing additional formatting or processing such as a collection, etc
log theClass & ": " & errmess
set theValue to "*ERROR*" -- or add formatting for the object
end if
end try
set keyName to (keyItem's lowercaseString's stringByReplacingOccurrencesOfString:" " withString:"_") -- no spaces in key names
set end of XMLElements to indent & indent & "<" & keyName & ">" & theValue & "</" & keyName & ">" & linefeed -- can also use NSXMLNode
end repeat
set elementArray to current application's NSArray's arrayWithArray:XMLElements
return (elementArray's sortedArrayUsingSelector:"compare:") as list as text -- sort
end XMLtext
# add wrappers for individual track entries or the document
to addWrapper(theKey, theText)
if theKey is not missing value then -- wrap individual track elements - the key is used as an attribute
return linefeed & indent & "<track id=\"" & theKey & "\">" & linefeed & theText & indent & "</track>" & linefeed
else -- wrapper and root element for a standard XML document
return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<!-- track data extracted " & ((current date) as «class isot» as string) & " from exported Apple Music Library -->
<music_tracks>" & theText & "</music_tracks>" & linefeed
end if
end addWrapper
on writeToFile(filePath, whatever)
try
set fileRef to (open for access filePath with write permission)
set eof of fileRef to 0 -- overwrite existing
write whatever to fileRef starting at eof
close access fileRef
on error
try
close access fileRef
end try
end try
end writeToFile