FREE TRIAL: Accelerate your app development career with Hacking with Swift+! >>

SOLVED: ITunesLibraryFramework

Forums > macOS

I am trying to figure out a few things with this code. My ultimate goal is to make this a class file in my project to return a dictionary of playlists (or even better just modify in place a CoreData stored playlist dictionary in the future).

I first retrieve the iTunes/Apple Music library and compare playlists to stored playlists within my app to check for any changes to the tracks in the playlist. If there are changes then I update the stored dictionary. As you can see below my structure is pretty simple - I use the name of a playlist as the key and the tracks of that playlist in the tracks dictionary as the value in the playlist dictionary. The tracks dictionary key is the persistendID of the song and the value is trackInfo.

First problem: The library.musicfolderlocation is coming up 'nil' and I don't know why. I'm obviously getting into the library because I'm able to see the playlists and process them.

let music = library.musicFolderLocation // <-- required
print("Music location: \(music)")
---------
Music location: nil

Second problem: Why are some the non-optional values in the trackInfo properties being stored as optionals, title being one of them (please see problem three output example)? How can I change the property value to be non-optionals for the properties that should not be optionals? I tried force-unwrapping but was told to not do that.

Third problem: (problem two solution may resolve third problem) You'll notice when the code runs and prints the tracks[pid] property that it includes:

Optional(lldb_expr_31.TrackInfo(title: <-- Why is it printing like this?
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

and the location: field is printing the Optional( also:

location: Optional(file:///...
          ^^^^^^^^^

Fourth problem: I would like to validate that all required trackInfo properties have values assigned to them before I commit the trackInfo data to the tracks dictionary and then clear/empty/reset the trackInfo property after commiting the trackInfo to the tracks dictionary.

Fifth problem: I takes approximately 10 seconds to process 46 playlists that have on average 68 tracks. Is there a more effecient way to do this?

I'm new to Swift and I'm having one heck of a time understanding. This is a complicated language over others I have learned, but I'm excited to learn Swift and do some great things in the future.

Here's the full code: (debugging print statements commented out to increase processing speed)

import Foundation
import iTunesLibrary

let library = try ITLibrary(apiVersion: "1.1")
let music = library.musicFolderLocation // <-- required
var tracks = [NSNumber:TrackInfo]()
var trackInfo = TrackInfo()
var playlistDict = [String():tracks]

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

let songs = library.allMediaItems
let playlists1 = library.allPlaylists

for playlist in playlists1 {
    if playlist.name.lowercased().contains("test-") {
        let name = playlist.name // - required
        //print("Processing Playlist: \(name.suffix(name.count-5))")
        for playlistItem in playlist.items {
            trackInfo.title = playlistItem.title // <--- required
            //print("\n\nNew Track-----------------\nTitle: \(trackInfo.title)")
            if let artistName = playlistItem.artist?.name {
                trackInfo.artist = artistName // <-- optional
            }
            //print("Artist: \(trackInfo.artist)")
            if let title = playlistItem.album.title {
                trackInfo.album = title // <-- optional
            }
            //print("Albumn Name: \(trackInfo.album)")
            trackInfo.totalTime = playlistItem.totalTime // <-- required
            //print("Total Time: \(trackInfo.totalTime)")
            trackInfo.year = playlistItem.year // <-- optional
            //print("Year: \(trackInfo.year)")
            if let location = playlistItem.location {
                trackInfo.location = location // <-- required
            }
            //print("Location: \(trackInfo.location!)")
            let persistentID = playlistItem.persistentID
            trackInfo.persistentID = persistentID // <-- required

            // Need to verify that all non-optional properties (title, totalTime, location, persistentID)
            // are set before assigning trackInfo to the tracks dictionary

            tracks[persistentID] = trackInfo
        }
        playlistDict[name] = tracks
    }
}

print("Music location: \(String(describing: music))") // <-- This is printing as an optional
let pid = NSNumber(7488805305623215538)
print(tracks[pid]?.title)  // <-- this is printing as an optional()
print("Playlist dictionary = \(playlistDict)")

I appreciate any feedback.

   

I used the exact code you supplied, just with all the print statements uncommented, and here's my results:

First problem: This code works fine for me.

Second problem: I don't see this issue either. The TrackInfo structs aren't being stored as Optionals when I run your code. The location field of TrackInfo is because that's how you've declared it in your struct. An implicitly-unwrapped Optional (like var location:URL!) is still an Optional.

Third problem: I don't see this output, likely because the pid doesn't match anything when I run it on my machine. But the reason the output is an Optional is because accessing an item of a dictionary returns an optional because the key you use to access it might not exist.

Fourth problem: Why clear/empty/reset the TrackInfo struct each time? You shouldn't be reusing the same object over and over. structs are cheap in Swift, just create a new one each time through the loop.

Fifth problem: I think that's down to your approach? Try something like the below, which takes a more functional approach to building your playlistDict. It was definitely faster on my machine.

import Foundation
import iTunesLibrary

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

let library = try ITLibrary(apiVersion: "1.1")
let music = library.musicFolderLocation // <-- required

let allPlaylists = library.allPlaylists.filter { $0.name.lowercased().contains("test-") }
let playlistDict = allPlaylists.reduce(into: [String: [NSNumber: TrackInfo]]()) { playlistResult, playlist in
    let tracksDict = playlist.items.reduce(into: [NSNumber:TrackInfo]()) { tracksResult, track in
        var trackInfo = TrackInfo()
        trackInfo.title = track.title
        trackInfo.artist = track.artist?.name ?? ""
        trackInfo.album = track.album.title ?? ""
        trackInfo.totalTime = track.totalTime
        trackInfo.year = track.year
        trackInfo.location = track.location
        trackInfo.persistentID = track.persistentID
        tracksResult[trackInfo.persistentID] = trackInfo
    }
    playlistResult[playlist.name] = tracksDict
}

print("Music location: \(String(describing: music))") // <-- This is printing as an optional
print("Playlist dictionary = \(playlistDict)")

   

Oh, and re: your note about music printing out as an Optional, that's because it is an Optional, as can be seen from the documentation:

var musicFolderLocation: URL? { get }

Although note that this property is deprecated and you should use mediaFolderLocation instead.

   

Thank you. Your edits have enlighted me about Swift's list comprehension. Speed isn't any better on my system, but the code is much leaner.

I see that the location URL is showing as:

Optional (file:///Volumes/Music-1/Music/Media.localized/Music/Cliff%20Nobles%20&%20Co_/Single/01%20The%20Horse.mp3))

However the actual location on disk is "/Volumes/Music-1/iTunes/iTunes Media/Music/Cliff Nobles & Co_/Single/01 The Horse.mp3" but if I try to use the URL to play the file using AVPlayer or similar this wouldn't play. This is why I need to figure out why the mediaFolderLocation (I changed it and it still doesn't work for me) is important. I can reconstruct the URL to the file if I could get that to work at least. I've read a lot about code signing plist items, etc. Just doesn't make sense to me that I can read the library but yet that property is showing as nil for me. Weird.

As always, thanks for your help.

   

Well, while I was looking for another playground I was using yesterday that did show the mediaFolderLocation I realized I was using a project on another computer via filesharing. I fired up the code locally and it was showing the correct full URL path. So, it must have something to do with remote libraries. Now I'm off to see the wizard...

   

Hacking with Swift is sponsored by Sentry

SPONSORED With Sentry’s error and performance monitoring for iOS you see mobile vitals that actually matter, can solve any latency issues quickly, and learn how each release is performing over time.

Get started

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.