Apple Music: Export playlist?

Have a look at this topic:

https://macscripter.net/viewtopic.php?id=47414

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 :slight_smile:

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

https://dougscripts.com/itunes/2019/12/updated-export-playlist-as-text-v4-0/

https://dougscripts.com/itunes/2019/12/updated-make-a-text-list-v6-0/

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! :smiley:

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! :slight_smile:

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 :

  1. you can only import one playlist at a time
  2. 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
1 Like

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