Go Back   The macosxhints Forums > OS X Help Requests > AppleScript



Reply
 
Thread Tools Rate Thread Display Modes
Old 09-26-2010, 01:14 PM   #1
nick_harambee
Triple-A Player
 
Join Date: Oct 2006
Posts: 135
Script to duplicate Apple Lossless iTunes library as MP3 files

Hi,

I have written the applescript below to duplicate my Apple Lossless iTunes library as a MP3 Library. I wanted the script to be able to do the following things:

1. For all Apple Lossless tracks in iTunes Library (at the moment the script is just set to work with a selection for testing purposes) check if track already exists in MP3 folder with more recent modification date and:
a If yes, do nothing
b If same track with earlier modification date convert and replace track (so that tag changes are transferred)
c If track doesn't exist convert track.

2. If there are any MP3 files in the target folder that aren't in the source folder (i.e. the iTunes Music Folder), delete these files. This is for the scenario when I delete files from my iTunes Library that I no longer want, and I want the equivalent MP3 file to be deleted as well. So once the script has finished running there should be an exact match between the Apple Lossless library and the MP3 folder.

The script below does everything I want it to do with the following caveats:

1. I am not sure how to go about deleting the MP3 files as in part 2. I guess it would have to be an additional section of script which compares each file by name in the folder/subfolder in the iTunes Music Library with the equivalent folders in the target music folder, deleting any files found in the target music folder that don't exist in the iTunes Music folders, but I'm not sure if this is the best/only method, and how I would go about scripting this.

2. Because I need to specify the conversion twice (once if no file exists in target folder and once if it does but with an older modification date) I am wondering about using a subroutine method here. I tried doing so, but no conversions happened even though some should have happened. I wonder if this is because the relevant variables aren't picked up properly by the script when it's a subroutine. I can leave things as they are, just thought it would be good to make the script neater.

Code:
tell application "iTunes"
	activate
	
	set sel to selection
	set encoderBackup to name of current encoder
	set this_folder to "Macintosh HD:Users:nick:Desktop:music:" as text
	set current encoder to encoder "MP3 Encoder"
	
	
	repeat with this_track in sel
		set trackComposer to this_track's composer
		set trackAlbum to this_track's album
		set trackName to this_track's name
		tell application "Finder"
			
			set path1 to "Macintosh HD:Users:nick:Desktop:music:" & trackComposer as text
			
			if folder path1 exists then
				{}
			else
				make new folder at this_folder with properties {name:trackComposer}
			end if
			
			set path2 to this_folder & trackComposer & ":" & trackAlbum as text
			
			if folder path2 exists then
				{}
			else
				make new folder at path1 with properties {name:trackAlbum}
			end if
			
			if exists file (path2 & ":" & trackName & ".mp3") then
				set file2 to (path2 & ":" & trackName & ".mp3") as alias
				set modDate to modification date of file2
				set modDate2 to this_track's modification date
				if modDate is greater than modDate2 then
					{}
				else
					tell application "iTunes"
						try -- skip on failure
							set new_track to item 1 of (convert this_track)
							set loc to new_track's location
							set dbid to new_track's database ID
							delete artworks of new_track
							
							-- move the file to new location
							do shell script "mv " & (quoted form of POSIX path of loc) & " " & (quoted form of POSIX path of path2 as string)
							
							-- delete the track
							delete new_track
						end try
					end tell
					
				end if
			else
				tell application "iTunes"
					try -- skip on failure
						set new_track to item 1 of (convert this_track)
						set loc to new_track's location
						set dbid to new_track's database ID
						delete artworks of new_track
						
						-- move the file to new location
						do shell script "mv " & (quoted form of POSIX path of loc) & " " & (quoted form of POSIX path of path2 as string)
						
						-- delete the track
						delete new_track
					end try
				end tell
				
			end if
		end tell
		
	end repeat
	
	set current encoder to encoder encoderBackup
	display dialog "done"
end tell
Can anyone help with either points?

Thanks

Nick
__________________
OS X 10.6.2
Mac Pro
2 x 3 GHz Dual-Core Intel Xeon
8 GB RAM
nick_harambee is offline   Reply With Quote
Old 09-27-2010, 10:37 AM   #2
tw
Hall of Famer
 
