It’s just a simple script to iterate over a user’s Music library tracks, resetting any ratings and ‘loved’ statuses. I had a similar problem with a script I wrote yesterday when I had it check the media kind of every track in turn. Removing that check fixed the script. I’m running this from inside Script Editor.
tell application "Music"
repeat with aTrack in tracks of library playlist 1
try
if loved of aTrack is true then
set loved of aTrack to false
end if
if album loved of aTrack is true then
set album loved of aTrack to false
end if
set rating of aTrack to 0
on error errStr number errorNumber
if errorNumber is equal to -128 then
-- user cancelled
return
else
log "Error number: " & errorNumber & "
" & "Description: " & errStr & "
" & "Track: " & name of aTrack
end if
end try
end repeat
end tell
The script works perfectly well if I simply set the property values without checking them first, like this:
tell application "Music"
repeat with aTrack in tracks of library playlist 1
try
set loved of aTrack to false
set album loved of aTrack to false
set rating of aTrack to 0
on error errStr number errorNumber
if errorNumber is equal to -128 then
-- user cancelled
return
else
log "Error number: " & errorNumber & "
" & "Description: " & errStr & "
" & "Track: " & name of aTrack
end if
end try
end repeat
end tell
I tried using
if (get loved of aTrack) is true then
and still ran into the memory leak (?) issue.
I’m relatively new to practical programming but have studied enough to (begin to) understand relatively low level explanations. Is this technically a memory leak or some other issue? Why does it happen? Am I missing something fundamental about how AppleScript should be used to test properties of objects?
Model: M1 MBA 2020 with 7-core GPU
AppleScript: 2.7
Browser: Safari 14.0.3
Operating System: Other
I can’t test this out in Music, but the value of aTrack in your repeat won’t be an actual ‘track’ but a more complex reference involving every track of the playlist. This has to be resolved by Music every time aTrack is used.
You may do better to resolve the ‘tracks’ part of the reference first and then loop through the returned list. No guarantees, though:
tell application "Music"
set theTracks to tracks of library playlist 1
repeat with aTrack in theTracks
-- etc.
Or:
tell application "Music"
repeat with aTrack in (get tracks of library playlist 1)
-- etc.
You can also use a counter to get the contents of each item in theTracks
tell application "Music" to set theTracks to tracks of library playlist 1
repeat with i from 1 to count theTracks
set aTrack to item i of theTracks
try
tell application "Music"
set loved of aTrack to false
set album loved of aTrack to false
set rating of aTrack to 0
end tell
on error errStr number errorNumber
if errorNumber is equal to -128 then
-- user cancelled
return
else
log "Error number: " & errorNumber & "" & "Description: " & errStr & "" & "Track: " & name of aTrack
end if
end try
end repeat
I also moved the portions not needed in the tell to outside the tell block
Thank you Nigel, I’ll give this a try. Do you happen to know if I can read about this in the official AppleScript documentation? It doesn’t seem intuitive to me to write the code like in your example so it would help if I could understand in detail the way objects are accessed/referenced in AppleScript.
Hi. Perhaps it’s in the documentation somewhere, but having the event log open to see live feedback is one way to learn. Observe the difference with and without the explicit ‘get.’
I’m not sure if the original problem may be a bug or simply a list inflation product on a really large library. How many tracks are you iterating? You can also try an index approach that forgoes list references.
tell application "iTunes" to repeat with counter from 1 to count tracks
tell track counter
if (loved is true) or (album loved is true) then set {loved, album loved} to {false, false}
set rating to 0
end tell
end repeat
The OP can perform this task without involving the repeat loop with tracks, as I see.
tell application "Music" to tell tracks of library playlist 1
set loved to false
set album loved to false
set rating to 0
end tell
Note that the script’s result in the Script Debugger displays the tracksCount Music application instances.
This looks like a bug that seems to be related to the OP’s memory overflow problem. I don’t know how to explain this. Maybe, the memory manager not have time to free unnecessary Music instances due to instant multiple calls to the application. The following script apparently solves this problem:
tell application "Music" to tell tracks of library playlist 1
set {loved, album loved, rating} to {false, false, 0}
end tell
It is equivalent to following. It seems, the parentheses helps to return control to AppleScript:
tell application "Music" to tell tracks of library playlist 1
set loved to false
set album loved to false
set {rating} to {0}
end tell
This is a bit confusing to me as well. I always understood that the values (counter, contents of a list, etc) in a repeat loop are set when execution of the repeat loop begins. Perhaps Nigel means something different when he states:
Unfortunately, I do not have Music set up and can’t easily test this in Script Editor as Marc Anthony suggests.
That’s the case in the alternatives I suggested: get a list of the playlist tracks and loop through the list. robertferns’s script does the same. Marc’s indexes the tracks in “iTunes” itself rather than in a list.
Dentarthurdent’s (a Hitchhiker’s Guide to the Galaxy fan? :)) original loops through the specifier ‘tracks of library playlist 1’, so the value of ‘aTrack’ is successively ‘item 1 of every track of library playlist 1 of application “Music”’, ‘item 2 of every track of playlist 1 of application “Music”’, and so on. This whole caboodle gets sent to Music to interpret every time ‘aTrack’ is used, since it’s a specifier for something in Music. And ‘aTrack’ is used quite a lot in the original script, so it’s possible this may be causing congestion.
Just a note to say thanks to everyone who’s contributed, and that when I get a chance I will time how long the script takes to run with the different constructs and report the results here.
Subjectively, when I ran Nigel’s version, it took just as long (perhaps 20-25 minutes), but curiously all the track IDs that showed up in the Replies box were in the 60,000s to 90,000s (I have just under 30,000 tracks).
Thanks Nigel for the explanation. Always something new to learn.
Because I couldn’t use Music, I instead used Preview to better illustrate how this works:
# TEST SCRIPT 1
tell application "Preview"
repeat with aDocument in (name of every document)
log aDocument
end repeat
end tell
aDocument --> item 2 of name of every document of application "Preview"
# TEST SCRIPT 2
tell application "Preview"
repeat with aDocument in (get name of every document)
log aDocument
end repeat
end tell
aDocument --> item 2 of {"Test 1.pdf", "Test 2.pdf"}
Sorry, got sidetracked for a few weeks. For those who are curious/interested, here’s a loose comparison of the iterative constructs and the time it took to run the scripts containing them.
Edit: posting results in text form as requested. The reason I didn’t do this was because I can’t find a way of formatting them as a table using BBCode. I hope it doesn’t display too badly for you formatted with spaces.
The library size is approximately 30,000 tracks.
Construct Operations on aTrack Time taken (s)
tell application “Music” to tell tracks of library playlist 1 set {rating} to {0} 3
tell application “Music” to tell tracks of library playlist 1 set {album rating, 98
rating} to {0, 0}
set theTracks to tracks of library playlist 1 set rating to 0 152
repeat with aTrack in theTracks
set theTracks to tracks of library playlist 1 set rating to 0 414
repeat with aTrack in theTracks set album rating to 0
repeat with aTrack in tracks of library playlist 1 set rating to 0 929
repeat with aTrack in tracks of library playlist 1 set rating to 0 2800
set album rating to 0