Why does using conditionals in this script use up all system memory?

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

If someone can tell me how to get the AppleScript tags to do their job I will edit the above to make the code more readable.

The closing tag should be

 not [/AppleScript]

Thanks, it must have triggered the spellchecker when I copied and pasted the post in the message box.

Hi Dentarthurdent.

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.

Thanks Robert. I’ll try to bear in mind the different methods of iterating over collections in future scriptbuilding.

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

Hi,

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.

Hi peavine.

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. :slight_smile:

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.

https://ibb.co/dQjS6Hv

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

Could you simply post any timing results in the thread, so that readers don’t have to click on unknown links? Thanks.

Marc Anthony wrote:

Done.