Join Date: Apr 2007
Posts: 4,262
This should work. I've gotten rid of the senseless shell script call and converted files paths to Finder objects, but it's essentially the same script.

Code:
set this_folder to (path to desktop folder from user domain as text) & "music:"

tell application "iTunes"
	-- activate -- not really necessary to activate iTunes
	set sel to selection
	set encoderBackup to name of current encoder
	set current encoder to encoder "MP3 Encoder"
end tell

repeat with this_track in sel
	tell application "iTunes"
		set trackComposer to this_track's composer
		set trackAlbum to this_track's album
		set trackName to this_track's name
		set tunesModDate to this_track's modification date
	end tell
	tell application "Finder"
		-- build folder hierarchy, if it doesn't already exist
		try
			make new folder at folder this_folder with properties {name:trackComposer}
		end try
		try
			make new folder at folder trackComposer of folder this_folder with properties {name:trackAlbum}
		end try
		
		set folder_path to folder trackAlbum of folder trackComposer of folder this_folder
		try
			-- throws error if file doesn't exist
			set theExtantFile to file (trackName & ".mp3") of folder_path
			if modification date of theExtantFile is less than tunesModDate then
				delete theExtantFile
				tell me to set theConvertedFile to ConvertTune(this_track)
			end if
		on error
			-- no extant file 
			tell me to set theConvertedFile to ConvertTune(this_track)
		end try
		if theConvertedFile is not false then
			move theConvertedFile to folder_path
		end if
	end tell
end repeat

tell application "iTunes"
	set current encoder to encoder encoderBackup
	display dialog "done"
end tell

to ConvertTune(t)
	tell application "iTunes"
		try -- skip on failure
			set new_track to item 1 of (convert t)
			set loc to new_track's location
			set dbid to new_track's database ID
			delete artworks of new_track
			delete new_track -- deletes track from library; does not delete the file itself
			return loc
		on error
			return false
		end try
	end tell
end ConvertTune
__________________
Philosophy is a battle against the bewitchment of our intelligence by means of language. -LW-
tw is offline   Reply With Quote
Old 09-27-2010, 10:59 AM   #3
nick_harambee
Triple-A Player
 
Join Date: Oct 2006
Posts: 135
Thanks. I have been working on the script further and got it to do everything I want it to do except the deleting of MP3 files that are present in the destination folders that aren't present in m4a format in the source (iTunes) folders:

Code:
tell application "iTunes"
	activate
	
	set sel to selection
	set encoderBackup to name of current encoder
	set this_folder to "Macintosh HD:Users:nick:Desktop:music:" as text
	set current encoder to encoder "MP3 Encoder"
	
	repeat with this_track in sel
		set trackComposer to this_track's composer
		set trackAlbum to this_track's album
		set trackName to this_track's name
		tell application "Finder"
			
			set path1 to "Macintosh HD:Users:nick:Desktop:music:" & trackComposer as text
			
			if not (folder path1 exists) then
				make new folder at this_folder with properties {name:trackComposer}
			end if
			
			local path2
			set path2 to this_folder & trackComposer & ":" & trackAlbum as text
			
			if not (folder path2 exists) then
				make new folder at path1 with properties {name:trackAlbum}
			end if
			
			if exists file (path2 & ":" & trackName & ".mp3") then
				set file2 to (path2 & ":" & trackName & ".mp3") as alias
				set modDate to modification date of file2
				set modDate2 to this_track's modification date
				if not (modDate is greater than modDate2) then
					my convertTrack(this_track, path2)
				end if
			else
				my convertTrack(this_track, path2)
			end if
		end tell
		
	end repeat
	
	set current encoder to encoder encoderBackup
	display dialog "done"
end tell

on convertTrack(this_track, path2)
	tell application "iTunes"
		try -- skip on failure
			set new_track to item 1 of (convert this_track)
			set loc to new_track's location
			set dbid to new_track's database ID
			delete artworks of new_track
			-- move the file to new location
			do shell script "mv " & (quoted form of POSIX path of loc) & " " & (quoted form of POSIX path of path2 as string)
			
			-- delete the track
			delete new_track
		end try
	end tell
