I was hoping someone can point me in the right direction relating to Music.app AppleScript or ASOC
As efficiently as possible I’d like to identify and convert tags that have a diacritic (non-ASCII) “accent,” “diacritical mark,” or simply “accentuation” and replace this with standard ASCII. Same as what we can do in shell command on macOS using:
From everything I have seen, lists work so much quicker than relying on the ASOC bridge, But I just thought I would reach out because I haven’t moved further with this. My pure AppleScript below only retrieves the list to be converted at this stage, I haven’t converted it yet. Not sure about the most effective way to go about this over many files. And this is just the trackName tag.
tell application "Music"
set trackID to (get persistent ID of every file track of library playlist 1)
set trackName to (get name of every file track of library playlist 1)
-- Initialize a list to hold tracks that need conversion
set tracksNeedingConversion to {}
-- Iterate through each track name
repeat with aTrack in trackName
-- Check if the track name contains non-ASCII characters
if my containsNonASCII(aTrack) then
-- Add the track name to the list of tracks needing conversion
set end of tracksNeedingConversion to contents of aTrack
end if
end repeat
-- Display the result
return tracksNeedingConversion
end tell
-- Handler to check if a string contains non-ASCII characters
on containsNonASCII(theString)
-- Iterate through each character in the string
repeat with i from 1 to (length of theString)
set theChar to character i of theString
set asciiValue to (id of theChar)
-- Check if the ASCII value is outside the standard ASCII range
if asciiValue > 127 then
return true
end if
end repeat
return false
end containsNonASCII
set badNameList to {}
repeat with i from 128 to 255
set thisHighAsciiCharacter to ASCII character i
tell application "Music"
tell playlist "Disco"
set the end of badNameList to (every track whose name contains thisHighAsciiCharacter)
end tell
end tell
end repeat
But music.app fails to correctly filter the tracks.
This reduces repeat loops by checking that the trackName doesn’t contain any of the high ascii characters all at once using text item delimiters.
set highAsciiList to {}
repeat with i from 128 to 255
set the end of highAsciiList to ASCII character i
end repeat
set badNameList to {}
tell application "Music"
tell playlist "Disco"
set trackList to every track
repeat with thisTrack in trackList
tell thisTrack
set trackName to name
set AppleScript's text item delimiters to highAsciiList
if length of (text items of trackName) > 1 then
set the end of badNameList to trackName
(*--or fix it immidiately if you just want to remove high asccii characters rather than replace them.
copy (text items of trackName) to newName
set AppleScript's text item delimiters to ""
set newName to newName as text
set name to newName*)
end if
end tell
end repeat
end tell
end tell
Not sure to understand what you want.
If you want to rename the tracks, try this:
use framework "Foundation"
use scripting additions
tell application "Music"
set theTracks to tracks of playlist "Disco"
repeat with aTrack in theTracks
set aName to name of aTrack
set aString to (current application's NSString's stringWithString:aName)
set aString to (aString's stringByApplyingTransform:(current application's NSStringTransformStripDiacritics) |reverse|:false) as string
set name of aTrack to aString
end repeat
end tell
But if you just need to build a list of tracks whose name contains diacriticals:
use framework "Foundation"
use scripting additions
tell application "Music"
set theTracks to tracks of playlist "Disco"
set theIDs to {}
repeat with aTrack in theTracks
set aName to name of aTrack
set aString to (current application's NSString's stringWithString:aName)
set aString to (aString's stringByApplyingTransform:(current application's NSStringTransformStripDiacritics) |reverse|:false) as string
if aName ≠ aString then set end of theIDs to persistent ID of aTrack
end repeat
end tell
return theIDs
This is great, and you clearly demonstrated how to achieve and automate the highAsciiList . I was asking myself how yo go about that also, and wish we could filter the way you mentioned first. with the same principal in mind I was wondering if this could be done using predicate in ASOC, but it looks like there is a better solution for that already posted.
But I do want to compare the 2 solutions, so I am going to run your suggestion over my entire library to collect badNameList, but what I’m noticing is that there are many records in the badNameList generated that don’t have a text item in highAsciiList that are being returned.
tell application "Music"
set highAsciiList to {}
repeat with i from 128 to 255
set the end of highAsciiList to ASCII character i
end repeat
set badNameList to {}
set trackList to every track in library playlist 1
repeat with thisTrack in trackList
tell thisTrack
set trackName to name
set AppleScript's text item delimiters to highAsciiList
if length of (text items of trackName) > 1 then
set the end of badNameList to trackName
(*--or fix it immidiately if you just want to remove high asccii characters rather than replace them.
copy (text items of trackName) to newName
set AppleScript's text item delimiters to ""
set newName to newName as text
set name to newName*)
end if
end tell
end repeat
end tell
It’s exactly what I was looking for. You have used stringByApplyingTransform and NSStringTransformStripDiacritics in a way that just simply works.
Where I struggle is reading the Foundation documentation online or in Xcode and transforming this in AppleScript, so thanks for showing me the structure of exactly what I set out to achieve.
I’m currently running a comparison between the ASOC method and the purely AppleScript lists. Sometimes it’s not great for me to use ASOC in iterations over 10,000 tracks.
So while I’m here, do you know of any solution to perhaps filter an array using predicate for diacritics? Before this post on macscripter.net, I was originally headed down the path of having a dictionary with key: persistent ID value: name trying to use predicate to filter the dictionary, and return the ID’s but with your solution I can have that in say 30 seconds…
Are there any other ways?
use framework "Foundation"
use scripting additions
tell application "Music"
set theTracks to tracks of library playlist 1
set theIDs to {}
set theNewName to {}
set theOldName to {}
repeat with aTrack in theTracks
set aName to name of aTrack
set aString to (current application's NSString's stringWithString:aName)
set aString to (aString's stringByApplyingTransform:(current application's NSStringTransformStripDiacritics) |reverse|:false) as string
if aName ≠ aString then
set end of theIDs to persistent ID of aTrack
set end of theOldName to aName
set end of theNewName to aString
end if
end repeat
end tell
Converting a list to NSArray is almost instantaneous.
I don’t know if will be faster, because it depends on how quickly Music will build the first list:
use framework "Foundation"
use scripting additions
tell application "Music"
set theTracks to {name, id} of tracks of library playlist 1
set theDict to current application's NSDictionary's dictionaryWithObjects:(item 2 of theTracks) forKeys:(item 1 of theTracks)
set theData to theDict's descriptionInStringsFileFormat()'s dataUsingEncoding:(current application's NSUTF8StringEncoding)
set theString to (current application's NSString's alloc()'s initWithData:theData encoding:(current application's NSNonLossyASCIIStringEncoding))
set theArray to theString's componentsSeparatedByString:(";" & linefeed)
set theTracks to (theArray's filteredArrayUsingPredicate:(current application's NSPredicate's predicateWithFormat:"self MATCHES %@" argumentArray:{".*[À-ɍ].*"}))
repeat with aTrack in theTracks
set {aName, anID} to (aTrack's componentsSeparatedByString:" = ")
set aString to (aName's stringByApplyingTransform:(current application's NSStringTransformStripDiacritics) |reverse|:false)
set name of track id (anID as integer) to (aString as string)
end repeat
end tell
This is great, I was exactly stuck at how to define predicateWithFormat
set theTracks to (theArray's filteredArrayUsingPredicate:(current application's NSPredicate's predicateWithFormat:"self MATCHES %@" argumentArray:{".*[À-ɍ].*"}))
It’s been a while, so where did you get the regex for argumentArray:{“.[À-ɍ].”})) ?
I did apply this, and everything ran smoothly although now running a second time, the script is stuck on a missing value contained in: componentsSeparatedByString potentially because everything IS fixed. I can deal with that. Really keen to understand argumentArray with the higher ASCII 128+ which I have never included in regex before.
I work through a few aspects tonight to understand it better. Removed quotation marks around the saved name and it seems this no longer fails on the second run if there is nothing to adjust, and using the persistent ID instead now.
use framework "Foundation"
use scripting additions
tell application "Music"
set theTracks to {name, persistent ID} of tracks of library playlist 1
set theDict to current application's NSDictionary's dictionaryWithObjects:(item 2 of theTracks) forKeys:(item 1 of theTracks)
set theData to theDict's descriptionInStringsFileFormat()'s dataUsingEncoding:(current application's NSUTF8StringEncoding)
set theString to (current application's NSString's alloc()'s initWithData:theData encoding:(current application's NSNonLossyASCIIStringEncoding))
set theArray to theString's componentsSeparatedByString:(";" & linefeed)
set theTracks to (theArray's filteredArrayUsingPredicate:(current application's NSPredicate's predicateWithFormat:"self MATCHES %@" argumentArray:{".*[À-ɍ].*"}))
repeat with aTrack in theTracks
set {aName, anID} to (aTrack's componentsSeparatedByString:" = ")
set strippedName to (aName's stringByApplyingTransform:(current application's NSStringTransformStripDiacritics) |reverse|:false)
set strippedName to (strippedName's stringByTrimmingCharactersInSet:(current application's NSCharacterSet's characterSetWithCharactersInString:"\"")) as text
set name of track id (anID as integer) to (strippedName as text)
end repeat
end tell
This is all happening nicely. I’d love to know about the regex if you have time.
And thank you both for your responses.
If the error is on componentsSeparatedByString it’s rather that the playlist has no track.
use framework "Foundation"
use scripting additions
tell application "Music"
set theTracks to {name, id} of tracks of library playlist 1
set theDict to current application's NSDictionary's dictionaryWithObjects:(item 2 of theTracks) forKeys:(item 1 of theTracks)
set theData to theDict's descriptionInStringsFileFormat()'s dataUsingEncoding:(current application's NSUTF8StringEncoding)
set theString to (current application's NSString's alloc()'s initWithData:theData encoding:(current application's NSNonLossyASCIIStringEncoding))
if theString = missing value then return "Unable to read tracks properties"
set theString to (theString's stringByReplacingOccurrencesOfString:"\"" withString:"" options:1024 range:{0, theString's |length|()})
set theArray to theString's componentsSeparatedByString:(";" & linefeed)
set theTracks to (theArray's filteredArrayUsingPredicate:(current application's NSPredicate's predicateWithFormat:"self MATCHES %@" argumentArray:{".*[À-ɍ].*"}))
if theTracks's |count|() = 0 then return "All tracks are OK"
repeat with aTrack in theTracks
set {aName, anID} to (aTrack's componentsSeparatedByString:" = ")
set aString to (aName's stringByApplyingTransform:(current application's NSStringTransformStripDiacritics) |reverse|:false)
set name of track id (anID as integer) to (aString as string)
end repeat
end tell
Perfect!
I made one minor inclusion to strip out the double quotes which were being written to name during AppleScript conversion, let me know if there was a better way to do it…
use framework "Foundation"
use scripting additions
tell application "Music"
set theTracks to {name, persistent id} of tracks of library playlist 1
set theDict to current application's NSDictionary's dictionaryWithObjects:(item 2 of theTracks) forKeys:(item 1 of theTracks)
set theData to theDict's descriptionInStringsFileFormat()'s dataUsingEncoding:(current application's NSUTF8StringEncoding)
set theString to (current application's NSString's alloc()'s initWithData:theData encoding:(current application's NSNonLossyASCIIStringEncoding))
if theString = missing value then return "Unable to read tracks properties"
set theArray to theString's componentsSeparatedByString:(";" & linefeed)
set theTracks to (theArray's filteredArrayUsingPredicate:(current application's NSPredicate's predicateWithFormat:"self MATCHES %@" argumentArray:{".*[À-ɍ].*"}))
if theTracks's |count|() = 0 then return "All tracks are OK"
repeat with aTrack in theTracks
set {aName, anID} to (aTrack's componentsSeparatedByString:" = ")
set aString to (aName's stringByApplyingTransform:(current application's NSStringTransformStripDiacritics) |reverse|:false)
set aString to (aString's stringByTrimmingCharactersInSet:(current application's NSCharacterSet's characterSetWithCharactersInString:"\"")) as text
set name of track id (anID as integer) to (aString as string)
end repeat
end tell
This is occurring because ascii 167 is resolving to “ss”, 222 resolves to “fi” and 223 to “fl”.
set AppleScript's text item delimiters to (ASCII character 167)
AppleScript's text item delimiters --"ß"
text items of "Mississippi" -->{"Mi", "i", "ippi"}
This is unexpected behavior from my point of view. Can anyone explain why this behavior is happening?
ASCII character 222 -->"fi"
ASCII character 223 -->"fl"
ASCII character 167 -->"ß" displays as Eszett but matches "ss" when used in text item delimiters.
--In German orthography, the letter ß, called Eszett or scharfes S, represents the /s/ phoneme in Standard German when following long vowels or diphthongs. The name Eszett combines the names of the letters of ⟨s⟩ (Es) and ⟨z⟩ (Zett) in German. The character's Unicode names in English are sharp s[1] and eszett. The letter is only used in German, and can be replaced with ⟨ss⟩
Revised code that avoids these three high ascii values.
set highAsciiList to {}
repeat with i from 128 to 255
if i is not in {167, 222, 223} then set the end of highAsciiList to ASCII character i
end repeat
set badNameList to {}
tell application "Music"
tell playlist "Disco"
set trackList to every track
repeat with thisTrack in trackList
tell thisTrack
set trackName to name
set AppleScript's text item delimiters to highAsciiList
if length of (text items of trackName) > 1 then
set the end of badNameList to trackName
(*--or fix it immidiately if you just want to remove high asccii characters rather than replace them.
copy (text items of trackName) to newName
set AppleScript's text item delimiters to ""
set newName to newName as text
set name to newName*)
end if
end tell
end repeat
end tell
end tell
set theString to (current application's NSString's alloc()'s initWithData:theData encoding:(current application's NSNonLossyASCIIStringEncoding))
TheString is always missing value.
theData is set to “(NSData) {length = 128, bytes = 0x2222203d 20313339 39383b0a 22546865 … 436f6e74 726f6c20 }<<truncated at 128 bytes out of 141060>>” by the previous line.
I did actually find the cause of missing value, one of my tracks is named “Beautiful Love (12\” Edit)" - with one backslash when looking through ASOC data collected and it’s not the only one that is escaping a double quote. It basically breaks some of this code and it makes sense why. So I’m working on the logic to deal with it as we speak.
Another thing I’m working on is recreating the dictionary because I have many tracks that do appear in multiple locations and they will have the exact same name, I usually remove the locations after a while but while I’m working on many tracks with the same name in the entire library playlist 1, it wasn’t a good idea for me to use the trackName as key in the dictionary. So I’m using persistent ID as the key now, and trackName as the value:
So this is how I have rewritten some of the above, but still yet to remove the backslash and also try and not remove the double quote next to e.g. 12" in a name that contains “Track Name 12” Extended".
I’m really liking your approach to working with the dictionary without having to iterate and filter each value which last time I tried that for predicate to work, it took too long.
Correct me if I’m incorrect, but predicate. only works quickly if you are filtering dictionary keys? And if filtering by value you would have to iterate over each?
This is where I’m at, working on the missing value part.
use framework "Foundation"
use scripting additions
tell application "Music"
-- Fetch the tracks' persistent IDs and names
set {trackIDs, trackNames} to {persistent ID, name} of tracks of library playlist 1
-- Create a dictionary with persistent IDs as keys and names as values
set theDict to current application's NSDictionary's dictionaryWithObjects:trackNames forKeys:trackIDs
-- Convert the dictionary to NSString format
set theData to theDict's descriptionInStringsFileFormat()'s dataUsingEncoding:(current application's NSUTF8StringEncoding)
set theString to (current application's NSString's alloc()'s initWithData:theData encoding:(current application's NSNonLossyASCIIStringEncoding))
--if theString = missing value then return "Unable to read tracks properties"
-- Remove double quotes from theString
set theString to (theString's stringByReplacingOccurrencesOfString:"\"" withString:"" options:1024 range:{0, theString's |length|()})
-- Split the NSString into an array
set theArray to theString's componentsSeparatedByString:(";" & linefeed)
-- Filter the array to find tracks with diacritics
set theTracksToFix to (theArray's filteredArrayUsingPredicate:(current application's NSPredicate's predicateWithFormat:"self MATCHES %@" argumentArray:{".*[À-ɍ].*"}))
if theTracksToFix's |count|() = 0 then return "All tracks are OK"
-- Initialize the list of names to fix
set namestofix to {}
-- Process each track
repeat with aTrack in theTracksToFix
-- Split the track info into persistent ID and name
set {anID, aName} to (aTrack's componentsSeparatedByString:" = ")
-- Strip the diacritics from the name
set strippedName to (aName's stringByApplyingTransform:(current application's NSStringTransformStripDiacritics) |reverse|:false)
-- Find the track using persistent ID and update its name
set theTrack to (first track of library playlist 1 whose persistent ID is anID as string)
-- NOT DOING THIS UPDATE JUST YET : set name of theTrack to (strippedName as string)
-- Add the fixed name to the list
set end of namestofix to strippedName as text
end repeat
-- Return the list of fixed names
return namestofix
end tell
These quotes inside a song name invalidate the use of descriptionInStringsFileFormat().
This script may be slower but should not generate issues with name strings.
use framework "Foundation"
use scripting additions
tell application "Music"
set theTracks to tracks of library playlist 1
set theTracks to current application's NSArray's arrayWithArray:theTracks
set theArray to current application's NSMutableArray's new()
repeat with aTrack in theTracks
set aName to name of aTrack
set anID to id of aTrack
(theArray's addObject:(current application's NSDictionary's dictionaryWithObjects:{aName, anID} forKeys:{"trackName", "trackID"}))
end repeat
set tracksToFix to (theArray's filteredArrayUsingPredicate:(current application's NSPredicate's predicateWithFormat:"trackName MATCHES %@" argumentArray:{".*[À-ÿ].*"}))
repeat with aFix in tracksToFix
set aName to (aFix's valueForKey:"trackName")
set anID to (aFix's valueForKey:"trackID")
set aString to (aName's stringByApplyingTransform:(current application's NSStringTransformStripDiacritics) |reverse|:false)
set name of track id (anID as integer) to (aString as string)
end repeat
end tell
There’s a possible workaround. Until we find another issue!
use framework "Foundation"
use scripting additions
tell application "Music"
set theTracks to {name, ID} of tracks of library playlist 1
set theDict to current application's NSDictionary's dictionaryWithObjects:(item 2 of theTracks) forKeys:(item 1 of theTracks)
set theDescro to theDict's descriptionInStringsFileFormat()
set theDescro to (theDescro's stringByReplacingOccurrencesOfString:"(?m)^\"|\"(?= =)" withString:"" options:1024 range:{0, theDescro's |length|()}) -- trim double quotes
set theDescro to (theDescro's stringByReplacingOccurrencesOfString:"\\\\\"" withString:"mn" options:1024 range:{0, theDescro's |length|()}) -- convert stings like 12" to 12mn
set theData to theDescro's dataUsingEncoding:(current application's NSUTF8StringEncoding)
set theString to (current application's NSString's alloc()'s initWithData:theData encoding:(current application's NSNonLossyASCIIStringEncoding))
if theString = missing value then return "Unable to read tracks properties"
set theString to (theString's stringByReplacingOccurrencesOfString:"\"" withString:"" options:1024 range:{0, theString's |length|()}) -- NSRegularExpressionSearch
set theArray to theString's componentsSeparatedByString:(";" & linefeed)
set theTracks to (theArray's filteredArrayUsingPredicate:(current application's NSPredicate's predicateWithFormat:"self MATCHES %@" argumentArray:{".*[À-ɍ].*"}))
if theTracks's |count|() = 0 then return "All tracks are OK"
repeat with aTrack in theTracks
set {aName, anID} to (aTrack's componentsSeparatedByString:" = ")
set aString to (aName's stringByApplyingTransform:(current application's NSStringTransformStripDiacritics) |reverse|:false)
set name of track id (anID as integer) to (aString as string)
end repeat
end tell
I have to say, I truly appreciate your efforts with the constants and methods you have provided above.
I should be able to do the rest, and I’ll get back to this post.
I do like to use persistent ID though. I can’t trust the track ID especially if deletions are happening prior or if sharing files across separate testing libraries. I’m sure this can somehow be done in AOC without relying on bridge conversion and iteration, with conversion to list/text as the final step. I’ll see if my regex is going to slow down things there.
You probably know where I’m headed with all of this… if we can fix up the diacritics in the name tag and not cause a missing value error, then I’m doing this for every other tag
I have the following tonight. I have achieved implementing some different techniques.
Seem’s to be handling my 12" Extended versions okay and converting them. I’m liking the fact that it works firstly. working on optimization next
use framework "Foundation"
use scripting additions
tell application "Music"
-- Fetch the tracks' persistent IDs and names. Names should not be the key in dictionary
set {trackIDs, trackNames} to {persistent ID, name} of tracks of library playlist 1
-- Create a dictionary with persistent IDs as keys and names as values
set theDict to current application's NSDictionary's dictionaryWithObjects:trackNames forKeys:trackIDs
-- Convert the dictionary values (track names) to an array
set theArray to theDict's allValues()
-- Define the predicate to filter names containing diacritics
set theTracksToFix to (theArray's filteredArrayUsingPredicate:(current application's NSPredicate's predicateWithFormat:"self MATCHES %@" argumentArray:{".*[À-ɍ].*"}))
-- Create a new mutable dictionary to store the filtered key-value pairs
set filteredDict to current application's NSMutableDictionary's dictionary()
repeat with i from 1 to (count theTracksToFix)
set thisTrackName to item i of theTracksToFix
-- Find the key (persistent ID) corresponding to this track name
set theseKeys to (theDict's allKeysForObject:thisTrackName)
-- Assuming there is one unique key per track name
set thisKey to item 1 of theseKeys
-- Add the key-value pair to the new dictionary
(filteredDict's setObject:thisTrackName forKey:thisKey)
end repeat
set keysArray to filteredDict's allKeys()
-- Iterate through each persistent ID in the keys array
repeat with a from 1 to (count of keysArray)
set originalName to (filteredDict's objectForKey:(item a of keysArray))
set transformedName to (originalName's decomposedStringWithCanonicalMapping's stringByFoldingWithOptions:(current application's NSDiacriticInsensitiveSearch) locale:(missing value))
set trackID to (item a of keysArray) as string
set name of (some track of library playlist 1 whose persistent ID is trackID) to (transformedName as string)
end repeat
end tell
So this is what I ended up doing. It does deal with almost everything. Not sure if my transforms are overkill with latin and then diacritics but apparently there is some latin that can be left behind. If anyone wants to chime in with improvements I’d be glad to test them out.
use framework "Foundation"
use scripting additions
tell application "Music"
activate
set diacriticIDs to current application's NSMutableArray's array()
set diacriticNames to current application's NSMutableArray's array()
set diacriticArtists to current application's NSMutableArray's array()
-- Fetch the tracks' persistent IDs, names, and artists
set {trackIDs, trackNames, trackArtists} to {persistent ID, name, artist} of tracks of library playlist 1
-- Create a dictionary with persistent IDs as keys and {name, artist} pairs as values
set theDict to current application's NSMutableDictionary's dictionary()
repeat with i from 1 to (count of trackIDs)
set trackID to item i of trackIDs as text -- Ensure trackID is in text format
set trackName to item i of trackNames as text
set trackArtist to item i of trackArtists as text
-- Add the name and artist pair to the dictionary with the persistent ID as the key
(theDict's setObject:{trackName, trackArtist} forKey:trackID)
end repeat
-- Define the predicate to filter names or artists containing diacritics
set predicate to current application's NSPredicate's predicateWithFormat:"self MATCHES %@" argumentArray:{".*[À-ɍ].*"}
-- Create a new mutable dictionary to store the filtered key-value pairs
set filteredDict to current application's NSMutableDictionary's dictionary()
-- Filter and populate filteredDict based on both name and artist
repeat with trackID in theDict's allKeys()
set trackData to (theDict's objectForKey:trackID)
set trackName to item 1 of trackData
set trackArtist to item 2 of trackData
-- Check if either the track name or artist matches the predicate
if ((predicate's evaluateWithObject:trackName) as boolean) or ((predicate's evaluateWithObject:trackArtist) as boolean) then
(filteredDict's setObject:trackData forKey:trackID)
end if
end repeat
-- Iterate through each persistent ID in the filtered dictionary
repeat with trackID in filteredDict's allKeys()
set trackData to (filteredDict's objectForKey:trackID)
set originalName to item 1 of trackData
set originalArtist to item 2 of trackData
-- Manually replace 'ø' and 'Ø' with 'o' and 'O'
set originalName to (originalName's stringByReplacingOccurrencesOfString:"ø" withString:"o" options:(current application's NSLiteralSearch) range:{0, originalName's |length|()})
set originalName to (originalName's stringByReplacingOccurrencesOfString:"Ø" withString:"O" options:(current application's NSLiteralSearch) range:{0, originalName's |length|()})
set originalArtist to (originalArtist's stringByReplacingOccurrencesOfString:"ø" withString:"o" options:(current application's NSLiteralSearch) range:{0, originalArtist's |length|()})
set originalArtist to (originalArtist's stringByReplacingOccurrencesOfString:"Ø" withString:"O" options:(current application's NSLiteralSearch) range:{0, originalArtist's |length|()})
-- Apply transformations to both the name and artist
set transformedName to (originalName's stringByApplyingTransform:(current application's NSStringTransformToLatin) |reverse|:false)
set transformedName to (transformedName's stringByApplyingTransform:(current application's NSStringTransformStripCombiningMarks) |reverse|:false)
set transformedName to (transformedName's stringByApplyingTransform:(current application's NSStringTransformStripDiacritics) |reverse|:false)
set transformedArtist to (originalArtist's stringByApplyingTransform:(current application's NSStringTransformToLatin) |reverse|:false)
set transformedArtist to (transformedArtist's stringByApplyingTransform:(current application's NSStringTransformStripCombiningMarks) |reverse|:false)
set transformedArtist to (transformedArtist's stringByApplyingTransform:(current application's NSStringTransformStripDiacritics) |reverse|:false)
-- Store the transformed name, artist, and track ID in arrays
(diacriticIDs's addObject:trackID)
(diacriticNames's addObject:transformedName)
(diacriticArtists's addObject:transformedArtist)
end repeat
-- Bulk update the Music library
repeat with i from 0 to ((diacriticIDs's |count|()) - 1)
set trackID to (diacriticIDs's objectAtIndex:i) as text
set transformedName to (diacriticNames's objectAtIndex:i) as text
set transformedArtist to (diacriticArtists's objectAtIndex:i) as text
-- UNCOMMENT THE FOLLOWING TO: Update both the name and artist in a single operation per track
--set theTrack to (some track of library playlist 1 whose persistent ID is trackID)
--set name of theTrack to transformedName
--set artist of theTrack to transformedArtist
end repeat
end tell