Populate iTunes playlist from csv file - applescript

Could anyone offer a way to populate my playlist with songs from a csv file/text file formatted like this:
song title,artist?
I can do it for title alone but can't specify it must have a certain artist.
EDIT: Here is an example of how I'm getting them by titles:
set TheFile to read file "Macintosh HD:Applications:Automator stuff:01b iTunes Scripts:SongList.txt"
tell application "iTunes"
set thePlaylist to playlist "SongList"
try
delete every track of thePlaylist
end try
set MySongs to paragraphs of (TheFile) -- read artist names (separated by newlines) from the file
repeat with AnItem in MySongs -- get all tracks from each artist
set AnItem to (contents of AnItem)
if AnItem is not "" then try -- don't bother with empty names
set MyTracks to (location of file tracks of playlist "Music" whose name is AnItem)
--can also modify the above from "is" to "contains" or "_begins with_"
add MyTracks to thePlaylist
on error errmess -- oopsie (not found, etc)
log errmess -- just log it
end try
end repeat
end tell

OK, figured it out! Couldn't work out how to work around titles with commas in them (which I have a few of), so I ended up using tab separating them instead. So, once I have my tab-separated file, this code did the trick:
set thisTSVFile to (choose file with prompt "Select the CSV file")
readTabSeparatedValuesFile(thisTSVFile)
set theList to readTabSeparatedValuesFile(thisTSVFile)
tell application "iTunes"
set myPlaylist to playlist "Test1"
set sourcePlaylist to playlist "Music"
end tell
repeat with i from 2 to number of items in readTabSeparatedValuesFile(thisTSVFile)
--gets first column
set theName to item 1 of item i of theList
--gets second
set theArtist to item 2 of item i of theList
tell application "iTunes"
duplicate (some track of sourcePlaylist whose name is theName and artist is theArtist) to myPlaylist
end tell
delay 0.1
end repeat
on readTabSeparatedValuesFile(thisTSVFile)
try
set dataBlob to (every paragraph of (read thisTSVFile))
set the tableData to {}
set AppleScript's text item delimiters to tab
repeat with i from 1 to the count of dataBlob
set the end of the tableData to (every text item of (item i of dataBlob))
end repeat
set AppleScript's text item delimiters to ""
return tableData
on error errorMessage number errorNumber
set AppleScript's text item delimiters to ""
error errorMessage number errorNumber
end try
end readTabSeparatedValuesFile