end convertTrack
At present your script works fine for me the first time I convert a track. But when I ran the script again for the same track, which should have skipped any conversions (as the track exists with a more recent modification date) I got the error:

error "Finder got an error: An item with the same name already exists in this location." number -15267

I am not sure if there are advantages to your script over mine (once the issue is resolved).

Also, do you have any ideas as to how to delete any MP3 files in the destination folders/subfolders, that don't exist as m4a files in the source (iTunes) folders/subfolders (i.e. point 1 in my second post above).

Thanks again

Nick
__________________
OS X 10.6.2
Mac Pro
2 x 3 GHz Dual-Core Intel Xeon
8 GB RAM
nick_harambee is offline   Reply With Quote
Old 09-27-2010, 12:51 PM   #4
tw
Hall of Famer
 
Join Date: Apr 2007
Posts: 4,262
The main advantage of my script over yours is that mine is cleaner, for the following reasons:
  • It doesn't nest tell blocks for different applications (which can cause name conflicts, and makes the script a bit congested)
  • It uses the Finder's special properties, which makes the script stabler
minor points, really, but there you go.

The reason the script is throwing an error is that - because I switched to using the Finder's special properties - it now sees an error in your script that your script was ignoring before. there is a difference between the track name as displayed in iTunes and the file name under which iTunes stores the track. commonly, for instance, iTunes will append extra info: a track called "Snufflehead" might be stored as "03 - Snufflehead.xxx" (sorry for the example; have allergies this morning). You were building filenames different than the filenames that iTunes uses, which may or may not cause problems down the line (depending on what you're using this for).

doing back checks (trying to delete extraneous tracks) is a bit of a problem the way you have things up. The Finder only knows file names, but iTunes won't search by location, and as noted the file name of the track may not correspond to the track name in itunes. your best bet is actually to rewrite the script and maintain the list of mp3s within your itunes library - just make sure to set the enabled key to false and to add the tracks to a category, playlist, or etc., or use comments to mark them as recoded duplicates. That makes it fairly easy to check for absent originals within iTunes itself. (you might also add recoded m4v files to a playlist to cut down the search time when you're trying to find missing originals - easier to search a short list of files you know you've recoded than to search the entire library).
__________________
Philosophy is a battle against the bewitchment of our intelligence by means of language. -LW-
tw is offline   Reply With Quote
Old 09-27-2010, 02:05 PM   #5
nick_harambee
Triple-A Player
 
Join Date: Oct 2006
Posts: 135
Thanks. My filenames are set to be the same as the name of the tracks in my iTunes Library, so I don't think this is the issue.

The error in your script is occurring here:

Code:
if theConvertedFile is not false then
	move theConvertedFile to folder_path
end if
when no conversions are necessary, and thus theConvertedFile should be false, the script is still looking to move theConvertedFile.

Regarding the second point, I would rather avoid having the MP3s in my iTunes Library. I'd like to be able to just click on 'Music' in the iTunes library and get one version of each track, and also I'd like to avoid having to add 'kind is Apple Lossless Audio File' to all my smart playlists. If there is no other way of deleting the unneeded MP3 files I will consider this, but was hoping there was a way of just comparing the destination directory with the source directory using Finder in the script.

oh, and the purpose of the script is to create an exact duplicate of my Apple Lossless Library in MP3 format. The script would be run periodically and would add new tracks, update mp3 tracks that I have updated in my iTunes library, delete tracks that no longer exist in my iTunes library, and leave the rest alone.

Nick
__________________
OS X 10.6.2
Mac Pro
2 x 3 GHz Dual-Core Intel Xeon
8 GB RAM

Last edited by nick_harambee; 09-27-2010 at 02:11 PM.
nick_harambee is offline   Reply With Quote
Old 09-27-2010, 03:18 PM   #6
tw
Hall of Famer
 
Join Date: Apr 2007
Posts: 4,262
Quote:
Originally Posted by nick_harambee
Thanks. My filenames are set to be the same as the name of the tracks in my iTunes Library, so I don't think this is the issue.

The error in your script is occurring here:

Code:
if theConvertedFile is not false then
	move theConvertedFile to folder_path
