Have a look at this topic:
I didn’t find anything on dougscripts, or in the dictionary for the app. Since I wrote that post that you linked to I have managed to set playlists as variables.
I’ve managed to do what I wanted to do via UI scripting. It’s not ideal and I’ve managed to get it to export playlists, but would have preferred a ‘non UI scripting’ method
I have not installed Catalina yet. Therefore, I will show how I would do this with iTunes.app. For your purposes try replace “iTunes” with “Music”. My script is one starting point and may be enhanced.
If desired, you can build a table in a text document too. Like that:
property playListName : "Music"
set outputFile to ((path to desktop as text) & "PlaylistInfo.txt")
set aText to ""
set {aNameMaximum, artistMaximum} to {0, 0}
tell application "iTunes"
set theTracks to every track in playlist playListName
-- Get maximum lengths for each property
repeat with aTrack in theTracks
set aNameLength to length of (get name of aTrack)
if aNameLength > aNameMaximum then set aNameMaximum to aNameLength
set artistLength to length of (get artist of aTrack)
if artistLength > artistMaximum then set artistMaximum to artistLength
end repeat
-- build the body (text)
repeat with aTrack in theTracks
-- name
set namePlaceholder to (get name of aTrack)
repeat while (length of namePlaceholder) < aNameMaximum
set namePlaceholder to namePlaceholder & space
end repeat
set aText to aText & namePlaceholder & " | "
-- artist
set artistPlaceholder to (get artist of aTrack)
repeat while (length of artistPlaceholder) < artistMaximum
set artistPlaceholder to artistPlaceholder & space
end repeat
set aText to aText & artistPlaceholder & " | "
-- duration
set aDuration to (get duration of aTrack) as integer
set aDuration to my timeConvert(aDuration)
set aText to aText & aDuration & return
end repeat
end tell
try -- write text to text file
set fileReference to open for access file outputFile with write permission
set eof of fileReference to 0
write aText to fileReference
close access fileReference
on error
try
close access file outputFile
end try
end try
-- Duration converting handler ------------------
on timeConvert(tis) --tis: time in seconds
set theSeconds to tis mod 60
set theMinutes to tis div 60
set theHours to theMinutes div 60 as string
set theMinutes to theMinutes mod 60
if length of theHours is 1 then --only the hours is infinte so it can be more than two decimals.
set theHours to "0" & theHours
end if
theHours & ":" & characters -2 thru -1 of ("00" & theMinutes as string) & ":" & ¬
characters -2 thru -1 of ("00" & theSeconds as string) as string
end timeConvert
Here info on accessing the playlist.
And the playlists tracks.
https://dougscripts.com/itunes/itinfo/playlists01.php
Just change the tell iTunes to Music.
I think you can access each “item”
Playlist or tracks.
All properties.
And then you have to convert that dictionary to text.
see the links above for dougsscripts links.
You didn’t look very hard.
In the Script Debugger Dictionary here what comes up for iTunes.
Should be same for Music
Sorry, I missed this! And thanks, I’ll take a look.
Sorry, I missed this!
I guess I need to learn to search better before I try writing scripts. Thanks a lot!
Hi, Jono.
I am now at new Catalina, and was able to test my script above with Music.app. Well, it works as expected. Here, I replaced tell application “iTunes” with tell application “Music”, and added writing the text as UTF-8, to fix the problem with none English track names:
property playListName : "Music"
set outputFile to ((path to desktop as text) & "PlaylistInfo.txt")
set aText to ""
set {aNameMaximum, artistMaximum} to {0, 0}
tell application "Music" -- EDITED
set theTracks to every track in playlist playListName
-- Get maximum lengths for each property
repeat with aTrack in theTracks
set aNameLength to length of (get name of aTrack)
if aNameLength > aNameMaximum then set aNameMaximum to aNameLength
set artistLength to length of (get artist of aTrack)
if artistLength > artistMaximum then set artistMaximum to artistLength
end repeat
-- build the body (text)
repeat with aTrack in theTracks
-- name
set namePlaceholder to (get name of aTrack)
repeat while (length of namePlaceholder) < aNameMaximum
set namePlaceholder to namePlaceholder & space
end repeat
set aText to aText & namePlaceholder & " | "
-- artist
set artistPlaceholder to (get artist of aTrack)
repeat while (length of artistPlaceholder) < artistMaximum
set artistPlaceholder to artistPlaceholder & space
end repeat
set aText to aText & artistPlaceholder & " | "
-- duration
set aDuration to (get duration of aTrack) as integer
set aDuration to my timeConvert(aDuration)
set aText to aText & aDuration & return
end repeat
end tell
try -- write text to text file
set fileReference to open for access file outputFile with write permission
set eof of fileReference to 0
write aText to fileReference as «class utf8» -- EDITED
close access fileReference
on error
try
close access file outputFile
end try
end try
-- Duration converting handler ------------------
on timeConvert(tis) --tis: time in seconds
set theSeconds to tis mod 60
set theMinutes to tis div 60
set theHours to theMinutes div 60 as string
set theMinutes to theMinutes mod 60
if length of theHours is 1 then --only the hours is infinte so it can be more than two decimals.
set theHours to "0" & theHours
end if
theHours & ":" & characters -2 thru -1 of ("00" & theMinutes as string) & ":" & ¬
characters -2 thru -1 of ("00" & theSeconds as string) as string
end timeConvert
That’s brilliant. Thanks a lot, much appreciated!
Following on from Jon’s post Feb '20 on this, does anyone have a script (or know how I can adapt an existing script) that will batch export my Music Library’s entire collection of playlists (even better into the relevant (playlist) folders in which they are arranged within Music)? Since the DougScripts iTunes script that I paid for did such an incredible job, I haven’t found anywhere someone who does this successfully with the exception of 1 GitHub project, which creates copies of all files in a Music playlist to a new chosen location and then creates a playlist from that new location. I can paste that script here if anyone has an idea of how to adapt it? I have asked ChatGPT to write something from scratch but to no avail as its mimicry doesn’t resolve the errors generated. Does anyone know a good Applescript resource for learning how to write the script if none of the above work? Cheers in advance!
The script here doesn’t export playlists to other location of hard disk. It only creates playlists info report in text form on the desktop.
I can help you with that.
Question is do you want to export the tracks as well into the folders?
That will be pretty inefficient as there will be multiple copies of tracks in different folders.
Do you want the exported playlists in iTunes compatible XML format?
Just FYI when you reimport those :
- you can only import one playlist at a time
- you’ll lose your “Folder Striucture”
you’ll need a seperate script to handle they above issues. I have objC function that
Hey @technomorph - thanks for coming back, I do not want the script to export any actual tracks, just the .m3u format playlists from within macOS Music (not iTunes). If it’s possible to export playlists into the same folder structure as they are organised within Music that would be awesome but is not essential. I have a work around for reimporting them back into Music in the same folder structure as part of any new Library so that’s not a problem. Can upload the 2 scripts I have so far if that helps? Cheers!
Here’s a script that’s really slow. It’s doing a bit more than your asking… and currently it’s just creating a “Master Tree”… From that you could then work thru it and create Folders in Finder / or Export M3U playlist files as needed. I’d add functions to add / calculate a finder path based on the “Root Export Path”. The M3U Playlist Create function can easily be made: of course change any iTunes references to Music
Notice the properties:
property trimTotal : true
property trimCount : 40
property includeSmarts : false
My iTunes has over 7000 playlists so I’ve included the ability to trim that down for testing to the trimCount. When trimTotal is true:
Also includeSmarts to include exclude smartPlaylists
use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions
-- classes, constants, and enums used
property NSDictionary : a reference to current application's NSDictionary
property NSMutableDictionary : a reference to current application's NSMutableDictionary
property NSMutableArray : a reference to current application's NSMutableArray
property NSPredicate : a reference to current application's NSPredicate
property NSArray : a reference to current application's NSArray
property everyUserPlaylist : {}
property finalTree : missing value
property allUserPlaylists : missing value
property rootPlaylists : missing value
property rootPlaylistsNames : missing value
property aPlaylist : missing value
property aPlaylistProps : missing value
property aPlaylistDict : missing value
property aTrackDict : missing value
property currentIndex : 0
property totalCount : 0
property progressTotalCount : 0
property progressDivider : 25
property progressUpdateInterval : 0
property progressCompleted : 0
property progressMainDesc : ""
property progressSubDesc : ""
property progressAtRoot : true
property progressMainUpdate : false
property progressTracksUpdate : false
property progressTracksCount : 0
property progressTracksCompleted : 0
property progressTracksDesc : ""
property progressTracksSubDesc : ""
property trimTotal : true
property trimCount : 40
property includeSmarts : false
property saveFilePath : missing value
property exportFolderPath : missing value
property logProgress : true
property logExtras : false
my setUpDefaults()
my selectExportFolder()
my selectSaveFile()
my loadUserPlaylists()
my parseRootPlaylists()
my createTreeFromRootPlaylists()
my saveMasterTree()
on selectExportFolder()
set exportFolderPath to choose folder with prompt ¬
"Select or Create Root Export Folder" default location path to music folder ¬
log {"exportFolderPath is:", exportFolderPath}
log {"class of exportFolderPath is:", class of exportFolderPath}
end selectExportFolder
on selectSaveFile()
set saveFilePath to choose file name with prompt ¬
"Select Name for Master Tree File" default name ¬
"ITunes Tree Master.XML" default location exportFolderPath
log {"saveFilePath is:", saveFilePath}
log {"class of saveFilePath is:", class of saveFilePath}
end selectSaveFile
on saveMasterTree()
if (logProgress) then log {"START saveMasterTree"}
set savedMaster to (finalTree's writeToURL:saveFilePath atomically:true)
log {"savedMaster is:", savedMaster}
end saveMasterTree
on setUpDefaults()
set allUserPlaylists to NSMutableArray's new()
set finalTree to NSMutableArray's new()
end setUpDefaults
on loadUserPlaylists()
if (logProgress) then log {"START loadUserPlaylists"}
tell application id "com.apple.iTunes"
--set everyUserPlaylist to get (every playlist whose special kind is none or special kind is folder)
if (includeSmarts) then
set everyUserPlaylist to (every playlist whose special kind is none or special kind is folder)
else
set everyUserPlaylist to (every playlist whose (special kind is none and smart is false) or special kind is folder)
end if
end tell
set totalCount to count of everyUserPlaylist
if (trimTotal) then
set totalCount to trimCount
log {"setting to trimCount is:", trimCount}
end if
my setUpProgressWithCount:totalCount progressDescription:"Creating Playlist Dictionaries for All User Playlists" subDescription:"Parsing Playlist"
repeat with currentIndex from 1 to totalCount
set aPlaylist to item currentIndex of everyUserPlaylist
if (aPlaylist's name ≠ "Home Videos") then
set aPlaylistDict to (my getBasePropertiesForPlaylist:aPlaylist)
(allUserPlaylists's addObject:aPlaylistDict)
end if
(my updateProgressWithCompletedSteps:currentIndex)
end repeat
my updateProgressCompletedMessage:"Completed Creating All Playlist Base Dictionaries" completedSubMessage:"Next Filter And Parse Root Playlists"
end loadUserPlaylists
on parseRootPlaylists()
if (logProgress) then log {"START parseRootPlaylists"}
set aPred to NSPredicate's predicateWithFormat:"parentName = 'ROOT'"
set rootPlaylists to allUserPlaylists's filteredArrayUsingPredicate:aPred
set rootPlaylistsNames to rootPlaylists's valueForKeyPath:"playlistName"
my resetProgress()
end parseRootPlaylists
on createTreeFromRootPlaylists()
if (logProgress) then log {"START createTreeFromRootPlaylists rootPlaylists count is:", count of rootPlaylists}
set finalTree to NSMutableArray's new()
repeat with aRootDict in rootPlaylists
set progressAtRoot to true
set isFolder to (aRootDict's valueForKey:"isFolder") as boolean
if (isFolder) then
set currentChildren to (my getPlaylistFolderChildren:aRootDict)
set currentChildrenCount to count of currentChildren
(aRootDict's setValue:(currentChildrenCount) forKey:"playlistCount")
(aRootDict's setValue:currentChildren forKey:"children")
else
set aPlaylistTracks to (my getPlaylistTracks:aRootDict)
set aPlaylistTracksCount to count of aPlaylistTracks
(aRootDict's setValue:(aPlaylistTracksCount) forKey:"playlistCount")
(aRootDict's setValue:aPlaylistTracks forKey:"playlistTracks")
end if
(finalTree's addObject:aRootDict)
end repeat
end createTreeFromRootPlaylists
on getPlaylistFolderChildren:aFolderDict
if (logProgress) then log {"START getPlaylistFolderChildren"}
set aFoldersChildren to NSMutableArray's new()
set aFolderID to (aFolderDict's valueForKey:"playlistPersistentID")
set aFolderName to (aFolderDict's valueForKey:"playlistName")
-- GET ALL THE PLAYLISTS who's parentPersistentID matches the currentFolder
--set aPred to NSPredicate's predicateWithFormat_(formatString, aValue, ...)
set parentPred to NSPredicate's predicateWithFormat_("parentPersistentID = %@", aFolderID)
set folderChildDicts to allUserPlaylists's filteredArrayUsingPredicate:parentPred
set folderChildCount to count of folderChildDicts
set aDesc to "Processing Folder:" & aFolderName & "'s Children"
set aSubDesc to "Processing ChildDict"
if (progressAtRoot) then
my setUpProgressWithCount:folderChildCount progressDescription:aDesc subDescription:aSubDesc
else
set aDesc to "Processing Sub Folder:" & aFolderName & "'s Children"
my addToProgressSubCount:folderChildCount progressAdditionalDescription:aDesc
end if
repeat with currentIndex from 1 to folderChildCount
set aChildDict to item currentIndex of folderChildDicts
set isFolder to (aChildDict's valueForKey:"isFolder") as boolean
if (isFolder) then
set progressAtRoot to false
set currentChildren to (my getPlaylistFolderChildren:aChildDict)
set currentChildrenCount to count of currentChildren
(aChildDict's setValue:(currentChildrenCount) forKey:"playlistCount")
(aChildDict's setValue:currentChildren forKey:"children")
else
set aPlaylistTracks to (my getPlaylistTracks:aChildDict)
set aPlaylistTracksCount to count of aPlaylistTracks
(aChildDict's setValue:(aPlaylistTracksCount) forKey:"playlistCount")
(aChildDict's setValue:aPlaylistTracks forKey:"playlistTracks")
end if
(aFoldersChildren's addObject:aChildDict)
(my updateProgressWithCompletedSteps:currentIndex)
end repeat
set aCompletedDescription to "FINISHED " & aDesc
set aSubMessage to "Parsed " & folderChildCount & " Total Children"
my updateProgressCompletedMessage:aCompletedDescription completedSubMessage:aSubMessage
return aFoldersChildren
end getPlaylistFolderChildren:
on getPlaylistTracks:aPlaylistDict
set playlistTracksDicts to NSMutableArray's new()
set aPlaylistID to (aPlaylistDict's valueForKey:"playlistID")
set aPlaylistName to (aPlaylistDict's valueForKey:"playlistName")
set aPlaylist to my findPlaylistWithID:aPlaylistID
if (aPlaylist ≠ missing value) then
tell application id "com.apple.iTunes"
set aPlaylistTracks to aPlaylist's tracks
set aTracksCount to count of aPlaylistTracks
set aDescription to "Fetching Tracks for Playlist:" & aPlaylistName
my setUpProgressTracksWithCount:aTracksCount progressTracksDescription:aDescription tracksSubDescription:"Parsing Playlist Track"
repeat with aTrackIndex from 1 to aTracksCount
set aTrackDict to NSMutableDictionary's new()
set aTrack to item aTrackIndex of aPlaylistTracks
set aPersistentID to aTrack's persistent ID
set aName to aTrack's name
set aArtist to aTrack's artist
set aAlbum to aTrack's album
set aLocation to aTrack's location
set aLocationPath to POSIX path of aLocation
(aTrackDict's setValue:(aTrackIndex) forKey:"playlistIndex")
(aTrackDict's setValue:aPersistentID forKey:"trackPersistentID")
(aTrackDict's setValue:aName forKey:"trackName")
(aTrackDict's setValue:aArtist forKey:"trackArtist")
(aTrackDict's setValue:aAlbum forKey:"trackAlbum")
-- SAVING NSArray or NSDictionary with URLs fails
-- (aTrackDict's setValue:aLocation forKey:"trackLocation")
(aTrackDict's setValue:aLocationPath forKey:"trackLocationPath")
(playlistTracksDicts's addObject:aTrackDict)
(my updateProgressTracksWithCompletedSteps:aTrackIndex)
end repeat
set aCompletedDescription to "FINISHED " & aDescription
set aSubMessage to "Parsed " & aTracksCount & " Total Tracks"
my updateProgressTracksCompletedMessage:aCompletedDescription completedSubMessage:aSubMessage
end tell
end if
return playlistTracksDicts
end getPlaylistTracks:
on getBasePropertiesForPlaylist:aPlaylist
set aPlaylistProps to NSMutableDictionary's new()
tell application id "com.apple.iTunes"
set aName to aPlaylist's name
set aPersistentID to aPlaylist's persistent ID
set aPlaylistID to aPlaylist's id
set isSmart to aPlaylist's smart
set aSpecialKind to aPlaylist's special kind
set aNodeType to "playlist"
set isFolder to false
if (aSpecialKind is folder) then
set aNodeType to "folder"
set isFolder to true
set isSmart to false
end if
set aParent to missing value
set aParentName to ""
set aParentPersistentID to ""
try
set aParent to aPlaylist's parent
end try
if (aParent is missing value) then
set aParentName to "ROOT"
else
set aParentName to aParent's name
set aParentPersistentID to aParent's persistent ID
end if
end tell
(aPlaylistProps's setValue:aName forKey:"playlistName")
(aPlaylistProps's setValue:aPersistentID forKey:"playlistPersistentID")
(aPlaylistProps's setValue:(aPlaylistID) forKey:"playlistID")
(aPlaylistProps's setValue:(isSmart) forKey:"isSmart")
(aPlaylistProps's setValue:aNodeType forKey:"nodeType")
(aPlaylistProps's setValue:(isFolder) forKey:"isFolder")
(aPlaylistProps's setValue:aParentName forKey:"parentName")
(aPlaylistProps's setValue:aParentPersistentID forKey:"parentPersistentID")
--log {"aPlaylistProps is", aPlaylistProps}
return aPlaylistProps
end getBasePropertiesForPlaylist:
on findPlaylistWithID:aID
set aPlaylistID to aID as integer
set aPlaylist to missing value
tell application id "com.apple.iTunes"
set aFoundPlaylist to missing value
set playlistExsists to exists (a reference to playlist id aPlaylistID)
if playlistExsists then
try
set aFoundPlaylist to playlist id aPlaylistID
on error theErr number theNum
log {"Can't find aFoundPlaylist " & theErr & " " & theNum & " " & aPlaylistID}
end try
end if
if (aFoundPlaylist ≠ missing value) then
set aPlaylist to get aFoundPlaylist
end if
end tell
return aPlaylist
end findPlaylistWithID:
on findOrCreateFolderWithName:aFolderName
tell application id "com.apple.iTunes"
set aFolder to missing value
set aFolderName to aFolderName as text
set folderExists to exists (a reference to folder playlist aFolderName)
if logCreate then
log {"folderExists is:", folderExists}
end if
if folderExists then
try
set aFolder to folder playlist aFolderName
on error theErr number theNum
log {"Can't find aFolder " & theErr & " " & theNum & " " & aFolderName}
end try
else
set aFolder to (make new folder playlist with properties {name:aFolderName})
end if
end tell
return aFolder
end findOrCreateFolderWithName:
on movePlaylistNamed:aPlaylistName toFolderNamed:aFolderName
set aPlaylist to (my findOrCreatePlaylistWithName:aPlaylistName)
set aFolder to (my findOrCreateFolderWithName:aFolderName)
set moveOK to (my moveITunesItem:aPlaylist toFolder:aFolder)
return moveOK
end movePlaylistNamed:toFolderNamed:
on moveFolderNamed:aFolderName toParentNamed:aParentName
set aFolder to (my findOrCreateFolderWithName:aFolderName)
set aParentFolder to (my findOrCreateFolderWithName:aParentName)
set moveOK to (my moveITunesItem:aFolder toFolder:aParentFolder)
return moveOK
end moveFolderNamed:toParentNamed:
on movePlaylist:aPlaylist toFolderNamed:aFolderName
set aFolder to (my findOrCreateFolderWithName:aFolderName)
set moveOK to (my moveITunesItem:aPlaylist toFolder:aFolder)
return moveOK
end movePlaylist:toFolderNamed:
on moveITunesItem:aItem toFolder:aFolder
set moveOK to true
set shouldMove to (my shouldMoveITunesItem:aItem toParent:aFolder)
if shouldMove then
tell application id "com.apple.iTunes"
try
move aItem to aFolder
on error theErr number theNum
log {"Can't move aItem to aFolder " & theErr & " " & theNum}
set moveOK to false
end try
end tell
end if
set moveOK to moveOK as boolean
return moveOK
end moveITunesItem:toFolder:
on shouldMoveITunesItem:aItem toParent:aFolder
set moveIt to true
tell application id "com.apple.iTunes"
if exists (aItem's parent) then
try
if (aItem's parent is aFolder) then
set moveIt to false
end if
end try
end if
end tell
return moveIt
end shouldMoveITunesItem:toParent:
on setUpProgressWithCount:aCount progressDescription:aDescription subDescription:subDescription
if (logProgress) then log {"START setUpProgress"}
-- Update the initial progress information
set progressMainUpdate to true
set progressTotalCount to aCount
set progressUpdateInterval to (progressTotalCount div progressDivider)
if (progressUpdateInterval = 0) then
set progressUpdateInterval to 1
end if
if (logProgress) then log {"progressUpdateInterval is:", progressUpdateInterval, " for progressTotalCount:", progressTotalCount}
set progressCompleted to 0
set progressMainDesc to aDescription
set progressSubDesc to subDescription
set progress total steps to progressTotalCount
set progress completed steps to progressCompleted
set progress description to progressMainDesc
set progress additional description to "Preparing to process."
end setUpProgressWithCount:progressDescription:subDescription:
on addToProgressSubCount:aAddCount progressAdditionalDescription:addSubDescription
if (logProgress) then log {"START addToProgressSubCount aAddCount is:", aAddCount}
-- Update the initial progress information
set progressTotalCount to progressTotalCount + aAddCount
set progressUpdateInterval to (progressTotalCount div progressDivider)
if (progressUpdateInterval = 0) then
set progressUpdateInterval to 1
end if
if (logProgress) then log {"progressUpdateInterval is:", progressUpdateInterval, " for progressTotalCount:", progressTotalCount}
set progressSubDesc to addSubDescription
set progress total steps to progressTotalCount
--set progress completed steps to 0
--set progress description to aDescription
set progress additional description to "Adding " & aAddCount & " sub tasks"
end addToProgressSubCount:progressAdditionalDescription:
on updateProgressWithCompletedSteps:completedSteps
-- Update the progress completed steps
set progressCompleted to completedSteps
set progress completed steps to progressCompleted
-- Update the progress detail if past progressUpdateInterval
if (progressCompleted mod progressUpdateInterval) = 0 then
set progress additional description to progressSubDesc & " " & progressCompleted & " of " & progressTotalCount
end if
end updateProgressWithCompletedSteps:
on updateProgressCompletedMessage:completedDescription completedSubMessage:subMessage
if (logProgress) then
log {"START updateProgressCompletedMessage"}
--my debugLogProgressMainInfo()
end if
-- Update the progress description completed
set progress description to completedDescription
set progress additional description to subMessage
if (progressCompleted ≥ progressTotalCount) then
my resetProgress()
end if
end updateProgressCompletedMessage:completedSubMessage:
on resetProgress()
-- Reset the progress information
set progress total steps to 0
set progress completed steps to 0
set progress description to ""
set progress additional description to ""
(*
set progressTotalCount to 0
set progressCompleted to 0
set progressMainDesc to ""
set progressSubDesc to ""
*)
end resetProgress
on prepareSwitchProgressMainMode()
set shouldBackUp to (progressTracksCount > 0 and progressTracksCompleted < progressTracksCount) as boolean
if (logProgress) then log {"shouldBackUp is:", shouldBackUp}
if (progressMainUpdate or shouldBackUp) then
my backUpProgressTracksInfo()
end if
my restoreProgressMainInfo()
end prepareSwitchProgressMainMode
on backUpProgressMainInfo()
set progressTotalCount to progress total steps
set progressCompleted to progress completed steps
set progressMainDesc to progress description
--set progressSubDesc to progress additional description
if (logProgress) then my debugLogProgressMainInfo()
end backUpProgressMainInfo
on restoreProgressMainInfo()
set progressMainUpdate to true
set progressTracksUpdate to false
set progress total steps to progressTotalCount
set progress completed steps to progressCompleted
set progress description to progressMainDesc
set progress additional description to progressSubDesc
end restoreProgressMainInfo
on debugLogProgressMainInfo()
log {"progressTracksUpdate is:", progressTracksUpdate}
log {"progressAtRoot is:", progressAtRoot}
log {"progressMainUpdate is:", progressMainUpdate}
log {"progressMainDesc is:", progressMainDesc}
log {"progressSubDesc is:", progressSubDesc}
log {"progressTotalCount is:", progressTotalCount}
log {"progressCompleted is:", progressCompleted}
log {"progressUpdateInterval is:", progressUpdateInterval}
end debugLogProgressMainInfo
on prepareSwitchProgressTracksMode()
if (logProgress) then log {"START prepareSwitchProgressTracksMode"}
if (progressMainUpdate or not progressTracksUpdate) then
my backUpProgressMainInfo()
end if
set progressMainUpdate to false
set progressTracksUpdate to true
end prepareSwitchProgressTracksMode
on setUpProgressTracksWithCount:aCount progressTracksDescription:aDescription tracksSubDescription:subDescription
if (logProgress) then log {"START setUpProgressTracksWithCount"}
my prepareSwitchProgressTracksMode()
-- Update the initial progress information
set progressTracksCount to aCount
set progressTracksCompleted to 0
set progressTracksDesc to aDescription
set progressTracksSubDesc to subDescription
set progress total steps to progressTracksCount
set progress completed steps to progressTracksCompleted
set progress description to progressTracksDesc
set progress additional description to "Preparing to process Tracks"
end setUpProgressTracksWithCount:progressTracksDescription:tracksSubDescription:
on updateProgressTracksWithCompletedSteps:completedSteps
-- Update the progress completed steps
set progressTracksCompleted to completedSteps
set progress completed steps to progressTracksCompleted
-- Update the progress detail if past progressUpdateInterval
set progress additional description to progressTracksSubDesc & " " & progressTracksCompleted & " of " & progressTracksCount
end updateProgressTracksWithCompletedSteps:
on updateProgressTracksCompletedMessage:completedDescription completedSubMessage:subMessage
-- Update the progress description completed
set progress description to completedDescription
set progress additional description to subMessage
my resetProgressTracks()
my prepareSwitchProgressMainMode()
end updateProgressTracksCompletedMessage:completedSubMessage:
on resetProgressTracks()
-- Reset the progress information
set progressTracksCount to 0
set progressTracksCompleted to 0
set progressTracksDesc to ""
set progressTracksSubDesc to ""
(*
set progress total steps to 0
set progress completed steps to 0
set progress description to ""
set progress additional description to ""
*)
end resetProgressTracks
on backUpProgressTracksInfo()
set progressTracksCount to progress total steps
set progressTracksCompleted to progress completed steps
set progressTracksDesc to progress description
set progressTracksSubDesc to progress additional description
if (logProgress) then my debugLogProgressTracksInfo()
end backUpProgressTracksInfo
on restoreProgressTracksInfo()
set progressTracksUpdate to true
set progressMainUpdate to false
set progress total steps to progressTracksCount
set progress completed steps to progressTracksCompleted
set progress description to progressTracksDesc
set progress additional description to progressTracksSubDesc
end restoreProgressTracksInfo
on debugLogProgressTracksInfo()
log {"progressAtRoot is:", progressAtRoot}
log {"progressMainUpdate is:", progressMainUpdate}
log {"progressTracksUpdate is:", progressTracksUpdate}
log {"progressTracksDesc is:", progressTracksDesc}
log {"progressTracksSubDesc is:", progressTracksSubDesc}
log {"progressTracksCount is:", progressTracksCount}
log {"progressTracksCompleted is:", progressTracksCompleted}
end debugLogProgressTracksInfo
Here’s a reworked script… only get’s playlists not folders.
(There’s probably a lot of junk properties I didn’t clean out)
Create’s m3UString
Exports and save’s m3UString to location with playlistName
Also added exporting the playlistDict as XML into same location.
converting the m3UString to array of FilePaths.
Can be used and helpful for reimporting as it has the parentNames
use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions
-- classes, constants, and enums used
property |NSURL| : a reference to current application's |NSURL|
property NSUTF8StringEncoding : a reference to 4
property NSFileManager : a reference to current application's NSFileManager
property NSCaseInsensitiveSearch : a reference to 1
property NSRegularExpressionSearch : a reference to 1024
property NSCharacterSet : a reference to current application's NSCharacterSet
property NSString : a reference to current application's NSString
property NSDictionary : a reference to current application's NSDictionary
property NSMutableDictionary : a reference to current application's NSMutableDictionary
property NSMutableArray : a reference to current application's NSMutableArray
property NSPredicate : a reference to current application's NSPredicate
property NSArray : a reference to current application's NSArray
property fileManager : missing value
property everyUserPlaylist : {}
property finalTree : missing value
property allUserPlaylists : missing value
property allExportDicts : missing value
property allExportDictsCount : 0
property rootPlaylists : missing value
property rootPlaylistsNames : missing value
property aPlaylist : missing value
property aPlaylistProps : missing value
property aPlaylistDict : missing value
property aTrackDict : missing value
property currentIndex : 0
property totalCount : 0
property progressTotalCount : 0
property progressDivider : 25
property progressUpdateInterval : 0
property progressCompleted : 0
property progressMainDesc : ""
property progressSubDesc : ""
property progressAtRoot : true
property progressMainUpdate : false
property progressTracksUpdate : false
property progressTracksCount : 0
property progressTracksCompleted : 0
property progressTracksDesc : ""
property progressTracksSubDesc : ""
property trimTotal : true
property trimCount : 40
property includeSmarts : false
property saveFilePath : missing value
property exportFolderURL : missing value
property exportFolderPath : ""
property logProgress : true
property logExtras : false
my setUpDefaults()
my selectExportFolder()
--my openExportPlaylists()
my selectSaveFile()
my loadUserPlaylists()
my parseExportPlaylists()
my saveExportPlaylists()
my createAndExportPlaylists()
--my parseRootPlaylists()
--my createTreeFromRootPlaylists()
--my saveMasterTree()
on selectExportFolder()
set exportFolderURL to choose folder with prompt ¬
"Select or Create Root Export Folder" default location path to music folder ¬
set exportFolderPosixPath to (POSIX path of exportFolderURL)
set exportFolderPath to NSString's stringWithString:exportFolderPosixPath
end selectExportFolder
on selectSaveFile()
set saveFilePath to choose file name with prompt ¬
"Select Name for Master Tree File" default name ¬
"ITunes Tree Master.XML" default location exportFolderURL
log {"saveFilePath is:", saveFilePath}
log {"class of saveFilePath is:", class of saveFilePath}
end selectSaveFile
on saveExportPlaylists()
if (logProgress) then log {"START saveExportPlaylists"}
set savedMaster to (allExportDicts's writeToURL:saveFilePath atomically:true)
log {"savedMaster is:", savedMaster}
end saveExportPlaylists
on openExportPlaylists()
set aOpenFile to choose file with prompt ¬
"Select Saved Export Playlists File" of type ("XML") ¬
default location exportFolderURL ¬
invisibles false ¬
multiple selections allowed false ¬
without showing package contents
set aPath to POSIX path of aOpenFile
set aPathSting to NSString's stringWithString:aPath
set aPathURL to |NSURL|'s fileURLWithPath:aPathSting
set {allExportDicts, theError} to NSMutableArray's arrayWithContentsOfURL:aPathURL |error|:(reference)
if (theError is not missing value) then
log {"Can't openFileAtURL " & aPathURL & " " & theError & " " & theError's localizedDescription()}
error (theError's localizedDescription() as text)
end if
set allExportDictsCount to count of allExportDicts
log {"allExportDictsCount is:", allExportDictsCount}
end openExportPlaylists
on setUpDefaults()
set fileManager to NSFileManager's defaultManager()
set allUserPlaylists to NSMutableArray's new()
set allExportDicts to NSMutableArray's new()
end setUpDefaults
on loadUserPlaylists()
if (logProgress) then log {"START loadUserPlaylists"}
tell application id "com.apple.iTunes"
--set everyUserPlaylist to get (every playlist whose special kind is none or special kind is folder)
if (includeSmarts) then
set everyUserPlaylist to (every playlist whose special kind is none)
else
set everyUserPlaylist to (every playlist whose (special kind is none and smart is false))
end if
end tell
set totalCount to count of everyUserPlaylist
if (trimTotal) then
set totalCount to trimCount
log {"setting to trimCount is:", trimCount}
end if
my setUpProgressWithCount:totalCount progressDescription:"Creating Playlist Dictionaries for All User Playlists" subDescription:"Parsing Playlist"
repeat with currentIndex from 1 to totalCount
set aPlaylist to item currentIndex of everyUserPlaylist
if (aPlaylist's name ≠ "Home Videos") then
set aPlaylistDict to (my getBasePropertiesForPlaylist:aPlaylist)
(allUserPlaylists's addObject:aPlaylistDict)
end if
(my updateProgressWithCompletedSteps:currentIndex)
end repeat
my updateProgressCompletedMessage:"Completed Creating All Playlist Base Dictionaries" completedSubMessage:"Next Filter And Parse Root Playlists"
end loadUserPlaylists
on parseExportPlaylists()
if (logProgress) then log {"START parseExportPlaylists"}
set aPred to NSPredicate's predicateWithFormat:"isFolder == 0"
set allExportDicts to allUserPlaylists's filteredArrayUsingPredicate:aPred
set allExportDictsCount to count of allExportDicts
my resetProgress()
end parseExportPlaylists
on createAndExportPlaylists()
if (logProgress) then log {"START createAndExportPlaylists allExportDictsCount:", allExportDictsCount}
set aDesc to "Processing Export Playlists"
set aSubDesc to "Exporting Playlist"
my setUpProgressWithCount:allExportDictsCount progressDescription:aDesc subDescription:aSubDesc
repeat with currentIndex from 1 to allExportDictsCount
set aExportDict to item currentIndex of allExportDicts
set aM3UString to (aExportDict's valueForKey:"playlistM3UString")
set aFileName to (aExportDict's valueForKey:"playlistFileName")
set aExportPath to (aExportDict's valueForKey:"playlistExportPath")
set aParentPath to (aExportDict's valueForKey:"parentExportPath")
set createdFolders to (my createFoldersAtPath:aParentPath)
set aExportString to (NSString's stringWithString:aM3UString)
set {exportedM3U, theError} to (aExportString's writeToFile:aExportPath atomically:true encoding:NSUTF8StringEncoding |error|:(reference))
if exportedM3U as boolean is false then error (theError's localizedDescription() as text)
set aExportXMLPath to ((aExportPath's stringByDeletingPathExtension())'s stringByAppendingPathExtension:"XML")
set exportFilePaths to (aExportString's componentsSeparatedByString:"
")
(aExportDict's removeObjectForKey:"playlistM3UString")
(aExportDict's setValue:exportFilePaths forKey:"tracksFilePaths")
set exportedXML to (aExportDict's writeToFile:aExportXMLPath atomically:true)
--log {"exportedXML is:", exportedXML}
set aSubDesc to "Exported File:" & aFileName & " to Folder Path:" & aParentPath
set progress completed steps to currentIndex
set progress description to "Exported " & currentIndex & " of " & allExportDictsCount
set progress additional description to aSubDesc
end repeat
if (logProgress) then log {"END createAndExportPlaylists allExportDictsCount:", allExportDictsCount}
end createAndExportPlaylists
on createFoldersAtPath:aParentPath
set results to true
set exsists to fileManager's fileExistsAtPath:aParentPath
if (not exsists) then
set {results, theError} to fileManager's createDirectoryAtPath:aParentPath withIntermediateDirectories:true attributes:(missing value) |error|:(reference)
if (results as boolean is false) then
log {"Can't createDirectoryAtPath " & aParentPath & " " & theError's localizedDescription() as text}
error (theError's localizedDescription() as text)
end if
end if
return results
end createFoldersAtPath:
on getBasePropertiesForPlaylist:aPlaylist
set aPlaylistProps to NSMutableDictionary's new()
tell application id "com.apple.iTunes"
set aName to aPlaylist's name
set aPersistentID to aPlaylist's persistent ID
--set aPlaylistID to aPlaylist's id
set aSpecialKind to aPlaylist's special kind
set isFolder to false
if (aSpecialKind is folder) then set isFolder to true
end tell
(aPlaylistProps's setValue:aName forKey:"playlistName")
(aPlaylistProps's setValue:aPersistentID forKey:"playlistPersistentID")
(aPlaylistProps's setValue:(isFolder) forKey:"isFolder")
set aParentDict to my getParentPropertiesForPlaylist:aPlaylist
(aPlaylistProps's setValuesForKeysWithDictionary:aParentDict)
set aParentFolderPath to (aPlaylistProps's valueForKey:"parentFolderPath")
set aPlaylistFileName to NSString's stringWithString:aName
if (not isFolder) then
set aM3UString to my createM3UForPlaylist:aPlaylist
set aPlaylistFileName to (aPlaylistFileName's stringByAppendingPathExtension:"m3u")
(aPlaylistProps's setValue:aM3UString forKey:"playlistM3UString")
end if
set aParentExportPath to exportFolderPath
--set aPlaylistFilePath to aPlaylistFileName
if (aParentFolderPath is not "") then
set aParentExportPath to (exportFolderPath's stringByAppendingPathComponent:aParentFolderPath)
--set aPlaylistFilePath to (aParentFolderPath's stringByAppendingPathComponent:aPlaylistFileName)
end if
set aPlaylistExportPath to (aParentExportPath's stringByAppendingPathComponent:aPlaylistFileName)
(aPlaylistProps's setValue:aPlaylistFileName forKey:"playlistFileName")
--(aPlaylistProps's setValue:aPlaylistFilePath forKey:"playlistFilePath")
(aPlaylistProps's setValue:aPlaylistExportPath forKey:"playlistExportPath")
(aPlaylistProps's setValue:aParentExportPath forKey:"parentExportPath")
return aPlaylistProps
end getBasePropertiesForPlaylist:
on getParentPropertiesForPlaylist:aPlaylist
set aParentDict to NSMutableDictionary's new()
set parentFolderPath to ""
tell application id "com.apple.iTunes"
set aParentName to "ROOT"
set aParentPersistentID to ""
set parentNames to NSMutableArray's new()
set aParent to missing value
set aGrandParent to missing value
try
set aParent to aPlaylist's parent
end try
if (aParent is not missing value) then
set aParentName to aParent's name
set aParentPersistentID to aParent's persistent ID
(parentNames's addObject:aParentName)
try
set aGrandParent to aParent's parent
end try
end if
repeat while (aGrandParent is not missing value)
set aGrandParentName to aGrandParent's name
(parentNames's insertObject:aGrandParentName atIndex:0)
try
set aGrandParent to aGrandParent's parent
on error
set aGrandParent to missing value
end try
end repeat
end tell
set parentNamesCount to count of parentNames
if (parentNamesCount > 0) then
set parentFolderPath to (parentNames's componentsJoinedByString:"/")
end if
(aParentDict's setValue:aParentName forKey:"parentName")
(aParentDict's setValue:aParentPersistentID forKey:"parentPersistentID")
(aParentDict's setValue:parentNames forKey:"parentNames")
(aParentDict's setValue:(parentNamesCount) forKey:"parentNamesCount")
(aParentDict's setValue:parentFolderPath forKey:"parentFolderPath")
return aParentDict
end getParentPropertiesForPlaylist:
on createM3UForArrayOfFilePaths:tracksFilePaths
set aM3UString to NSString's stringWithString:""
if ((count of tracksFilePaths) > 0) then
set aM3UString to (tracksFilePaths's componentsJoinedByString:"
")
end if
return aM3UString
end createM3UForArrayOfFilePaths:
on createM3UForPlaylist:aPlaylist
set tracksFilePaths to NSMutableArray's new()
tell application id "com.apple.iTunes"
set aPlaylistTracks to aPlaylist's tracks
set aTracksCount to count of aPlaylistTracks
repeat with aTrackIndex from 1 to aTracksCount
set aTrack to item aTrackIndex of aPlaylistTracks
set aLocation to missing value
try
set aLocation to aTrack's location
end try
if (aLocation is not missing value) then
set aLocationPath to POSIX path of aLocation
(tracksFilePaths's addObject:aLocationPath)
end if
end repeat
end tell
set aM3UString to my createM3UForArrayOfFilePaths:tracksFilePaths
return aM3UString
end createM3UForPlaylist:
on setUpProgressWithCount:aCount progressDescription:aDescription subDescription:subDescription
if (logProgress) then log {"START setUpProgress"}
-- Update the initial progress information
set progressMainUpdate to true
set progressTotalCount to aCount
set progressUpdateInterval to (progressTotalCount div progressDivider)
if (progressUpdateInterval = 0) then
set progressUpdateInterval to 1
end if
if (logProgress) then log {"progressUpdateInterval is:", progressUpdateInterval, " for progressTotalCount:", progressTotalCount}
set progressCompleted to 0
set progressMainDesc to aDescription
set progressSubDesc to subDescription
set progress total steps to progressTotalCount
set progress completed steps to progressCompleted
set progress description to progressMainDesc
set progress additional description to "Preparing to process."
end setUpProgressWithCount:progressDescription:subDescription:
on updateProgressWithCompletedSteps:completedSteps
-- Update the progress completed steps
set progressCompleted to completedSteps
set progress completed steps to progressCompleted
-- Update the progress detail if past progressUpdateInterval
if (progressCompleted mod progressUpdateInterval) = 0 then
set progress additional description to progressSubDesc & " " & progressCompleted & " of " & progressTotalCount
end if
end updateProgressWithCompletedSteps:
on updateProgressCompletedMessage:completedDescription completedSubMessage:subMessage
if (logProgress) then log {"START updateProgressCompletedMessage"}
-- Update the progress description completed
set progress description to completedDescription
set progress additional description to subMessage
if (progressCompleted ≥ progressTotalCount) then
my resetProgress()
end if
end updateProgressCompletedMessage:completedSubMessage:
on resetProgress()
-- Reset the progress information
set progress total steps to 0
set progress completed steps to 0
set progress description to ""
set progress additional description to ""
(*
set progressTotalCount to 0
set progressCompleted to 0
set progressMainDesc to ""
set progressSubDesc to ""
*)
end resetProgress
Dude, only just saw your last 2 posts, can’t thank you enough for sharing, don’t know why I never received notification, anyway testing out now, thank you!
Deployed the second script to ScriptEditor, changed iTunes to Music (I am using Music not iTunes) and executed - gave me error “Music got an error: Can’t get name of user playlist id 68 of source id 64.” number -1728 from name of user playlist id 68 of source id 64" " Not sure why?!
Could be a permissions issue. Have you granted script editor accessibly access?
I don’t think there a change in accessing the name. Where in the script do it error? On the getPlaylistDict function?
Also may need to change the filtering of the get playlists. In base iTunes there is only PlaylistKind. In iTunes with Bridging there is a
Kind property and a specialKind Property.
Based on the low id number of the playlist I’m guessing that is one of of the Library playlists, like Music Library Playlist. Which you probably don’t want. When I get home I’ll try to give you a better filter that may work.
Filtering on kind and specialKind.
Can you view the Music scripting def Dictionary in script editor?
Have a look a Playlist Class and it’s prioerties.
Also look at the UserPlaylist class as that what you really want