1

I have searched everywhere and I am unable to find a swifty example of how to use the Apple ITunesLibraryFramework. I have been trying to figure out how to use this Framework in Swift 5.2.

I want to get information directly from the Music library rather than having to rely on a XML library export file.

I have the below code in my playground and it prints nothing legible. How would I access the fields for playlists and mediaItems and be able to read them in human readable form?

I have installed the framework in the project. This is my project playground code:

import Foundation
import iTunesLibrary

var library:ITLibrary

do {
    let library = try ITLibrary(apiVersion: "1.1")
    let mediaItems = library.allMediaItems
    let playlists = library.allPlaylists
    print("Media Folder Location - \(String(describing: library.mediaFolderLocation))")
    print("\nPlaylists - \(playlists)")
    print("\nTracks - \(mediaItems)")
} catch let error as NSError {
    print(error)
}

This is the ITLibrary.m file that I imported the header:

#import <Foundation/Foundation.h>
#import <iTunesLibrary/iTunesLibrary.h>

When I run the above code in the project playground all I get is a bunch of binary data for both playlists and mediaItems. All I want to do is iterate over the data and collect information from the library for use in my program. It's probably something easy, but I haven't found it yet.

EDIT: - After using @Vincent's answer I ran into another problem with the following code:

import Foundation
import iTunesLibrary

let library = try ITLibrary(apiVersion: "1.1")
typealias tracks = [NSNumber:TrackInfo]
var trackInfo = TrackInfo()

struct TrackInfo {
    var title = ""
    var artist = ""
    var album = ""
    var totalTime = 0
    var year = 0
    var persistentID = ""
    var location:URL!
}

let songs = library.allMediaItems
let playlists = library.allPlaylists

for playlist in playlists {
    if playlist.name.lowercased().contains("test-") {
        print("New Playlist---------------\nName: \(playlist.name)")
        for song in playlist.items {
            trackInfo.title = song.title
            print("\n\nNew Track-----------------\nTitle: \(trackInfo.title)")
            if song.artist?.name != nil {
                trackInfo.artist = song.artist?.name as! String
            }
            print("Artist: \(trackInfo.artist)")
            trackInfo.album = song.album.title!
            print("Albumn Name: \(trackInfo.album)")
            trackInfo.totalTime = song.totalTime
            print("Total Time: \(trackInfo.totalTime)")
            trackInfo.year = song.year
            print("Year: \(trackInfo.year)")
            trackInfo.location = song.location!
            print("Location: \(trackInfo.location!)")
            var persistentID = song.persistentID
            tracks.updateValue(song.persistentID, trackInfo)
        }
    }
}

The issue I'm having is getting the tracks info into the trackInfo dictionary. I'm trying to use the track persistentID (NSNumber) as the key for the dictionary, which I have declared. For some reason it isn't allowing me to use it.

SouthernYankee65
  • 1,129
  • 10
  • 22
  • I'm have figured most of it out now, but I'm still having an issue with using the persistentID as the key for the trackInfo dictionary. – SouthernYankee65 Jun 17 '20 at 06:56

1 Answers1

1

Here's how you can have it print each playlist and track:

Each ITLibPlaylist or ITLibMediaItem object contains many information about each playlist/media item. To get only the name/title of each, you will have to iterate through the results to retrieve them.

For this example below, the name of each playlist's name is printed.

    print("\nPlaylists -")
    for playlist in playlists {
        print(playlist.name)
    }

Which will print (for example):

Playlists -
Library
Music

For this example below, the name of each track's name is printed.

    print("\nTracks -")
    for mediaItem in mediaItems {
        print(mediaItem.title)
    }

Which will print (for example):

Tracks -
Ev'ry Time We Say Goodbye
My Favorite Things
But Not for Me
Summertime

Edit: Here's the secondary solution to the secondary problem:

First things first, a dictionary should be initialised, instead of using typealias.

typealias only makes an alias for a pre existing type, like

typealias NumberWithAlotOfDecimals = Double
let a: NumberWithAlotOfDecimals = 10.1
let b: Double = 10.1

both will a and b are Double, as NumberWithAlotOfDecimals is just an alias for Double.

Here's how to initialise:

//typealias tracks = [NSNumber:TrackInfo] // not this
var tracks = [NSNumber:TrackInfo]() // but this

Secondly, nullable objects should be properly handled

            if let artistName = song.artist?.name {
                trackInfo.artist = artistName
            }

and

            if let title = song.album.title {
                trackInfo.album = title
            }
            if let location = song.location {
                trackInfo.location = location
                print("Location: \(location)")
            }

instead of

            if song.artist?.name != nil {
                trackInfo.artist = song.artist?.name as! String
            }

Please do not use ! to force unwrap nullable objects as that will cause runtime crashes when the object is nil.

Lastly, this is the way to store key value into dictionary in Swift.

            let persistentID = song.persistentID
            //tracks.updateValue(song.persistentID, trackInfo) // not this
            tracks[persistentID] = trackInfo // this 
vincent
  • 227
  • 2
  • 13
  • I accepted your answer, but I ran into another problem when trying to print the album name of a playlist song. How would I fix that? – SouthernYankee65 Jun 17 '20 at 06:09
  • @SouthenYankee65 may I know more information about the problem? – vincent Jun 17 '20 at 06:41
  • Please see the update above. I figured out the playlist item problem. I'm at the last hurdle! :) – SouthernYankee65 Jun 17 '20 at 06:56
  • 1
    @SouthernYankee65 alright I have added the additional stuff! – vincent Jun 17 '20 at 13:11
  • This is awesome. Thank you so much! – SouthernYankee65 Jun 17 '20 at 14:51
  • So it appeared everything worked, but I noticed that the tracksInfo.persistentID property is not being updated before the trackInfo is being added to the dictionary. I tried using "trackInfo.persistentID = persistentID but the compiler complained "Expression type () is ambiguous without more context." – SouthernYankee65 Jun 17 '20 at 15:22
  • Nevermind. I see where I had persistentID in my struct as "String" but when I changed it to NSNumber() the error went away. – SouthernYankee65 Jun 17 '20 at 15:27
  • Hey @vincent, quick related question. Is there a way I can set the music library I want to use? Maybe through an extension? This way the user can specify via a configuration setting the library they wish to use. Some people have multiple music libraries, but it appears only the last opened library by iTunes/Music is being loaded. – SouthernYankee65 Sep 03 '20 at 18:04
  • @SouthernYankee65 apologies for the late reply. Been busy with other stuff lately. Having read through the API docs I don’t think it’s possible to select which library, unfortunately. – vincent Sep 05 '20 at 18:19
  • @SouthernYankee65 it appears that should you want to explicitly read from a specific library, you might have to do some kind of XML reading yourself, and not this API. – vincent Sep 05 '20 at 18:22
  • https://developer.apple.com/documentation/ituneslibrary Here’s for your reference. – vincent Sep 05 '20 at 18:23
  • Yeah, thanks for the reply. I found those docs and then realized I'm working with a dylib, which means I cannot use an extension. So, I've decided to go another route, which required me to take a whole new approach to my iTunes/Music library management. It's all good now. Appreciate it! – SouthernYankee65 Sep 06 '20 at 21:30