(* "Update Library all-in-one" for iTunes written by Doug Adams and Paul Rogers dougadams@mac.com / paul.rogers@flumps.org Paul's additions: ------------------- v2.4 23rd May 2010 -- Now saves the current state and resumes if it dies v2.3 18th May 2010 -- Updates the blank genre items with the genres from the track itself, because iTunes cannot manage doing them itself. v2.2 16th May 2010 -- Now deletes any items in the library where there are duplicate filenames (the first added item) and logs any changes to the same log file. v2.1 15th May 2010 -- Now updates track info for all non-dead tracks and logs any changes to the same log file. Doug's coding: ----------------- v2.0 april 19, 2007 -- creates a text file on desktop listing some available information from tracks that were removed v1.5.1 march 20 2007 - tidied up code v1.5 october 26 2005 -- prevents error when trying to delete empty Smart Playlists and/or playlist that can't be deleted (Party Shuffle, Podcasts, etc) -- code tweaks v1.0 sep 25 2003 -- initial release Get more free AppleScripts and info on writing your own at Doug's AppleScripts for iTunes http://www.dougscripts.com/itunes/ This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details. Get a copy of the GNU General Public License by writing to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA. or visit http://www.gnu.org/copyleft/gpl.html *) ------------------------------------- (* the number below is the number of tracks to count before reporting progress - you can change this number to a larger or smaller number.  If you set it to zero, you will not see the progress message *) property progress_factor : 200 property my_title : "Update Library all-in-one" property deftxt : "Update Library all-in-one Log" global text_file_path -- name of text file created global base_lib_path global save_state_path global deleted_tracks_dead global deleted_tracks_dupe global genre_tracks global updated_tracks global skipped_tracks global all_checked_tracks global countem global arrLines global arrLines2 global strGenreFile global strDupeFile global save_start_ID -- inits set base_lib_path to path to home folder as string set text_file_path to ((path to desktop) & deftxt & my get_ymd()) as string -- set name of text file log using today's date yyyy-mm-dd set save_state_path to base_lib_path & "Library:iTunes:Scripts:updliballin1.state" set deleted_tracks_dead to 0 set deleted_tracks_dupe to 0 set genre_tracks to 0 set updated_tracks to 0 set skipped_tracks to 0 set all_checked_tracks to 0 set countem to "" set strDupeFile to base_lib_path & "Library:iTunes:Scripts:dupes_to_delete.dat" set strGenreFile to base_lib_path & "Library:iTunes:Scripts:blank_genres.dat" set save_start_ID to 0 --proceed display dialog "This script will:" & return & return & "* Remove tracks whose files are missing or deleted." & return & return & "* Remove those tracks in the library which have duplicate file references" & return & return & "* Fix blank genres in the library with the genre from the actual track." & return & return & "* Update the Track Information for those tracks which are \"non-dead\"." & return & return & "WARNING! Will take up to a few hours on a large library (10000 tracks and above)." buttons {"Cancel", "Proceed..."} default button 2 with title my_title with icon 1 my chk_save_state() set refFile to (open for access strDupeFile) set blobLines to (read refFile for (get eof refFile) as «class utf8») close access refFile set arrLines to every paragraph of blobLines set refFile to (open for access strGenreFile) set blobLines to (read refFile for (get eof refFile) as «class utf8») close access refFile set arrLines2 to every paragraph of blobLines set boolGenreErrs to checkGenreErrs() if boolGenreErrs then return display dialog "Now processing the iTunes library to apply any changes required..." buttons {"Ok"} with title my_title with icon 1 giving up after 2 tell application "iTunes" set mainLibrary to library playlist 1 set totalTracks to count of file tracks of mainLibrary set all_playlists to user playlists whose smart is false -- don't delete Smart Playlists later set oldfi to fixed indexing set fixed indexing to true my add_to_text(-1, "Starting to update the library...") with timeout of 30000 seconds repeat with t from totalTracks to 1 by -1 (* try *) repeat 1 times set this_track to file track t of mainLibrary if (save_start_ID is greater than 0) and (save_start_ID is this_track's database ID) then my add_to_text(-1, "We skipped " & (skipped_tracks as string) & " tracks because we are resuming a previous run.") set save_start_ID to 0 else if (save_start_ID is greater than 0) then set skipped_tracks to skipped_tracks + 1 exit repeat end if my save_state(this_track's database ID as string) if this_track's location is missing value then -- add to text file my add_to_text(0, this_track) delete this_track set deleted_tracks_dead to deleted_tracks_dead + 1 else set boolRetCode to my checkIDDupes(this_track's database ID as string) if (boolRetCode) then -- add to text file my add_to_text(1, this_track) delete this_track set deleted_tracks_dupe to deleted_tracks_dupe + 1 else set arrReturn to my checkIDGenre(this_track's database ID as string) set boolRetCode to item 1 of arrReturn set strRetGenre to item 2 of arrReturn if (boolRetCode) then -- add to text file my add_to_text(2, this_track) set this_track's genre to strRetGenre set genre_tracks to genre_tracks + 1 else -- update the track info my add_to_text(3, this_track) refresh this_track set updated_tracks to updated_tracks + 1 end if end if end if set all_checked_tracks to all_checked_tracks + 1 if (progress_factor is not 0) and (all_checked_tracks mod progress_factor) is 0 then if frontmost is true then set strMsg to (all_checked_tracks as string) & " total tracks processed. Breakdown is as follows:" if skipped_tracks is greater than 0 then set strMsg to strMsg & return & return & (skipped_tracks as string) & " skipped tracks as we are resuming a previous run..." end if set strMsg to strMsg & return & return & (deleted_tracks_dead as string) & " dead tracks removed so far..." & return & return & (deleted_tracks_dupe as string) & " duplicate tracks removed so far..." & return & return & (genre_tracks as string) & " track's genres updated..." & return & return & (updated_tracks as string) & " tracks updated..." display dialog strMsg buttons {"Ok"} with title my_title with icon 1 giving up after 2 end if end if end repeat (*end try*) end repeat end timeout set fixed indexing to oldfi -- check for empty playlists repeat with this_playlist in all_playlists if (get count of tracks of this_playlist) is 0 then try delete playlist this_playlist end try end if end repeat -- finish up my add_to_text(-1, "Finished!") set psdeldead to " was" set psdeldupe to " was" set psgenre to " was" set psupd to " was" set psall to " was" if deleted_tracks_dead is not 1 then set psdeldead to "s were" if deleted_tracks_dupe is not 1 then set psdeldupe to "s were" if genre_tracks is not 1 then set psgenre to "s were" if updated_tracks is not 1 then set psupd to "s were" if all_checked_tracks is not 1 then set psall to "s were" my del_save_state() activate set final_opts to button returned of (display dialog "Finished!" & return & return & all_checked_tracks & " track" & psall & " processed." & return & return & deleted_tracks_dupe & " duplicate track" & psdeldupe & " removed." & return & return & deleted_tracks_dead & " dead track" & psdeldead & " removed." & return & return & genre_tracks & " genre" & psgenre & " updated." & return & return & updated_tracks & " track" & psupd & " updated." buttons {"Delete Log", "Open Log", "Thanks"} default button 2 with title my_title with icon 1) if final_opts contains "open" then tell application "TextEdit" open text_file_path activate end tell else if final_opts contains "delete" then try do shell script "rm " & quoted form of POSIX path of (text_file_path as string) end try end if end tell to del_save_state() (*try*) tell application "Finder" if exists file save_state_path then delete file save_state_path end tell (*end try*) end del_save_state to add_to_text(action, t) if action is -1 then set action_txt to "INFO         - " else if action is 0 then set action_txt to "DELETED DEAD - " else if action is 1 then set action_txt to "DELETED DUPE - " else if action is 2 then set action_txt to "GENRE UPDATE - " else if action is 3 then set action_txt to "UPDATED      - " else set action_txt to "UNKNOWN     - " end if if (action ≥ 0 and action ≤ 3) then tell application "iTunes" tell t to set {aart, art, alb, nom} to {album artist, artist, album, name} end tell if aart is "" then set aart to "[Album Artist n/a]" if art is "" then set art to "[Artist n/a]" if alb is "" then set alb to "[Album n/a]" if nom is "" then set nom to "[Song Name n/a]" set mess to ((my get_ymd() as string) & " " & action_txt & aart & " - " & art & " - " & alb & " - " & nom & return) as text else set mess to (t & return) as text end if set f to (text_file_path) as string try set fileRef to open for access file f with write permission set z to (get eof of fileRef) write mess to fileRef starting at (z + 1) close access fileRef on error m log m -- debugging close access fileRef end try end add_to_text to checkIDGenre(strID) local strGenre set strGenre to "" repeat with m from 1 to count of arrLines2 set n to item m of arrLines2 if length of n > 1 then set p to text 1 thru ((offset of "," in n) - 1) of n if p is strID then set strGenre to text ((offset of "," in n) + 1) thru -1 of n if (m > 1) and (m < (count of arrLines2)) then set arrLines2 to items 1 thru (m - 1) of arrLines2 & items (m + 1) thru -1 of arrLines2 else if (m is 1) and ((count of arrLines2) > 1) then set arrLines2 to items (m + 1) thru -1 of arrLines2 else if (m is 1) and (m is (count of arrLines2)) then set arrLines2 to {} else if (m is (count of arrLines2)) then set arrLines2 to items 1 thru (m - 1) of arrLines2 end if return {true, strGenre} end if end if end repeat return {false, strGenre} end checkIDGenre to checkIDDupes(strID) repeat with a from 1 to count of arrLines set b to item a of arrLines if length of b > 0 then if b is strID then if (a > 1) and (a < (count of arrLines)) then set arrLines to items 1 thru (a - 1) of arrLines & items (a + 1) thru -1 of arrLines else if (a is 1) and ((count of arrLines) > 1) then set arrLines to items (a + 1) thru -1 of arrLines else if (a is 1) and (a is (count of arrLines)) then set arrLines to {} else if (a is (count of arrLines)) then set arrLines to items 1 thru (a - 1) of arrLines end if return true end if end if end repeat return false end checkIDDupes to checkGenreErrs() repeat with i from 1 to count of arrLines2 set j to item i of arrLines2 if length of j > 1 then set k to text 1 thru ((offset of "," in j) - 1) of j if (offset of "ERR" in k) > 0 then display dialog "WARNING! The blank genres data file (" & strGenreFile & ") contains errors." & return & return & "Please correct and re-run." buttons {"Ok"} with title my_title with icon 0 return true end if else if length of j = 1 then display dialog "WARNING! Malformed line in blank genres data file (" & strGenreFile & "). Length of line is only 1 character long. Please remove and re-run." buttons {"Ok"} with icon 0 with my_title return true end if end repeat return false end checkGenreErrs to save_state(t) set f to (save_state_path) as string try set fileRef to open for access file f with write permission set eof fileRef to 0 write t to fileRef close access fileRef on error m log m -- debugging close access fileRef end try end save_state to chk_save_state() tell application "Finder" if exists file save_state_path then display dialog "WARNING! The previous run did not complete fully." & return & return & "Do you want to continue or restart from the beginning?" buttons {"Restart", "Continue"} default button 2 with title my_title with icon 1 if button returned of result is "Restart" then my del_save_state() else my read_save_state() return end if end if end tell display dialog "Executing Perl scripts to generate / sort the data..." buttons {"Ok"} with title my_title with icon 1 giving up after 2 do shell script POSIX path of (base_lib_path & "Library:iTunes:Scripts:finddupes.pl") do shell script POSIX path of (base_lib_path & "Library:iTunes:Scripts:findblankgenres.pl") end chk_save_state to read_save_state() set refFile to (open for access file save_state_path) set blobLines to (read refFile for (get eof refFile) as «class utf8») close access refFile set tmpArrLines to every paragraph of blobLines set j to item 1 of tmpArrLines if length of j > 0 then set save_start_ID to (j as integer) else display dialog "WARNING! The save state file is empty." & return & return & "Will restart the whole process." buttons {"Ok"} with title my_title with icon 0 set save_start_ID to 0 end if end read_save_state to get_ymd() return (do shell script "date '+%F %I.%M%p'") end get_ymd