FixLinks - an AppleScript to repair broken links in Music
This script is potentially a work in progress and is posted here so that I can edit it if needs be. It was written in response to this thread: Can I change the location of a song in Catalina? - Apple Community. While it works in the scenarios that I have tested with tracks organized following standard iTunes/Music conventions it may be possible to adapt it where a user has imposed their own file and folder naming rules.
The entire script below should be copied into a new document in Script Editor and saved as FixLinks. To use select some tracks in Music that show with exclamation marks indicating broken links, then run the script. You will be prompted for the media folder that holds these tracks. This should be where the artist folders are located. The script will process each track in the selection, ignoring any that are not broken, and attempting to fix any that are. In its current form it will show the path of each attempt where its guess is wrong so that the information may be used to refine the script. The script should be able to fix broken links where the files are stored at:
<Target Folder>/<[Album]Artist>/<Album>/[[D-]## ]<Name>[ 1].<Ext>
The first subfolder will be the Compilations folder if the track is marked as a compilation, otherwise Album Artist, or Artist if Album Artist is blank, or Unknown Artist if Artist is blank too. The second subfolder is the Album title, or Unknown Album if blank. A leading disc number is used if disc number or disc count are greater than 1. If the file isn't found at the initially predicted path a second search will be made that includes a trailing " 1" in the filename, and if that fails some more variations are explored.
tt2
Copy the text between the two horizontal lines:
_________________________________________________________________________________________
-- FixLinks - V1.08 - © Steve MacGuire - 2022-10-22
-- An AppleScript to repair broken track links in Music or iTunes
-- Downloaded from https://discussions.apple.com/docs/DOC-250002671
set mediaPath to "" -- preset media folder here with form /<path>/ here to avoid opening prompt
set showMissing to true -- set to false to suppress any reporting of missing tracks
set maxLen to 0 -- use to truncate predicted paths to a max. length, e.g. 40 to match iTunes for Windows
-- tell application "iTunes" -- Mojave or earlier
tell application "Music" -- Catalina or later
if selection is {} then
display dialog "Please select some tracks with broken links before calling this script" with title "FixLinks"
else
set mySelection to selection
set m to 0
set p to 0
set u to 0
set s to 0
repeat with aTrack in mySelection
set p to p + 1
set aPath to ""
try
set aPath to location of aTrack as text
end try
-- display dialog aPath & return & mediaPath -- enable for debugging
if aPath is "missing value" then -- only work with missing tracks
if mediaPath is "" then set mediaPath to my getMediaPath()
-- evaluate artist folder
set theArtist to album artist of aTrack
if theArtist = "" then set theArtist to artist of aTrack
if theArtist = "" then set theArtist to "Unknown Artist"
if compilation of aTrack = true then set theArtist to "Compilations"
set theArtist to my safePath(theArtist)
-- evaluate album folder
set theAlbum to album of aTrack
if theAlbum = "" then set theAlbum to "Unknown Album"
set theAlbum to my safePath(theAlbum)
-- evaluate file name
set theFile to ""
if track number of aTrack > 0 then
set theFile to theFile & track number of aTrack & " "
if track number of aTrack < 10 then set theFile to "0" & theFile
-- add leading disc number if implied
if disc number of aTrack > 1 or disc count of aTrack > 1 then set theFile to (disc number of aTrack as text) & "-" & theFile
-- or always add leading disc number
-- if disc number of aTrack > 0 then set theFile to (disc number of aTrack as text) & "-" & theFile
end if
set theFile to theFile & name of aTrack
set theFile to my safeFile(theFile, true)
-- calculate file extension
set k to kind of aTrack
if k = "AAC audio file" then
set ext to ".m4a"
else if k = "AIFF audio file" then
set ext to ".aif"
else if k = "Apple Lossless audio file" then
set ext to ".m4a"
else if k = "Apple Music AAC audio file" then
set ext to ".m4p"
else if k = "iTunes LP" then
set ext to ".itlp"
else if k = "Matched AAC audio file" then
set ext to ".m4a"
else if k = "MPEG audio file" then
set ext to ".mp3"
else if k = "MPEG-4 video file" then
set ext to ".mp4"
else if k = "PDF document" then
set ext to ".pdf"
else if k = "Purchased AAC audio file" then
set ext to ".m4a"
else if k = "Purchased MPEG-4 video file" then
set ext to ".mp4" -- might also be ".m4v", alternatives will be checked later
else if k = "WAV audio file" then
set ext to ".wav"
else
display dialog "Edit script to provide extension for tracks of kind " & k with title "FixLinks"
set ext to ""
end if
-- extend this pattern as required
set newPath to theArtist & "/" & theAlbum & "/" & theFile -- Normal <Artist>/<Album>/[D-]## <Name>.<Ext> layout
-- set newPath to theArtist & "/" & theFile -- Option for no album folders within artist folders
set testPath to mediaPath & newPath & ext
if not my fileExists(testPath) then set testPath to mediaPath & newPath & " 1" & ext
if not my fileExists(testPath) then set testPath to mediaPath & newPath & " 2" & ext
if not my fileExists(testPath) then set testPath to mediaPath & "Music/" & newPath & ext
if not my fileExists(testPath) then set testPath to mediaPath & "Music/" & newPath & " 1" & ext
if not my fileExists(testPath) then set testPath to mediaPath & "Music/" & newPath & " 2" & ext
if ext = ".mp4" then
if not my fileExists(testPath) then set testPath to mediaPath & newPath & ".m4v"
if not my fileExists(testPath) then set testPath to mediaPath & newPath & " 1.m4v"
if not my fileExists(testPath) then set testPath to mediaPath & "Music/" & newPath & ".m4v"
if not my fileExists(testPath) then set testPath to mediaPath & "Music/" & newPath & " 1.m4v"
end if
-- pattern above could be extended for other variations, e.g. trailing 2
-- display dialog mediaPath & return & testPath -- enable for debugging
if my fileExists(testPath) then
-- display dialog "Connecting broken track to:" & return & testPath & with title "FixLinks" -- enable for debugging
try -- Workaround for Big Sur -- error "Music got an error: Parameter error." number -50
set location of aTrack to testPath
end try
-- note this script needs tweaking to correctly repair broken iTunes LP entries
set u to u + 1
else
set m to m + 1
if showMissing then
set msg to "File not found at:" & return & return & mediaPath & newPath & ext
set msg to msg & return & return & "Continue reporting?"
set answer to the button returned of (display dialog msg buttons {"Yes", "No", "Cancel"} with title "FixLinks")
if answer = "No" then set showMissing to false
end if
end if
else
-- display dialog location of aTrack as string with title "FixLinks" -- enable for debugging
set s to s + 1
end if
end repeat
-- report on script activity
set msg to (p as string) & " item"
set msg to msg & my plural(p, "s were", " was")
set msg to msg & " processed,"
set msg to msg & return & (u as string) & " item"
set msg to msg & my plural(u, "s were", " was")
set msg to msg & " updated"
if m > 0 then
set msg to msg & "," & return & (m as string) & " item"
set msg to msg & my plural(m, "s were", " was")
set msg to msg & " not located"
end if
if s > 0 then
set msg to msg & "," & return & (s as string) & " item"
set msg to msg & my plural(s, "s", "")
set msg to msg & " needed no action"
end if
set msg to msg & "."
display dialog msg with title "FixLinks" giving up after 300
end if
end tell
-- check that a file exists at a given path
on fileExists(theFile) -- (String) as Boolean
tell application "System Events"
if exists file (my replace(theFile, "/", ":")) then
-- display dialog theFile & " exists!"
return true
else
-- display dialog theFile & " does not exist!"
return false
end if
end tell
end fileExists
-- prompt for media folder location
on getMediaPath()
set aPath to choose folder with prompt "Select the media folder you wish to find missing tracks in" default location path to music folder
set aPath to POSIX path of aPath
return aPath
end getMediaPath
-- return plural or singular form dependent on V
on plural(V, p, s)
if V = 1 then
return s
else
return p
end if
end plural
-- find : Text to be found
-- replace : Text to replace with
-- someText : Text to be searched
on replace(someText, find, replace)
set prevTIDs to text item delimiters of AppleScript
set text item delimiters of AppleScript to find
set someText to text items of someText
set text item delimiters of AppleScript to replace
set someText to "" & someText
set text item delimiters of AppleScript to prevTIDs
return someText
end replace
-- Replace characters that are invalid in file paths and trim any spaces
on safeFile(someText, isFile)
-- optionally truncate filename when something other than iTunes/Music has organized files
set theLen to my maxLen
if theLen > 9 then -- catch errors with really small setting
if isFile then set theLen to theLen - 4
if length of someText > theLen then
set someText to text 1 thru theLen of someText
end if
end if
-- trim spaces and replace characters
set someText to trim(someText)
set someText to my replace(someText, "/", "_")
set someText to my replace(someText, "?", "_")
set someText to my replace(someText, "<", "_")
set someText to my replace(someText, ">", "_")
set someText to my replace(someText, "\\", "_")
set someText to my replace(someText, ":", "_")
set someText to my replace(someText, "*", "_")
set someText to my replace(someText, "|", "_")
set someText to my replace(someText, "\"", "_")
end safeFile
-- Additional replacements for folders only
on safePath(someText)
if someText begins with " " then set someText to "_" & text 2 thru -1 of someText
if someText begins with "." then set someText to "_" & text 2 thru -1 of someText
if someText ends with "." then set someText to text 1 thru -2 of someText & "_"
-- custom option for me that needs commenting out
-- if someText begins with "The " then set someText to text 5 thru -1 of someText & ", The"
set someText to safeFile(someText, false)
return someText
end safePath
-- strip leading and trailing spaces
on trim(someText)
repeat until someText does not start with " "
set someText to text 2 thru -1 of someText
end repeat
repeat until someText does not end with " "
set someText to text 1 thru -2 of someText
end repeat
return someText
end trim
_________________________________________________________________________________________
Paste into a new empty document window in the Script Editor app and save as FixLinks. Alternatively download using this link - FixLinks.zip - and extract the file. Save to ~/Library/Music/Scripts if you want to be able to call it from the Music menu bar.