Query joined/matching NSArray's. Return lookup value at same index

I’m looking for ways to query/filter/lookup/update all music ID3 tags from Music.app (iTunes). Essentially I’d like to be able to query all arrays like a database without a repeat loop, working with 20+ arrays which all have this same count/index.

I’m thinking that coercing back to AppleScript as text & list should come last, after I have completed every evaluation and updated arrays before I write to file.

If anyone can provide some suggestions as how to go about this while remaining optimised as ASOC NSArray (without repeat loops if possible) that would be greatly appreciated.

In my code below, I wasn’t sure if I needed to first join arrays, using one as the predicate to return the value of other, or if there is a more straight forward way. As an example, I’m trying to find ‘theID’ of all files where ‘thePosixPath’ is NSNull.

tell application "Music"
	set theID to current application's NSArray's arrayWithArray:(get persistent ID of every file track in library playlist 1)
	set theLocation to current application's NSArray's arrayWithArray:(get location of every file track in library playlist 1)
	set theName to current application's NSArray's arrayWithArray:(get name of every file track in library playlist 1)
	set theArtist to current application's NSArray's arrayWithArray:(get artist of every file track in library playlist 1)
end tell

--create custom arrays	
set thePosixPath to theLocation's valueForKey:"path"
set theParentFolder to theLocation's valueForKeyPath:"URLByDeletingLastPathComponent.lastPathComponent"

-- work in progress, trying to join a few NSArrays.... 
set joinedArray to {theID:theID, thePosixPath:thePosixPath, theName:theName, theArtist:theArtist}

its itemsWhose:"thePosixPath" isEqualTo:"" inList:joinedArray

on itemsWhose:theProperty isEqualTo:theValue inList:theList
	set theArray to current application's NSArray's arrayWithArray:theList
	set thePred to current application's NSPredicate's predicateWithFormat:"%K = %@" argumentArray:{theProperty, theValue}
	return (theArray's filteredArrayUsingPredicate:thePred)
end itemsWhose:isEqualTo:inList:

You’ll have to build an array of dictionaries, and that’s going to require a repeat loop*. Assuming the data doesn’t change too often, it might make sense to do that and write it to a file, assuming you want to search it regularly.

  • There are some shortcuts in my BridgePlus script library, using columnsToRowsIn:error: and subarraysIn:asDictionariesUsingLabels:error:, which would do it quite fast.

You may want to look into using the ITLibrary Framework.
it’s super fast.

https://developer.apple.com/documentation/ituneslibrary?language=objc

You can get all the Media Items from the library.
Then it’s recommended to filter their tracks array example to only “Songs”

-(void)loadAllTracks {
    NSArray* tracks = self.iTunesLib.allMediaItems; //  <- NSArray of ITLibMediaItem
    NSPredicate *songPred = [NSPredicate predicateWithFormat:@"mediaKind == %d", 2];
    self.allTracks = [tracks filteredArrayUsingPredicate];
}

You can then apply any predicate to the allTracks array.
You now have a much smaller filteredTracks array.
Which your going to have to enumerate.

The ITLibMediaEntity Class has a function (private?):

- (void)enumerateValuesForProperties:(id)arg1 usingBlock:(CDUnknownBlockType)arg2;

I use it (code in OBJC like this for each track):

-(NSMutableDictionary*)onlyTheseProperties:(NSArray*)onlyProps forMediaItem:(id)aTrack {
    ITLibMediaItem* aMediaItem = aTrack;

    __block NSMutableDictionary* mediaDict = [NSMutableDictionary new];
    NSSet* onlyProperties = [NSSet setWithArray:onlyProps];
    [aMediaItem enumerateValuesForProperties:onlyProperties
                                  usingBlock:^(NSString * _Nonnull property, id  _Nonnull value, BOOL * _Nonnull stop) {
                                      NSString* curKey = property;
                                      id curValue = value;
                                      [mediaDict setValue:curValue forKey:curKey];
                                  }];
    return mediaDict;
}

The properties and Keys can be found here:
https://developer.apple.com/documentation/ituneslibrary/itlibmediaitem?language=objc

PS I’m not sure if it’s just using the ITLibrary Framework or just OS X security scopes.
But I had difficulty accessing the location. As I believe it’s a security scoped book mark.
I had to do things like setting up sandbox privileges and then access like this:

-(NSURL*)libLocationOfMediaItem:(id)aMediaItem {
    ITLibMediaItem* aITLibMITrack = aMediaItem;

    NSURL* testURL1A = aITLibMITrack.location;
    [testURL1A startAccessingSecurityScopedResource];
    NSLog(@"testURL1A is :%@", testURL1A);

    NSString* tempLocSting = [[testURL1A path] copy];
    NSLog(@"tempLocSting is :%@", tempLocSting);

    NSURL* testURL1A2 = [NSURL URLWithString:tempLocSting];
    [testURL1A stopAccessingSecurityScopedResource];

    NSLog(@"testURL1A2 after security is :%@", testURL1A2);
       return testURL1A2 ;
 }

it was finickey. I found the best way was to use this (again a property not publicly documented)

-(NSURL*)libLocationOfMediaItem:(id)aMediaItem {
    ITLibMediaItem* aITLibMITrack = aMediaItem;

    NSURL* testURL3A = [aITLibMITrack valueForKey:@"URL"];
    NSLog(@"testURL3A is :%@", testURL3A);

       return testURL3A ;
 }

But also I’m not sure how that will work out if the returned value is NULL on the URL property.

When I was doing straight appleScript OBJC stuff.
Shane’s BridgePlus script library and the Functions he listed were super valuable and easy to use!!!

Interesting about using the ITLibrary framework. The documents say you app must be code-signed to access it, and that was indeed the case when I last tried it, which was probably when it came out (10.13?). But trying just now on 10.15, that seems no longer to be the case. Even for location:

use framework "Foundation"
use framework "iTunesLibrary"
use scripting additions
set {theLib, theError} to current application's ITLibrary's libraryWithAPIVersion:"1.0" |error|:(reference)
set theFile to theLib's allPlaylists()'s firstObject()'s |items|()'s firstObject()'s location() as «class furl»

It’s not private (it’s in ITLibMediaEntity), but being block-based it’s useless from AppleScript.