Get NSURLContentModificationDateKey from array of NSURL's

Hey All,

I’d like to try retrieving NSURLContentModificationDateKey from an array of NSURL’s with the end result being an NSDictionary with the POSIX path as key and NSURLContentModificationDateKey as the value.

If I’m using Music.app I’m starting out as per the below, and also I’d like to know if there is an option for building lists using ‘script o’? I should be able to figure that out after I can get an NSArray/NSDictionary of NSURLContentModificationDateKey. If I can get pass this hurdle I’ll hopefully be able to see which method is faster if ‘script o’ is possible…


use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions

tell application "Music"
	set trackNSURL to (current application's NSArray's arrayWithArray:(get location of every file track of library playlist 1))
	set trackPosixPath to trackNSURL's valueForKey:"path"
	set lastModifiedDict to current application's NSDictionary's dictionaryWithObjects:"***NSURLContentModificationDateKey goes here***" forKeys:trackPosixPath	
end tell

Thanks!

MrCee. The following should do what you want. I don’t use the Music app, so I used Finder to get the files. You’re probably aware of this, but you have to append “of me” if the handler is called from within the Music tell statement.

use framework "Foundation"
use scripting additions

set theFiles to (choose file with multiple selections allowed) -- a list of aliases
set theFiles to current application's NSArray's arrayWithArray:theFiles -- an array of URLs

set lastModifiedDic to getDictionary(theFiles)

