Applescript to read mp3 metatags and sort file into folder

Here is the “short old-fashioned” SLOW version.

use AppleScript version "2.4" # requires at least Yosemite
use scripting additions
use framework "Foundation"

script o
	property targetFile : missing value
	property filesMoved : 0
	property filesNotMoved : 0
end script

# The main code

set theFolder to choose folder with prompt "Please select a folder containing MP3/AIFF files" # ADDED

set theFiles to its listFolder:(POSIX path of theFolder) # ADDED

set o's targetFile to ((path to desktop as text) & "report Mp3_3pMtroper.txt") as «class furl»

# Creates the file reporting files not moved
my writeto(o's targetFile, "", «class utf8», false)

repeat with aFile in theFiles
	my treatThisFile(aFile)
end repeat

# Write the count of moved and not moved files in the text file "report Mp3_3pMtroper.txt"
my writeto(o's targetFile, linefeed & o's filesMoved & " were moved." & linefeed & o's filesNotMoved & " were not moved !", «class utf8», true)

#===== Below are the handlers

on treatThisFile(theFile)
	set posixFilePath to the quoted form of (POSIX path of theFile)
	
	set genreTag to (do shell script "mdls " & posixFilePath & " -name kMDItemMusicalGenre")
	set artistTag to (do shell script "mdls " & posixFilePath & " -name kMDItemAuthors")
	set tempoTag to (do shell script "mdls " & posixFilePath & " -name kMDItemTempo")
	set keyTag to (do shell script "mdls " & posixFilePath & " -name kMDItemKeySignature")
	
	set theGenre to text ((offset of "= " in genreTag) + 3) through -2 of genreTag
	set theArtist to text ((offset of "= " in artistTag) + 9) through -4 of artistTag
	set theTempo to text ((offset of "= " in tempoTag) + 2) through -1 of tempoTag
	set theKey to text ((offset of "= " in keyTag) + 3) through -2 of keyTag
	
	#SOME MP3 TAGS ARE NOT PROPERLY FILLED OUT- THIS BELOW PART DOESNT WORK AS SCRIPT PROCEEDS??????. # problem solved below
	if ((count theGenre) ≤ (count "(null)") and theGenre contains "null") or ((count theArtist) ≤ (count "(null)") and theArtist contains "null") or ((count theTempo) ≤ (count "(null)") and theTempo contains "null") or ((count theKey) ≤ (count "(null)") and theKey contains "null") then
		
		# The file can't be moved. Write it's path in the dedicated text file
		my writeto(o's targetFile, theFile as text, «class utf8», true)
		set o's filesNotMoved to (o's filesNotMoved) + 1
	else
		# There was no need to extract this info if one of the four other ones is null
		set titleTag to (do shell script "mdls " & posixFilePath & " -name kMDItemTitle")
		set theTitle to text ((offset of "= " in titleTag) + 3) through -2 of titleTag
		
		# For some files, theTitle is returned as "null" or "(null)" so it may generate duplicates # Edited
		# I choose to use the original name deprieved of the extension
		
		if (count theTitle) ≤ (count "(null)") and theTitle contains "null" then # EDITED
			tell application "System Events" # ADDED
				set theTitle to text 1 thru -5 of (get name of file (theFile as text))
			end tell
		end if
		
		set renamedTitle to ("{" & theTempo & "-" & theKey & "}  " & theArtist & " - " & theTitle)
		
		set DestFolder to ("/Volumes/Production/test/" & theGenre & "/" & theArtist)
		--set DestFolder to ("/Volumes/Macintosh HD/Users/Important/Test/" & theGenre & "/" & theArtist)
		
		my createFolder:DestFolder
		
		set DestFolder to POSIX file DestFolder
		
		set theFile to (theFile as text) as alias # ADDED. Finder refuse to get extension or rename the object returned by the ASObjC handler
		tell application "Finder"
			set theExtension to name extension of theFile
			set name of theFile to renamedTitle & "." & theExtension #AS MAYBE AIFF OR MP3
			try
				move theFile to DestFolder
				set o's filesMoved to (o's filesMoved) + 1
				-- log ">>>>> " & theFile & linefeed & DestFolder
			on error errMsg
				-- display dialog renamedTitle & errMsg # IF ALREADY EXISTS WILL PROMPT
				my writeto(o's targetFile, (theFile as text) & errMsg, «class utf8», true)
				set o's filesNotMoved to (o's filesNotMoved) + 1
			end try
			
		end tell
	end if # (moveitGenre is false) or.
end treatThisFile

#=====

on listFolder:POSIXPath # handler using ASObjC
	set fileManager to current application's NSFileManager's defaultManager()
	set aURL to current application's |NSURL|'s fileURLWithPath:POSIXPath
	set theOptions to (current application's NSDirectoryEnumerationSkipsPackageDescendants as integer) + (current application's NSDirectoryEnumerationSkipsHiddenFiles as integer)
	set theEnumerator to fileManager's enumeratorAtURL:aURL includingPropertiesForKeys:{} options:theOptions errorHandler:(missing value)
	set theFiles to theEnumerator's allObjects()
	set theFiles to theFiles's filteredArrayUsingPredicate:(current application's NSPredicate's predicateWithFormat:"(lastPathComponent ENDSWITH '.mp3') OR (lastPathComponent ENDSWITH '.aiff')")
	return theFiles as list # if we work upon files
	--return (theFiles's valueForKey:"path") as list # if we work upon POSIX paths
end listFolder:


-- Creates a new folder. There is no error if the folder already exists, and it will also create intermediate folders if required
on createFolder:POSIXPath
	set |⌘| to current application
	set theFolderURL to |⌘|'s |NSURL|'s fileURLWithPath:POSIXPath
	set theFileManager to |⌘|'s NSFileManager's defaultManager()
	set {theResult, theError} to theFileManager's createDirectoryAtURL:theFolderURL withIntermediateDirectories:true attributes:(missing value) |error|:(reference)
	if not (theResult as boolean) then error (theError's |localizedDescription|() as text)
end createFolder:

#=====
(*
Handler borrowed to Regulus6633 - http://macscripter.net/viewtopic.php?id=36861
*)
on writeto(targetFile, theData, dataType, apendData)
	-- targetFile is the path to the file you want to write
	-- theData is the data you want in the file.
	-- dataType is the data type of theData and it can be text, list, record etc.
	-- apendData is true to append theData to the end of the current contents of the file or false to overwrite it
	try
		--set targetFile to targetFile as «class furl» # no need, the calling instructions pass a «class furl» object
		set openFile to open for access targetFile with write permission
		if not apendData then set eof of openFile to 0
		write (theData & linefeed) to openFile starting at eof as dataType
		close access openFile
		return true
	on error
		try
			close access targetFile
		end try
		return false
	end try
end writeto

#=====

I hope that I didn’t forgot a change to match your settings after testing it with mines.

Added code writing the count of moved and the count of not-moved files in the file : “report Mp3_3pMtroper.txt”

Yvan KOENIG running Sierra 10.12.4 in French (VALLAURIS, France) jeudi 20 avril 2017 19:49:29

thanks Yvan

I’m looking to change the renaming format to: {118-Gmaj} Elton John - Tiny Dancer

In the script I found where it does it, however not sure how to script it (the older script I could do it)

Also would it be hard to script how many mp3/aiff files were moved after completing?


			set theTitle to (metaItem's valueForAttribute:(|⌘|'s NSMetadataItemTitleKey))   --SET THE NEW FILE NAME
			if (theTitle is missing value) then
				--set theTitle to "(no title)"
				-- Derive a destination path for the file with a new name made up from these details.
				# but replace theTtle by the original fileName
				set destinationFileName to (|⌘|'s NSString's stringWithFormat_("%@-%@ %@", theTempo, theKey, theName))
			else
				# CAUTION, if the original title contained underscores they are replaced by slashes in theTitle.
				# We must revert that
				set theTitle to (theTitle's stringByReplacingOccurrencesOfString:"/" withString:"_")
				-- Derive a destination path for the file with a new name made up from these details.
				set destinationFileName to (|⌘|'s NSString's stringWithFormat_("%@-%@ %@.%@", theTempo, theKey, theTitle, theExtension))
			end if


My physical Internet connexion is down. So I use a WiFi hotSpot whose performance are far from good.
Don’t worry if responses don’t come quickly.

As far as I know, If I understand well, at this time you get [format]118-Gmaj Tiny Dancer.mp3[/format]
I ask because the metadatas tempo and key are never defined here.

If it’s really what you get, edit the instructions as :

			if (theTitle is missing value) then
				--set theTitle to "(no title)"
				-- Derive a destination path for the file with a new name made up from these details.
				# but replace theTtle by the original fileName
				set destinationFileName to (|⌘|'s NSString's stringWithFormat_("{%@-%@} %@ - %@", theTempo, theKey, theArtist, theName))
			else
				# CAUTION, if the original title contained underscores they are replaced by slashes in theTitle.
				# We must revert that
				set theTitle to (theTitle's stringByReplacingOccurrencesOfString:"/" withString:"_")
				-- Derive a destination path for the file with a new name made up from these details.
				set destinationFileName to (|⌘|'s NSString's stringWithFormat_("{%@-%@} %@ - %@.%@", theTempo, theKey, theArtist, theTitle, theExtension))
			end if

The newly required feature is available in messages #53 and #54

Yvan KOENIG running Sierra 10.12.4 in French (VALLAURIS, France) vendredi 21 avril 2017 14:50:14

awesome that worked. Would it be hard to put a couple lines of script in, so that at the end I know how many files were moved?

Like a prompt saying “412 Files were successfully move to /test/test”

I carefully wrote :

# Edited on 2017/04/21 : insert theArtist in the new fileNames. Write the count of moved files and the count of not moved files in the report : "report Mp3_3pMtroper.txt"

at the beginning of the script using ASObjC.

At the beginning of the old fashioned one everybody may read :

use AppleScript version "2.4" # requires at least Yosemite
use scripting additions
use framework "Foundation"

script o
	property targetFile : missing value
	property filesMoved : 0
	property filesNotMoved : 0
end script

# The main code

set theFolder to choose folder with prompt "Please select a folder containing MP3/AIFF files" # ADDED

set theFiles to its listFolder:(POSIX path of theFolder) # ADDED

set o's targetFile to ((path to desktop as text) & "report Mp3_3pMtroper.txt") as «class furl»

# Creates the file reporting files not moved
my writeto(o's targetFile, "", «class utf8», false)

repeat with aFile in theFiles
	my treatThisFile(aFile)
end repeat

# Write the count of moved and not moved files in the text file "report Mp3_3pMtroper.txt"
my writeto(o's targetFile, linefeed & o's filesMoved & " were moved." & linefeed & o's filesNotMoved & " were not moved !", «class utf8», true)

Isn’t it clear enough ?

Yvan KOENIG running Sierra 10.12.4 in French (VALLAURIS, France) lundi 24 avril 2017 18:22:46

its clear looking at the script. But I need it in the newer script because with the old fashion script it has a few problems- which I have come to realise…such as doing bulk files creates strange artist name folders.

So I’m trying to incorporate the below into the “newer” script but I’m getting unrecognized handler sent to object at the line:

my writeto(o’s targetFile, “”, «class utf8», false)

error


error "*** -[BAGenericObjectNoDeleteOSAID writeto]: unrecognized selector sent to object <BAGenericObjectNoDeleteOSAID @0x7fe15e85fa70: OSAID(1) ComponentInstance(0x840007)>" number -10000


script o
   property targetFile : missing value
   property filesMoved : 0
   property filesNotMoved : 0
end script

set o's targetFile to ((path to desktop as text) & "report Mp3_3pMtroper.txt") as «class furl»

my writeto(o's targetFile, "", «class utf8», false)

my writeto(o's targetFile, linefeed & o's filesMoved & " were moved." & linefeed & o's filesNotMoved & " were not moved !", «class utf8», true)

Oops

One instruction was missing.
I added it.

I’m back while the Wifi Hotspot is active.

The script using ASObjC doesn’t use the handler Writeto.
It rely upon ASObjC instructions which were available in the script.

	
	set theCounts to (|⌘|'s NSString's stringWithString:(linefeed & filesMoved & " were moved." & linefeed & filesNotMoved & " were not moved !"))
	(newArray's addObject:theCounts)
	set outDoc to (newArray's componentsJoinedByString:linefeed)
	set outPath to (path to desktop as text) & "report Mp3_3pMtroper.txt"
	set outPath to (|⌘|'s NSString's stringWithString:(POSIX path of outPath))
	set theResult to outDoc's writeToFile:outPath atomically:true encoding:(current application's NSUTF8StringEncoding) |error|:(missing value)

Due to the missing instruction, the counter was not incremented so your “report Mp3_3pMtroper.txt” file was displaying something like :

0 were moved.
xx were not moved !

Yvan KOENIG running Sierra 10.12.4 in French (VALLAURIS, France) mardi 25 avril 2017 14:42:33

It seems now that it doesn’t create the txt file. I have added the missing instruction down the bottom of mainbusiness??

Yet the script still moves the file to the destination. I also tried taking away the defined properties (targetfile, filesmoved, filesnotmoved) but it does the same thing.

the other issue I get is when I select a folder that has “multiple” files within, I get a error at line:


		-- stop and get the first NSMetadataItem (there can only be one in this case)
		theQuery's stopQuery()
		set metaItem to (theQuery's resultAtIndex:0)

Error


error "*** -[NSMetadataQuery resultAtIndex:]: index (0) out of bounds (0)" number -10000

When I apply the script of message #53 on a folder containing 15 mp3 files,
as I posted in my late message, 11 are moved, 4 aren’t.

I compared the content of your last message to the one in message #53.

Below is the list of oddities available:

# Prepare the counters of treated files # YOU DROPPED IT
set filesMoved to 0 # YOU DROPPED IT
set filesNotMoved to 0 # YOU DROPPED IT

# You have.
set my destinationRootPath to "/Volumes/Production/test6" --YOU NEED TO CHANGE DESTINATION PATH 
# the comment is incorrect since several days. 
# The instruction is designed to define the path used on MY machine.
# it MUST be:
set my destinationRootPath to "/Volumes/Macintosh HD/Users/Important/Test/" --"/Volumes/Production/test3"

set filesNotMoved to filesNotMoved + 1 # YOU DROPPED IT

# The comment to the instruction below is WRONG. It's not mine. The variable theTitle is not set to the file name !!!!
set theTitle to (metaItem's valueForAttribute:(|⌘|'s NSMetadataItemTitleKey)) --SET THE NEW FILE NAME


set filesMoved to filesMoved + 1 # YOU DROPPED IT


set filesMoved to filesMoved + 1 # ADDED on 2017/04/25 -- YOU DROPPED IT

--if (newArray's |count|()) as integer > 0 then # WAS REMOVED WHEN I ADDED THE COUNTERS


-- end if # WAS REMOVED WHEN I ADDED THE COUNTERS


Please use the script in message #53 without changing anything except the path used on your machine defined by the instruction :

property destinationRootPath : “/Volumes/Production/test/” # I assume that it’s the one you need # Is silently changed when the script run on my machine

If you want to work upon Test6, it’s this instruction and only this one which must be edited.

Yvan KOENIG running Sierra 10.12.4 in French (VALLAURIS, France) jeudi 27 avril 2017 11:56:01

ok, I ran the script from #53 WITHOUT editing anything. It worked for one folder! which was the first time I ran the script.

However the second time I ran the script I selected another folder, and got the same error again.


error "*** -[NSMetadataQuery resultAtIndex:]: index (0) out of bounds (0)" number -10000

At line:
– stop and get the first NSMetadataItem (there can only be one in this case)
theQuery’s stopQuery()
set metaItem to (theQuery’s resultAtIndex:0) <<<<got error at this line.

ok now it didn’t error this time, but only moved one file? which is strange.

The source folder is located on my desktop not on the Production drive- but that shouldn’t matter.

Also the source folder is a already sorted mp3/aiff folder structure with the genre>artist from the script. Inside some artist folders are mp3s, so It should find them and move them, but instead throws this error.

When the script moved only one file, what is reported in the text file ?
You are supposed to see the list of the files which were not moved.
More,the script is carefully designed so that the history displayed in the dedicated window is supposed to report something like :
[format]
(Don’t move : /Users/???/Desktop/111 autre/Ah ! Vous dirai-je maman.mp3
because at least one of these flags is false → moveItArtist = false, moveItGenre = false, moveItKey = false, moveItTempo = false
)
(Don’t move : /Users/???/Desktop/111 autre/Aldo Romano, Louis Sclavis, Henri Texier - live 2005 - 3-3.mp3
because at least one of these flags is false → moveItArtist = false, moveItGenre = false, moveItKey = false, moveItTempo = false
)
(Don’t move : /Users/???/Desktop/111 autre/Ancient_Beijing.mp3
because at least one of these flags is false → moveItArtist = false, moveItGenre = true, moveItKey = false, moveItTempo = false
)
(Don’t move : /Users/???/Desktop/111 autre/Anne Sylvestre - Une sorcière comme les autres .mp3
because at least one of these flags is false → moveItArtist = false, moveItGenre = false, moveItKey = false, moveItTempo = false
)
(Don’t move : /Users/???/Desktop/111 autre/Bach_Two_Part_Invention_in_C_Major.mp3
because at least one of these flags is false → moveItArtist = false, moveItGenre = false, moveItKey = false, moveItTempo = false
)
(Don’t move : /Users/???/Desktop/111 autre/Barbara (1962) les boutons dorés.mp3
because at least one of these flags is false → moveItArtist = false, moveItGenre = false, moveItKey = false, moveItTempo = false
)
[/format]

According to what you asked, a file is not moved if at least ONE of the 4 flags is set to false because the related metadata is unavailable.

No idea about what you get.

With the folder “1dossier” I got:
[format]/Users/???/Desktop/1 dossier/Anne Sylvestre - Mes amis d’autrefois (Live).mp3
/Users/???/Desktop/1 dossier/Anne Sylvestre en studio _ Des calamars à l’harmonica 2.mp3
/Users/???/Desktop/1 dossier/Anne Sylvestre en studio _ Des calamars à l’harmonica.mp3
/Users/???/Desktop/1 dossier/Jolie bouteille Sacrée bouteille.mp3
/Users/???/Desktop/1 dossier/Mouloudji chante Boris Vian le politique.mp3
/Users/???/Desktop/1 dossier/MOUSTAKI Maman Papa.mp4.mp3
/Users/???/Desktop/1 dossier/Romano-Sclavis-Texier - live 2005 - 33.mp3
/Users/???/Desktop/1 dossier/Serge Reggiani - Ma fille (Lyrics).mp3
/Users/???/Desktop/1 dossier/Serge Reggiani -Le Petit Garçon.mp3
/Users/???/Desktop/1 dossier/Serge Reggiani Le petit garçon.mp3
/Users/???/Desktop/1 dossier/Supplique pour être enterré sur une plage de Sète.mp3
/Users/???/Desktop/1 dossier/The Future of Jazz_ Billy Taylor_George Russell_Bill Evans.mp3
/Users/???/Desktop/1 dossier/Un été.mp3

11 were moved.
13 were not moved ![/format]

With the folder “111 autre” containing 89 files in 4 subfolders I got :

[format]
89 were moved.
0 were not moved ![/format]

Yvan KOENIG running Sierra 10.12.4 in French (VALLAURIS, France) jeudi 27 avril 2017 16:25:04

ok I’ve uploaded a screen capture video showing the issue. You will get a idea of the source folder structure and files I’m trying to move.

https://youtu.be/z75mEF-ivUY

I just tried a artist folder from within that “Deep House” folder and it worked fine. Got the text file with the following text.

2 were moved.
0 were not moved !

However when I get this error, it doesn’t create a text file.

I apologize but your video doesn’t help.
With my 73 years old eyes I’m quite unable to read what is displayed.

I’m always unable to guess what fails on your machine.
I made new tests with a folder containing 21 folders and didn’t got the described error.

It would have been more useful to reproduce what is displayed in the history window.

I replaced the content of message #53 by an edited version.
This time it write infos about not saved file one by one like the old fashioned script.
I inserted a lot of log instructions.
When you will get the infamous error, looking in the history window will tell you which is the last instruction correctly executed.
If you pass this info it would be easier to try to understand what failed.

Yvan KOENIG running Sierra 10.12.4 in French (VALLAURIS, France) samedi 29 avril 2017 18:14:03

ok I ran the script from #53
Got the error again, seems to be stopping at point 27. A text file was created on the desktop but nothing in it.

This is from log history window - replies -


tell application "Script Editor"
	choose folder with prompt "Please select folder"
		--> alias "Macintosh HD:Users:mattsquires:Desktop:SOURCE:Deep House:"
end tell
(*point 000*)
tell current application
	path to desktop as text
		--> "Macintosh HD:Users:mattsquires:Desktop:"
	(*point 010*)
	path to home folder as text
		--> "Macintosh HD:Users:mattsquires:"
	(*point 014*)
	(*point 015*)
	(*point 016*)
	(*point 017*)
	(*point 018*)
	(*point 019*)
	(*point 020*)
	(*point 021*)
	(*point 022*)
	(*point 023*)
	(*point 024*)
	(*point 025*)
	(*point 026*)
	(*point 027*)
Result:
error "*** -[NSMetadataQuery resultAtIndex:]: index (0) out of bounds (0)" number -10000


ok I’ve found the issue, it was a mp3 file with some odd characters in its names.

Once I took that out of the folder it worked fine.

When the script sorted it previously, it put some weird stuff in the title.

I’ve uploaded the original and sorted mp3 here, so you can try & see why it would stop the script from working.

https://www.dropbox.com/sh/twxdfvgxm4emi9y/AABLch6GBfOQodiNJNQK5dt7a?dl=0

The sorted problematic file name was:

{125-Bbm} A.Squared \U00bb download-techno.com - Third Eye (Original Mix) » download-techno.com.mp3

Is there anyway to prevent this? Without having to go through hundreds looking to change any long filenames.

Would be fine to report which is the last point logged when you treat the infamous file.
This would help to know where a change would be welcome.
According to the given string, I may know that
theTempo is 125
theKey is Bbm
theArtist is A.Squared \U00bb download-techno.com
theTitle is Third Eye (Original Mix) » download-techno.com

It appears that theArtist isn’t encoded the same way than theTitle.
Alas, it’s not the encoding which I encountered on my side, encoding treated by the handler

# Called with sourceString defined as NSString
on decodeText(theText) # Handler written by Nigel GARVEY
   set |⌘| to current application
   --set theText to |⌘|'s class "NSString"'s stringWithString:(theText)
   -- If the string contains at least two consecutive 8+bit characters, assume it's a mangled result.
   if ((theText's rangeOfString:("[\\u0080-\\U0010ffff]{2}") options:(|⌘|'s NSRegularExpressionSearch))'s |length|() > 0) then
       set dataObj to (theText's dataUsingEncoding:(|⌘|'s NSISOLatin1StringEncoding))
       set theText to (|⌘|'s class "NSString"'s alloc()'s initWithData:(dataObj) encoding:(|⌘|'s NSUTF8StringEncoding))
   end if
   return theText as text
end decodeText

Alas I’m unable to create a general modified version of this handler.
If Nigel GARVEY see this thread maybe he would build such handler.
At this time I created one able to treat two characters

on decodeText2(theText) # Temporary transcoder handler
	set theText to current application's class "NSString"'s stringWithString:(theText)
	set theText to my replaceSeveralStrings:theText existingStrings:{"\\U00ab", "\\U00bb"} newStrings:{"«", "»"}
	return theText
end decodeText2

I inserted the enhanced script in message #53.

Yvan KOENIG running Sierra 10.12.4 in French (VALLAURIS, France) dimanche 30 avril 2017 18:02:44

Browser: Safari 601.7.7
Operating System: Mac OS X (10.10)

I enhanced the handler decodeText2

Yvan KOENIG running Sierra 10.12.4 in French (VALLAURIS, France) lundi 1 mai 2017 14:12:13

Hi Yvan.

I only noticed this earlier this evening just before I had to go out.

I don’t understand the problem it’s supposed to cure. The MP3 in squigee90’s “Original” folder has these metadata, as noted by my own ‘log’ commands in the post #53 script:

Even with decodeText2() switched out, the script relocates the file as “{125-Bbm} A.Squared » download-techno.com - Third Eye (Original Mix) » download-techno.com.mp3” in a folder called “A.Squared » download-techno.com in a folder called “Deep House” in the destination folder set near the top of the code. It does not “put some weird stuff in the title” and change the first chevron to “\U00bb”.

There’s no file in squigee90’s “Problem” folder. However, renaming the good file in the manner described and putting it back does lead to the error when the script tries to access the metadata query’s resultAtIndex:0. Basically, the query returns an empty array. The strange file name stops it from returning any results at all, so there’s no point in having a special handler to massage the Artist result.

I don’t know why the file name causes the metadata query to fail. When the \U00bb is manually changed back to », everything works again ” on my system, anyway.