end if
when no conversions are necessary, and thus theConvertedFile should be false, the script is still looking to move theConvertedFile.

Regarding the second point, I would rather avoid having the MP3s in my iTunes Library. I'd like to be able to just click on 'Music' in the iTunes library and get one version of each track, and also I'd like to avoid having to add 'kind is Apple Lossless Audio File' to all my smart playlists. If there is no other way of deleting the unneeded MP3 files I will consider this, but was hoping there was a way of just comparing the destination directory with the source directory using Finder in the script.

oh, and the purpose of the script is to create an exact duplicate of my Apple Lossless Library in MP3 format. The script would be run periodically and would add new tracks, update mp3 tracks that I have updated in my iTunes library, delete tracks that no longer exist in my iTunes library, and leave the rest alone.

Nick

Yes, I know where the error is, and I told you why it's happening. I haven't got the time to fix it right at the moment, however (mostly since it would involve restructuring the concept, but I don't want to do that without a better sense of what you want from the script).

Deleting without having the files as part of your iTunes library is not impossible, just a bit of a pain. The easiest way I can think of to do it is as follows:
Code:
tell application "iTunes"
	-- get a text list of all paths of all files in the library
	set theLocations to location of every file track of source "library"
	set {oldTID, AppleScript's text item delimiters} to {AppleScript's text item delimiters, return}
	set theLocations to theLocations as text
	set AppleScript's text item delimiters to oldTID
	
	set killTheseFiles to {}
	repeat with thisPath in theRecodedFilesFolder
		-- get the album:artist:file name of a given recoded file, withut the extension
		set {oldTID, AppleScript's text item delimiters} to {AppleScript's text item delimiters, ":"}
		set pathSnippet to (text items -3 thru -1 of (thisPath as text)) as text
		set AppleScript's text item delimiters to oldTID
		set pathSnippet to (text 1 thru -5 of pathSnippet)
		
		-- see if that snippet is in the text list of file locations, store it for deletion if so
		if theLocations does not contain pathSnippet then
			copy thisPath to end of killTheseFiles
		end if
	end repeat
end tell

-- delete the stored files
tell application "Finder" to delete killTheseFiles
this gathers the locations from itunes as delimited text and checks to see if the album:artist:file name combination of the recoded file is in the text.

However, I'm going to suggest that you stop trying to do this with iTunes. there are a number of free music conversions apps on the web, and I suspect that if you look a bit you will find one that will convert your music library to a new location without invoking itunes at all (and probably one that will automatically synchronize the folders).
__________________
Philosophy is a battle against the bewitchment of our intelligence by means of language. -LW-
tw is offline   Reply With Quote
Old 09-27-2010, 06:09 PM   #7
nick_harambee
Triple-A Player
 
Join Date: Oct 2006
Posts: 135
Thanks. I know there's other apps to convert files, but, despite quite a bit of searching I have not found an app on the Mac that will synchronize an Apple Lossless library with a Compressed Format Library. J River Media Center on Windows will do it, but I'd rather not go down the Parallels route for this. Hence the need for this script.

So I made a small adjustment to your script so that it just lists my main Music Playlist, which contains the files I want to synchronize with.

Then I needed to set theRecodedFilesFolder (I hope I did this right).

Then when I run the script I get the error:

error "Can’t get text 1 thru -5 of \"M\"." number -1728 from text 1 thru -5 of "M"

Code:
tell application "iTunes"
	-- get a text list of all paths of all files in Music Playlist
	set theLocations to location of every file track of playlist "Music"
	set {oldTID, AppleScript's text item delimiters} to {AppleScript's text item delimiters, return}
	set theLocations to theLocations as text
	set AppleScript's text item delimiters to oldTID
	
	set killTheseFiles to {}
	set theRecodedFilesFolder to "Macintosh HD:Users:nick:Desktop:music:" as text
	repeat with thisPath in theRecodedFilesFolder
		-- get the album:artist:file name of a given recoded file, withut the extension
		set {oldTID, AppleScript's text item delimiters} to {AppleScript's text item delimiters, ":"}
		set pathSnippet to (text items -3 thru -1 of (thisPath as text)) as text
		set AppleScript's text item delimiters to oldTID
		set pathSnippet to (text 1 thru -5 of pathSnippet)
		
		-- see if that snippet is in the text list of file locations, store it for deletion if so
		if theLocations does not contain pathSnippet then
			copy thisPath to end of killTheseFiles
		end if
	end repeat
end tell

-- delete the stored files
tell application "Finder" to delete killTheseFiles
__________________
OS X 10.6.2
Mac Pro
2 x 3 GHz Dual-Core Intel Xeon
8 GB RAM
nick_harambee is offline   Reply With Quote
Old 09-28-2010, 02:54 AM   #8
tw
Hall of Famer
 
Join Date: Apr 2007
Posts: 4,262
ah, no. you need a list of the all the files in the folder, not just the folder name itself. add the following code right before the 'tell application "iTunes"' line:
Code:
tell application "Finder"
	set theRecodedFilesFolder to (every file of entire contents of folder "Macintosh HD:Users:nick:Desktop:music:") as alias list
end tell
note that you basically have to loop through every item of your recoded folder to see if there are any missing sources. It's not terribly efficient.

You might want to look at PowerTunes and Max. PowerTunes might be just the thing you're looking for, but it costs $20. Max won't synchronize folders, but it's a good converter, and it's scriptable, and it might be easier to use (all things considered) than iTunes.
__________________
Philosophy is a battle against the bewitchment of our intelligence by means of language. -LW-
tw is offline   Reply With Quote
Old 09-28-2010, 03:42 AM   #9
nick_harambee
Triple-A Player
 
Join Date: Oct 2006
Posts: 135
Many thanks tw. That seems to work fine. When I have more time I'll test it more thoroughly and report back.

I have been in communication with the developer of PowerTunes and he is thinking of implementing the feature to sync two versions of iTunes Libraries in different formats, but it's not there yet.

Sure, I could use Max for the conversions if I was just comparing two directories. It would still need scripting though.

I appreciate you taking the time to help me on my way...

Nick
__________________
OS X 10.6.2
Mac Pro
2 x 3 GHz Dual-Core Intel Xeon
8 GB RAM
nick_harambee is offline   Reply With Quote
Old 09-28-2010, 10:27 AM   #10
tw
Hall of Famer
 
Join Date: Apr 2007
Posts: 4,262
well, the advantage of scripting Max is that Max doesn't care whether a file is in the music library. iTunes is limited in what it can do with non-library files; Max is equal opportunity. makes life easier.

my pleasure though.
__________________
Philosophy is a battle against the bewitchment of our intelligence by means of language. -LW-
tw is offline   Reply With Quote
Old 09-30-2010, 08:13 AM   #11
nick_harambee
Triple-A Player
 
Join Date: Oct 2006
Posts: 135
one issue that has emerged with this script is that Finder does not permit the character '?' in file names, and replaces them with '_', so if tracks in iTunes have a question mark in the title the script will want to re-convert these files even if no changes have been made.

I am wondering if there is a way of adapting the script so that it can account for this difference, i.e. so that when the script is looking to see whether the track exists in Finder it will interpret a ? in the track Name in iTunes as a _ in the track Name in Finder.
__________________
OS X 10.6.2
Mac Pro
2 x 3 GHz Dual-Core Intel Xeon
8 GB RAM
nick_harambee is offline   Reply With Quote
Old 10-02-2010, 07:55 AM   #12
nick_harambee
Triple-A Player
 
Join Date: Oct 2006
Posts: 135
I now realise there are other characters that are not supported by Finder, namely / and :. So these would need to be built into the script as well, presumably for albums and composer tags as well as track name.
__________________
OS X 10.6.2
Mac Pro
2 x 3 GHz Dual-Core Intel Xeon
8 GB RAM
nick_harambee is offline   Reply With Quote
Reply

Thread Tools
Display Modes Rate This Thread
Rate This Thread:

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump



All times are GMT -5. The time now is 01:36 AM.


Powered by vBulletin® Version 3.8.7
Copyright ©2000 - 2013, vBulletin Solutions, Inc.
Site design © IDG Consumer & SMB; individuals retain copyright of their postings
but consent to the possible use of their material in other areas of IDG Consumer & SMB.