on getDictionary(theFiles)
	set theDates to current application's NSMutableArray's new()
	repeat with aFile in theFiles
		set {theResult, aDate} to (aFile's getResourceValue:(reference) forKey:(current application's NSURLContentModificationDateKey) |error|:(missing value))
		(theDates's addObject:aDate)
	end repeat
	set thePaths to theFiles's valueForKey:"path"
	set theDictionary to current application's NSDictionary's dictionaryWithObjects:theDates forKeys:thePaths
end getDictionary

Pretty sure the iTunesFileTrack class has a
Modified Date property you can access as well.

NSObject has a function dictionaryWithValuesForKeys
https://developer.apple.com/documentation/objectivec/nsobject/1411319-dictionarywithvaluesforkeys

You could apply that to each track and then recreate
Your own dictionary from that dictionary (remap)

That is much simpler and takes the same amount of time.

use framework "Foundation"
use scripting additions

tell application "Music" to set {theFiles, theDates} to {location, modification date} of every file track of library playlist 1
set theFiles to (current application's NSArray's arrayWithArray:theFiles)'s valueForKey:"path"
set theDictionary to current application's NSDictionary's dictionaryWithObjects:theDates forKeys:theFiles

Hey, thanks so much, guys; it works well.

At the time, I was working on optimising the refresh part of a script to only refresh tags that were recently modified. In iTunes/Music.app, most of us have all seen the code to refresh all tracks in the GUI should the tags have been modified in another app. This takes forever (this is likely to be as just tested a 1 hour + 50 minute process) with over 15,000 tracks:


use AppleScript version "2.5" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions

tell application "Music"
set allTracks to (get every file track of library playlist 1)
	repeat with aTrack in allTracks
		try
			refresh aTrack
		end try
	end repeat
end tell

I’m not sure if what I’m seeing now is new; if anyone can shed some light, that would be great.

To refresh all tags in iTunes/Music.app across 15,000 tracks on Monterey, this code will do the trick in about 50 seconds. The GUI is fully updated in a flash, not each record at a time.
I’ve been getting the location of all tracks using this method for years but never thought to put this first above any refresh commands…


use AppleScript version "2.5" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions

tell application "Music"
	set trackLocation to current application's NSArray's arrayWithArray:(get location of every file track of library playlist 1)
end tell

Is it just me discovering this now, or is this new?
I have tested with Traktor and mp3tag updates, and any updates to a usual iTunes/Music.app tag field is picked up and reflected in the iTunes/Music.app GUI in an approximately 50-second, one update flash once the array has been built.

MrCee. I don’t know the answer to your question regarding tags.

FWIW, the following script date-limits the tracks that are refreshed. I only have 1 playlist with 3 songs, so it’s hard to know for certain if this script works correctly. The dialog is for testing purposes only and would be removed in actual use.

set refreshDate to (current date) - 100 * days -- date after which to refresh

tell application "Music"
	set theFileTracks to every file track of library playlist 1
	repeat with aFileTrack in theFileTracks
		set theModificationDate to modification date of aFileTrack
		if theModificationDate > refreshDate then
			display dialog "A track with a modification date of " & (theModificationDate as text) & " will be updated"
			refresh aFileTrack
		end if
	end repeat
end tell

@peavine that seems to be the exact method I was testing with your suggestion. But it seems we can get the job done in one sweep without a repeat loop now. Potentially? Previously accurate data across 15,000 track tags were never accurate without the repeat loop. Something has changed, I’m sure.

@technomorph, any chance you could please test this across your collection should a tag be updated in another app?

I see every tag in the iTunes/Music.app GUI update in a second using simply this…


tell application "Music"
-- This updates the GUI faster than a repeat loop, and therefore there is no need to consider last modified dates
   set trackLocation to current application's NSArray's arrayWithArray:(get location of every file track of library playlist 1)
end tell

MrCee. The Music app dictionary defines the refresh command as:

I’m not able to test this, but I would question whether running the refresh command and updating tags (by whatever method) perform the same task. If all you want to do is update tags, then it appears you’ve found a great solution.

BTW, if the repeat loop is a concern, you might try the following, which appears to work in my testing. It almost certainly will be slow compared with updating tags, though.

set refreshDate to (current date) - 100 * days -- date after which to refresh

tell application "Music"
	refresh (every file track of library playlist 1 whose modification date is greater than refreshDate)
end tell

Much appreciated, thank you.
I think most users would find the ‘refresh every file track of library playlist 1’ method extremely useful; it’s straightforward & effective.

The problem I found applying this method to an extensive music collection is that one apple event will cause Music.app to hang with a few other system issues as Activity Monitor shows Music.app is ‘Not Responding’, and I expect this result when the SSD is engaged with the one taxing Apple event with no break in processing.
Music.app does eventually respond after the event is complete. It is faster than a repeat loop, although I’m sure I have read about this better eloquently described on the forum, where the repeat loops break up the processing, which allows you to continue using Music.app. Until recently, I found repeat loops to be the lesser evil of the two.

My newfound current method of pulling in the location of every file track to an NSArray now ‘touches’ the GUI and updates it. I’ve tested this out many ways; it works as long as it’s one of the first scripted events. i.e. before a library folder import.

I’d like to know if anyone else is having tremendous success by reducing a tag refresh method by 99%. :slight_smile: Again, I haven’t seen this method suggested before, and I would be keen to know if something has changed behind the scenes in Music.app.

I think by using an asOBJc method is avoiding the
Multi-apple event issue.

As far as filtering the tracks to update (see next post on my workflow)
Anything that’s been changed extrernally as far as tags in iTunes.
I visually don’t really need to see any updates until I need to use those tracks.

If I am working straight in iTunes for a bit and not using my custom tag programs
Or apple scripts. And I want to update the tracks. I’ll highlight all
The tracks in the folder or parent folder and run Doug’s Scripts’s needle drop with a 0.5 second play length.

If I’m using any of my other workflows, then it’s possible that include a
Refresh command inside of those external scripts / functions.

In my ObjC code I’ve made the transitions :

  1. using a AppDelegate AppleScript
  2. using a AppleScript as a Class
  3. using AppleScripts with a bridging header class.
  4. using iTunes using Scripting Bridge
  5. using iTunes with the ITLibrary Framework

Using the ITLibrary Framework has been the fastest
as SB is still using apple events and I’ve found when I’m searching
For tracks SB doesn’t not like many of the NSPredicates I want to use.

Only issue with ITLibrary is the ability to change anything.