You can use ObjectiveC (or probably Swift too) with XCode to do the heavy lifting (parsing the files) then hit iTunes from there, although it will likely be a lot slower than running in the iTunes process through its script menu.
Here's some ObjectiveC code that gets the current track title; you can adapt the method to suit a more complicated script like populating a playlist.
+(NSString *)getTitle {
return [self runAppleScriptAndReturnResult:#"Tell application \"iTunes\" \nreturn the name of the current track\nend tell"];
}
+(NSString *)runAppleScriptAndReturnResult:(NSString*)script {
NSAppleScript *appleScript=[[NSAppleScript alloc] initWithSource:[NSString stringWithFormat:#"with timeout of 3 seconds\n%#\nend timeout\n", script]];
return [[appleScript executeAndReturnError:nil] stringValue];
}

Related

Renaming files randomly with AppleScript isn't working

I would like to take a directory's files and rename them by reassigning their existing filenames randomly among the same files.
For example, if a directory had the following three files (name and file size):
filenameA 100KB
filenameB 200KB
filenameC 300KB
After running the script, it might look like this:
filenameB 100KB
filenameC 200KB
filenameA 300KB
So there would be 6 permutations for three files, 24 for four files, etc….
tell application "Finder"
tell application "System Events" to set theFiles to every file of folder "/path/to/my/directory"
repeat count of theFiles times
tell application "System Events" to set theFiles to every file of folder "/path/to/my/directory"
set randint1 to random number from 1 to count of theFiles
set randint2 to random number from 1 to count of theFiles
set theName1 to name of item randint1 of theFiles
set theName2 to name of item randint2 of theFiles
set name of item randint1 of theFiles to "randomname"
set name of item randint2 of theFiles to theName1
set name of item randint1 of theFiles to theName2
end repeat
end tell
Running this code doesn't return any errors, however it doesn't work either.
I hope it's clear what the script is supposed to do.
It isn't pretty but this following AppleScript code should accomplish, what I believe you are looking to achieve.
activate
set containingFolder to (choose folder) as text
tell application "Finder"
set theFiles to files of alias containingFolder as alias list
end tell
set theNumber to 0
set numberList to {}
set randomNumbersList to {}
repeat (count of theFiles) times
set theNumber to theNumber + 1
set end of numberList to theNumber
end repeat
repeat with thisNumber in numberList
set thisNumber to some item of numberList
if thisNumber is not in randomNumbersList then
set end of randomNumbersList to thisNumber
else
repeat while randomNumbersList contains thisNumber
set thisNumber to some item of numberList
delay 0.01
end repeat
set end of randomNumbersList to thisNumber
end if
end repeat
tell application "System Events"
repeat with i from 1 to count of theFiles
set thisItem to item i of theFiles
set name of thisItem to "randomname " & ¬
(item i of randomNumbersList as text) & "." & name extension of thisItem
end repeat
end tell
After grabbing the initial files and file names, this script randomizes the file names into a separate list. It then temporarily renames each file to prevent name collisions. Finally, it cycles through each file and changes its file name to a random one and removes that name from the pool of eligible names.
tell application "Finder"
-- choose folder, list its files and file names
set baseFol to choose folder
set origFiles to files of baseFol as alias list -- list of files
set origNames to name of files of baseFol as alias list -- list of file names
-- create list of randomized names
set randNames to {}
set cc to 0
repeat count of origNames times -- pool of file names
set cc to cc + 1 -- increment counter to one above current list length
repeat until (count of randNames) is cc
set somName to some item of origNames
if randNames does not contain somName then
set end of randNames to somName -- increment list length to match counter
end if
end repeat
end repeat
-- temporarily rename files to avoid name collisions
repeat with tmpName in origFiles
set name of tmpName to "g" & space & name of tmpName
end repeat
set tmpFiles to files of baseFol as alias list -- all g-files list
-- rename each file to final name
repeat with finNames in tmpFiles
set name of finNames to (item 1 of randNames) -- use random name
set randNames to rest of randNames -- remove used name from name pool
end repeat
end tell

Applescript: Highlight most recent file from group

I'd like to know how you'd go about creating an action where you could highlight a group of files and get the modification date from them, then have it highlight/select/label the file with the most recent date.
UPDATE: I want to do it on Applescript because I've gotten further in that. Here's what I have so far
set dateList to {}
tell application "Finder"
set inputList to get selection
repeat with i from 1 to count (inputList)
set end of dateList to get modification date of item i of inputList
end repeat
end tell
dateList
--Compare section...
set boolList to {}
set j to 1
repeat with i from 1 to count (dateList)
if i is (count (dateList)) then
set j to 0
end if
set end of boolList to item i of dateList > item (i + j) of dateList
end repeat
boolList
Looking at your existing applescript code this should sort any files you've selected by last modified date and return the latest result into a dialog box for you:
set dateList to {}
tell application "Finder"
set inputList to get selection
repeat with i from 1 to count (inputList)
set end of dateList to get modification date of item i of inputList
end repeat
end tell
--Compare section...
set modDate to item 1 of dateList
repeat with i from 1 to count (inputList)
if dateList's item i > modDate then
set modDate to dateList's item i
set theResult to displayed name of item i of inputList
set theResultDate to item i of dateList
end if
end repeat
--Display Result…
display alert "Most recently modified file in selection:" message "" & theResult & "
" & theResultDate
Dick got it, but I just fixed something and made it so it labels the file instead of a popup.
set dateList to {}
tell application "Finder"
set inputList to get selection
repeat with i from 1 to count (inputList)
set end of dateList to get modification date of item i of inputList
end repeat
end tell
--Compare section...
set theResult to item 1 of inputList as alias
set theResultDate to item 1 of dateList
set modDate to item 1 of dateList
repeat with i from 1 to count (inputList)
if dateList's item i > modDate then
set modDate to dateList's item i
set theResult to item i of inputList as alias
set theResultDate to item i of dateList
end if
end repeat
--Display Result…
--display alert "Most recently modified file in selection:" message "" & theResult & "
--" & theResultDate
tell application "Finder" to set label index of (theResult as alias) to 6
This will label it green, if you want a different color fiddle around with the index number 1-8, they're apparently not in order. Finder is also apparently smart enough to not count the selections in other open windows.
Thanks!
And finally, to make it useful as a right-click item, open Automator, make a Service, select at the top to use this on files/folders, drag Run Applescript in there, paste script, save. Now it will be available on right click. One downside is it seems the files need to stay selected until something is labeled. So no clicking while it's working.
You are making it more complicated than it needs to be:
tell application "Finder" to reveal item 1 of (sort (get selection) by modification date)
There is a bug in 10.7 and 10.8 that can make all of the suggested scripts almost unusable depending on the way they are run. If you open a new Finder window and select some files, tell application "Finder" to selection returns files selected in some window behind the frontmost window (or an empty list).
One workaround is to switch focus to another application and back:
activate app "SystemUIServer"
tell application "Finder"
activate
set label index of item 1 of (sort selection by modification date) to 6
end tell
You could also create an Automator service with a Run AppleScript action like this:
on run {input, parameters}
tell application "Finder"
sort (input as alias list) by modification date
set label index of item 1 of result to 6
end tell
end run

can i add several attachments from subfolders to Mail?

trying to send several mails with specific attachments for each address. every address has its own subfolder for attachments. the "grab attachments part" does not work and I am not sure if the handler is set up right: should I pass the subfolder to mail inside the handler or keep it as I have it. This is my first long script so please don't be too harsh ;-)
I'm thinking that i get closer to the working solution, I still don't get it to function. here is my script so far:
` with timeout of 600 seconds
-- Liste: Alle Empfänger
tell application "Contacts"
set emailList to {}
set testPersons to every person of group "Test"
repeat with thisTestPerson in testPersons
set end of emailList to (value of email of thisTestPerson) as string
end repeat
end tell
-- Liste fuer die Übergabe alphabetisch sortieren
set the_list to emailList
set otid to AppleScript's text item delimiters
set AppleScript's text item delimiters to {ASCII character 10} -- always a linefeed
set list_string to (the_list as string)
set new_string to do shell script "echo " & quoted form of list_string & " | sort -f"
set new_list to (paragraphs of new_string)
set AppleScript's text item delimiters to otid
-- Liste: Alle Subfolder
tell application "Finder"
set mainfolder to choose folder "select a folder"
set folderList to {}
set myFolders to every folder of mainfolder
repeat with attachFolder from 1 to (count of myFolders)
set end of folderList to attachFolder as string
end repeat
end tell
-- Sicherheits-Check
set count1 to count of myFolders
set count2 to count of new_list
if count1 is not equal to count2 then
display dialog "Houston, we have a problem:" & return & "Die beiden Listen sind nicht gleich lang..." buttons {"ok"} with icon 2
return
end if
end timeout
--handler processfolder(myFiles)
on processfolder(myFiles)
tell application "Mail"
activate
set theAddress to (item i of emailList)
set theMex to (make new outgoing message at end of outgoing messages with properties {visible:true, subject:"Subjectheader", content:"email body"})
tell content of theMex
make new attachment with properties {file name:FileList} at after last paragraph
end tell
tell theMex
make new to recipient at end of to recipients with properties {address:theAddress}
end tell
send theMex
end tell
end processfolder
-- grab attachments and send mail
tell application "Finder"
repeat with myFolder from 1 to (count of folderList)
set FileList to {}
set myFiles to entire contents of myFolder
repeat with thisFile in myFiles
set end of FileList to thisFile as string
end repeat
my processfolder(myFiles)
end repeat
end tell
display dialog (count1 as string) & " Nachrichten verschickt."
end`
i believe the handler should work alright. Matching the subfolder list with the address list still seems to be a problem, I am not sure if my repeat loop "grab attachment und send mail" does the trick. It is a tricky use of repeat loops and I am still struggling with it. any quick thoughts about what I am still doing wrong?
thanks for being helpful! i really appreciate this!
marco
You must pass the variables as parameters in the handler :
1- (item i of emailList) : i and emailList is not defined in the handler.
2- {file name:FileList} : FileList is not defined in the handler, file name must be a path of type alias or string, not a list of path.
set myFiles to entire contents of myFolder : the myfolder variable is an integer, entire contents will contains the folders and files, if the folder doesn't contains subfolders, entire contents is useless, use files of xFolder.
The rest is okay, but contains unnecessary lines.
Here is the script:
with timeout of 600 seconds
-- Liste: Alle Empfänger
tell application "Contacts"
set emailList to value of email 1 of every person of group "Test"
end tell
-- Liste fuer die Übergabe alphabetisch sortieren
set otid to AppleScript's text item delimiters
set AppleScript's text item delimiters to {linefeed}
do shell script "echo " & (quoted form of (emailList as string)) & " | sort -f"
set emailList to (paragraphs of the result)
set AppleScript's text item delimiters to otid
-- Liste: Alle Subfolder
activate
set mainfolder to choose folder "select a folder"
tell application "Finder" to set folderList to folders of mainfolder
-- Sicherheits-Check
set count1 to count folderList
if count1 is not equal to (count emailList) then
display dialog "Houston, we have a problem:" & return & "Die beiden Listen sind nicht gleich lang..." buttons {"ok"} cancel button "ok" with icon 2
end if
end timeout
-- grab attachments and send mail
repeat with i from 1 to count1
try
tell application "Finder" to set myFiles to (files of entire contents of (item i of folderList)) as alias list
my processfolder(myFiles, item i of emailList)
end try -- no error on empty folder
end repeat
display dialog (count1 as string) & " Nachrichten verschickt."
on processfolder(tFiles, theAddress)
tell application "Mail"
activate
tell (make new outgoing message at end of outgoing messages with properties {visible:true, subject:"Subjectheader", content:("email body" & linefeed & " ")})
make new to recipient at end of to recipients with properties {address:theAddress}
tell content to repeat with tFile in tFiles
make new attachment with properties {file name:tFile} at after last paragraph
make new character with data linefeed at after last paragraph
end repeat
send
end tell
end tell
end processfolder
it is done! Thanks to you and one or two other pros I now have a beautiful bulk mailing script routine using automator, a bash line and (mainly) applescript. I use it for job applications but you can use it for any case where you want individualised bulk emailing with Mail, MS Word and any given list of contacts in Excel (or Address Book for that matter). For the sake of being complete I will add all necessary steps. with any given list of x names, email addresses, personal addresses you can generate x subfolders, containing x personalized letters and not-personalized documents (thanks, Jack! adding the docs works perfectly). once you start the last script and select the folder you can watch mail sending them all out, addressing the person by name and attaching the right personalized letter! It corrects for foreign name spelling that is rendered differently in the email address. It works best for email addresses using the last name before the "#" and can now ignore the first name if it is set in front of the last name (i.e. firstname.lastname#company.com). Thank you all very much for the assistance! this was great team effort.
I shall post it as soon as I am home, should I post it up here and in the other related question or is there a sharing forum?

Applescript: Get filenames in folder without extension

I can get the names of all files in a folder by doing this:
tell application "Finder"
set myFiles to name of every file of somePath
end tell
How can I change the strings in myFiles so that they do not include the file extension?
I could for example get {"foo.mov", "bar.mov"}, but would like to have {"foo", "bar"}.
Current solution
Based on the accepted answer I came up with the code below. Let me know if it can be made cleaner or more efficient somehow.
-- Gets a list of filenames from the
on filenames from _folder
-- Get filenames and extensions
tell application "Finder"
set _filenames to name of every file of _folder
set _extensions to name extension of every file of _folder
end tell
-- Collect names (filename - dot and extension)
set _names to {}
repeat with n from 1 to count of _filenames
set _filename to item n of _filenames
set _extension to item n of _extensions
if _extension is not "" then
set _length to (count of _filename) - (count of _extension) - 1
set end of _names to text 1 thru _length of _filename
else
set end of _names to _filename
end if
end repeat
-- Done
return _names
end filenames
-- Example usage
return filenames from (path to desktop)
From http://www.macosxautomation.com/applescript/sbrt/index.html :
on remove_extension(this_name)
if this_name contains "." then
set this_name to ¬
(the reverse of every character of this_name) as string
set x to the offset of "." in this_name
set this_name to (text (x + 1) thru -1 of this_name)
set this_name to (the reverse of every character of this_name) as string
end if
return this_name
end remove_extension
Single line way of doing it, no Finder, no System Events. So more efficient and faster. Side effect (could be good, or bad): a file name ending with "." will have this character stripped out. Using "reverse of every character" makes it works if the name as more than one period.
set aName to text 1 thru ((aName's length) - (offset of "." in ¬
(the reverse of every character of aName) as text)) of aName
The solution as a handler to process a list of names:
on RemoveNameExt(aList)
set CleanedList to {}
repeat with aName in aList
set the end of CleanedList to text 1 thru ((aName's length) - (offset of ¬
"." in (the reverse of every character of aName) as text)) of aName
end repeat
return CleanedList
end RemoveNameExt
Here's an applescriptish method to get Finder's idea of what the stripped filename is but please note it will only work if you have NOT enabled the option in Finder's preferences to "Show all filename extensions":
set extension hidden of thisFile to true
set thisName to displayed name of thisFile
-- display dialog "hey"
set extension hidden of thisFile to false
Here's a full script that does what you wanted. I was reluctant to post it originally because I figured there was some simple one-liner which someone would offer as a solution. Hopefully this solution is not a Rube Goldberg way of doing things.
The Finder dictionary does have a name extension property so you can do something like:
tell application "Finder"
set myFiles to name extension of file 1 of (path to desktop)
end tell
So the above will get you just the extension of the first file on the user's desktop. It seems like there would be a simple function for getting the (base name - extension) but I didn't find one.
Here's the script for getting just the filenames without extension for every file in an entire directory:
set filesFound to {}
set filesFound2 to {}
set nextItem to 1
tell application "Finder"
set myFiles to name of every file of (path to desktop) --change path to whatever path you want
end tell
--loop used for populating list filesFound with all filenames found (name + extension)
repeat with i in myFiles
set end of filesFound to (item nextItem of myFiles)
set nextItem to (nextItem + 1)
end repeat
set nextItem to 1 --reset counter to 1
--loop used for pulling each filename from list filesFound and then strip the extension
--from filename and populate a new list called filesFound2
repeat with i in filesFound
set myFile2 to item nextItem of filesFound
set myFile3 to text 1 thru ((offset of "." in myFile2) - 1) of myFile2
set end of filesFound2 to myFile3
set nextItem to (nextItem + 1)
end repeat
return filesFound2
Though the above script does work if anyone knows a simpler way of doing what the OP wanted please post it cause I still get the sense that there should be a simpler way of doing it. Maybe there's a scripting addition which facilitates this as well. Anyone know?
Based on Lauri Ranta's nice solution, which works for extensions that Finder doesn't know about:
set delims to AppleScript's text item delimiters
set AppleScript's text item delimiters to "."
set myNames to {}
tell application "Finder"
set myFiles to name of every file of (path to Desktop)
repeat with myfile in myFiles
set myname to name of file myfile
if myname contains "." then set myname to (text items 1 thru -2 of myname) as text
set end of myNames to myname
end repeat
end tell
set AppleScript's text item delimiters to delims
return myNames
I don't know how to remove the extensions when you use the "every file"
syntax but if you don't mind looping (loop not shown in example) through each file then this will work:
tell application "Finder"
set myFile to name of file 1 of somePath
set myFile2 to text 1 thru ((offset of "." in myFile) - 1) of myFile
end tell
Within a tell "Finder" block this collects file names stripped of the extension in myNames:
repeat with f in myFiles
set myNames's end to ¬
(f's name as text)'s text 1 thru -(((f's name extension as text)'s length) + 2)
end repeat
For a single file I found the answer here, copied below.
set theFileName to "test.jpg"
set thePrefix to text 1 thru ((offset of "." in theFileName) - 1) of theFileName
This is a little more than you need, but it will handle more than one "." in a file name.
Assuming that a file alias is passed in to the method.
on Process(myFileAlias)
set myFile to myFileAlias as string
set AppleScript's text item delimiters to ":"
set myItemCount to the number of text items in myFile
set myFileName to the last text item of myFile
set myFilePath to text items 1 through (myItemCount - 1) of myFile
set AppleScript's text item delimiters to "."
set myItemCount to the number of text items in myFileName
set myExtension to the last text item of myFile
// This line is the key.
set myShortFilename to text items 1 through (myItemCount - 1) of myFileName as string
log (myFileName)
log (myShortFilename)
end

How do I fix my AppleScript to delete a selection of tracks in iTunes?

I've created the following AppleScript for deleting all the selected tracks:
property okflag : false
-- check if iTunes is running
tell application "Finder"
if (get name of every process) contains "iTunes" then set okflag to true
end tell
if okflag then
tell application "iTunes"
if selection is not {} then
repeat with this_track in selection
try
try
set cla to class of this_track
set floc to (get location of this_track)
delete this_track
on error error_message number error_number
display alert error_message message ("Error number: ") & error_number & "."
end try
if cla is file track then
my delete_the_file(floc)
end if
end try
end repeat
end if
end tell
end if
to delete_the_file(floc)
try
-- tell application "Finder" to delete floc
do shell script "mv " & quoted form of POSIX path of (floc as string) & " " & quoted form of POSIX path of (path to trash as string)
on error
display dialog "Track deleted, but could not be moved to trash" buttons {"Hmm"} default button 1 with icon 1
end try
end delete_the_file
It works fine when I select a single item, but when I select more than one I get: "Can't get location of item 2 of selection" (error number -1728). I believe this is because by deleting a track, the script's index into the selection is corrupted.
I thought I'd try making my own list of tracks to be deleted first:
tell application "iTunes"
if selection is not {} then
set to_delete to {}
repeat with this_track in selection
try
set cla to class of this_track
set floc to (get location of this_track)
if cla is file track then
set pair to {this_track, floc}
set to_delete to to_delete & pair
end if
end try
end repeat
repeat with pair in to_delete
set the_track to item 1 of pair
set floc to item 2 of pair
delete the_track
my delete_the_file(floc)
end repeat
end if
end tell
But then I get 'Can't get item 1 of item 1 of selection of application "iTunes".' I think the problem is "this_track" is not an object of class Track, but an item of a selection. How do I get the actual track object from the selection item?
If you don't see the solution, I'll welcome tips on debugging or any other suggestions.
The variable this_track is a reference to an object specifier. You have to use the contents property to get the enclosed object specifier. The same is true for accessing the variable pair in the second loop. See the class reference section on the class reference in the AppleScript language guide.
Another problem exists in the way the list to_delete is being built. The statement set to_delete to to_delete & pair will not produce a list of pairs but a flat list. See the class reference section on the class list in the AppleScript language guide.
Here's a version of your second script, where these bugs have been removed:
tell application "iTunes"
if selection is not {} then
set to_delete to {}
repeat with this_track in selection
try
set cla to class of this_track
set floc to (get location of this_track)
if cla is file track then
set pair to {contents of this_track, floc}
copy pair to end of to_delete
end if
end try
end repeat
repeat with pair in to_delete
set the_track to item 1 of contents of pair
set floc to item 2 of contents of pair
delete the_track
my delete_the_file(floc)
end repeat
end if
end tell

Resources