0

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 &#38; 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!

timbot
  • 5
  • 5
  • The Cocoa APIs have classes that will do the conversion. If you are more familiar with AppleScript, AppleScriptObjC can be used. Your sample is just a portion of the Music library plist/xml file though - are you just looking for the tracks? What is the complete XML file going to look like? – red_menace Oct 19 '22 at 20:23
  • Also, the System Events app has two suites — a Property List suite and an XML suite — that would allow you to do the conversion more easily then puddling around in an Excel spreadsheet (though more headache-inducing than AppleScriptObjC would be). – Ted Wrigley Oct 20 '22 at 14:29
  • oh hmmm ill check out AppleScriptObjC. I keep hearing about Cocoa API, but honestly went way over my head. I felt like a monkey slamming on keys trying to get the results I wanted. Im not sure how to send the exact xml file output, but I would snip it down to only include track and metadata. Maybe put the playlist track references in a separate database. – timbot Oct 20 '22 at 17:55
  • basically I want to keep a record of my music metadata at certain timestamps because I lost it all at one point. Then I had some cool ideas I can work with this- like if play count in the last month is .... – timbot Oct 20 '22 at 17:57
  • I have a sample that converts Music library tracks to your XML format, but it would help to know _exactly_ what you are trying to capture and an example of the expected result. – red_menace Oct 20 '22 at 22:04
  • I basically want an easier to work with XML file (and maybe im just overcomplicating things and the one music spits out works fine). I manipulated the Apple Music XML (first snippet) to look like the second snippet in OP. The second is SO much easier to work with when connecting it to an Excel DB. I just want to be able to archive my metadata into a database (or excel sheet if needed) – timbot Oct 21 '22 at 21:49
  • XML is very flexible and can be used in a variety of ways, but you haven't shown the document format that whatever you are going to view or use the XML file with is expecting. Are you wanting just the tracks, and all the track keys? What structure are you using for the track item list? Are you wanting standard XML files? – red_menace Oct 22 '22 at 16:35
  • I think I need to look more into XML... basically I want to take those track properties (name, played count, date added, artist) which Music spits, have the track ID be the key, then load that into a database that I can access later. first step is I just want to be able to create that DB and populate it with that 1 XML file (later I will worry about the timestamps of when the library (XML file) was exported) – timbot Oct 22 '22 at 20:11
  • As I mentioned in an earlier comment, I have a script that will extract the tracks and convert the specified keys (do you want all of them?), but the question is what format does the XML file need to be? Just regular XML? What kind of format does the database application want? – red_menace Oct 22 '22 at 21:02
  • Oh I see. Sorry about my lack of background. So currently... I am using my formatted XML file (that I mentioned and did text manipulation on) to load the file into an excel database (using the import XML feature). Ideally, I would like to learn SQL and keep everything stored on a local server. I hope this is the info you need – timbot Oct 22 '22 at 21:27

1 Answers1

0

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
red_menace
  • 3,162
  • 2
  • 10
  • 18