Skip to content

Commit

Permalink
Added handling of playlist folders
Browse files Browse the repository at this point in the history
  • Loading branch information
ericdaugherty committed Apr 29, 2023
1 parent 47bb672 commit 2324a38
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 11 deletions.
5 changes: 4 additions & 1 deletion app.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ Flags:
FLAT Copies all the music into the output folder.
-musicPath <new path> Base path to the music files. This will override the Music Folder path from iTunes.
-musicPathOrig <path> When using -musicPath this allows you to override the Music Folder value that is replaced.
-includeFolders Playlists within folders will include the full path in the name.
`
UsageErrorMessage = `Unable to parse command line parameters.
%v
Expand Down Expand Up @@ -64,6 +65,7 @@ var (
copyType string
musicPath string
musicPathOrig string
includeFolders bool

exportSettings ExportSettings
)
Expand All @@ -84,6 +86,7 @@ func main() {
flags.StringVar(&copyType, "copy", "NONE", "")
flags.StringVar(&musicPath, "musicPath", "", "")
flags.StringVar(&musicPathOrig, "musicPathOrig", "", "")
flags.BoolVar(&includeFolders, "includeFolders", false, "")

err := flags.Parse(os.Args[1:])
if err != nil {
Expand Down Expand Up @@ -138,7 +141,7 @@ func main() {
}
libraryPath = filepath.Clean(libraryPath)

fmt.Printf("Include: %v, Exclude %v", includePlaylistNames, excludePlaylistNames)
fmt.Printf("Include: %v, Exclude %v ", includePlaylistNames, excludePlaylistNames)

fmt.Println("Loading Library:", libraryPath)
library, err := LoadLibrary(libraryPath)
Expand Down
53 changes: 43 additions & 10 deletions export.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"path/filepath"
"strings"
"time"
"regexp"
)

const (
Expand Down Expand Up @@ -44,14 +43,22 @@ func ExportPlaylists(exportSettings *ExportSettings, library *Library) error {
start := time.Now()

for _, playlist := range exportSettings.Playlists {
// Skip Folders
if playlist.Folder {
continue
}
fmt.Printf("Exporting Playlist %v\n", playlist.Name)

// Prevent illegal characters in playlist filenames
illegalChars := regexp.MustCompile(`[*?<>|]`)
safePlaylistName := illegalChars.ReplaceAllString(playlist.Name, "_")
safePlaylistName = strings.Replace(safePlaylistName, ":", " - ", -1)
filePath := ""
if includeFolders && playlist.ParentPersistentId != "" {
filePath = buildPlaylistPath(playlist, library)
}

if filePath != "" {
os.MkdirAll(filepath.Join(exportSettings.OutputPath, filePath), 0777)
}

fileName := filepath.Join(exportSettings.OutputPath, safePlaylistName+"."+exportSettings.Extension)
fileName := filepath.Join(exportSettings.OutputPath, filePath, playlist.SafeName()+"."+exportSettings.Extension)

file, err := os.OpenFile(fileName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
Expand Down Expand Up @@ -87,7 +94,7 @@ func ExportPlaylists(exportSettings *ExportSettings, library *Library) error {
sourceFileLocation, errParse := url.QueryUnescape(track.Location)
sourceFileLocation = trimTrackLocationPrefix(sourceFileLocation)

destFileLocation, err := copyTrack(exportSettings, &playlist, &track, sourceFileLocation)
destFileLocation, err := copyTrack(library, exportSettings, &playlist, &track, sourceFileLocation)
if err != nil {
fmt.Printf("Unable to copy file %v: %v\n", sourceFileLocation, err.Error())
continue
Expand Down Expand Up @@ -119,11 +126,15 @@ func ExportPlaylists(exportSettings *ExportSettings, library *Library) error {

// copyTrack copies a file from the provided sourceFileLocation to another location. The new location
// depends on the CopyType selected in exportSettings. If COPY_NONE is selected, the sourceFileLocation is returned.
func copyTrack(exportSettings *ExportSettings, playlist *Playlist, track *Track, sourceFileLocation string) (string, error) {
func copyTrack(library *Library, exportSettings *ExportSettings, playlist *Playlist, track *Track, sourceFileLocation string) (string, error) {
var destinationPath string
switch exportSettings.CopyType {
case COPY_PLAYLIST:
destinationPath = filepath.Join(exportSettings.OutputPath, playlist.Name)
filePath := ""
if includeFolders && playlist.ParentPersistentId != "" {
filePath = buildPlaylistPath(*playlist, library)
}
destinationPath = filepath.Join(exportSettings.OutputPath, filePath, playlist.SafeName())
case COPY_ITUNES:
destinationPath = filepath.Join(exportSettings.OutputPath, track.Artist, track.Album)
case COPY_FLAT:
Expand All @@ -145,13 +156,14 @@ func copyTrack(exportSettings *ExportSettings, playlist *Playlist, track *Track,
}

func copyFile(src, dest string) error {
src = strings.Replace(src, "file://", "", 1)
sourceFileInfo, err := os.Stat(src)
if err != nil {
return err
}

if !sourceFileInfo.Mode().IsRegular() {
return errors.New("source file is not a regular file.")
return errors.New("source file is not a regular file")
}

_, err = os.Stat(dest)
Expand Down Expand Up @@ -196,3 +208,24 @@ func copyFileData(src, dest string) error {
}
return out.Sync()
}

// buildPlaylistPath checks to see if the playlist has any parent folders.
// If so, it returns the full path of those folders.
func buildPlaylistPath(playlist Playlist, library *Library) string {
if playlist.ParentPersistentId == "" {
if playlist.Folder {
return playlist.SafeName()
}
return ""
}

parent, ok := library.PlaylistIdMap[playlist.ParentPersistentId]
if !ok {
return ""
}
pathSeg := ""
if playlist.Folder {
pathSeg = playlist.SafeName()
}
return filepath.Join(buildPlaylistPath(parent, library), pathSeg)
}
13 changes: 13 additions & 0 deletions library.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@ package main

import (
"os"
"regexp"
"strconv"
"time"

plist "howett.net/plist"
)

// Prevent illegal characters in playlist filenames
var illegalChars = regexp.MustCompile(`[\[\]\\:/*?<>|]`)

type Library struct {
MajorVersion int `plist:"Major Version"`
MinorVersion int `plist:"Minor Version"`
Expand All @@ -20,6 +24,7 @@ type Library struct {
Tracks map[string]Track
Playlists []Playlist
PlaylistMap map[string]Playlist
PlaylistIdMap map[string]Playlist
}

type Track struct {
Expand Down Expand Up @@ -76,14 +81,20 @@ type Playlist struct {
Master bool
PlaylistId int `plist:"Playlist ID"`
PlaylistPersistentId string `plist:"Playlist Persistent ID"`
ParentPersistentId string `plist:"Parent Persistent ID"`
DistinguishedKind int `plist:"Distinguished Kind"`
Visible bool
AllItems bool `plist:"All Items"`
Folder bool `plist:"Folder"`
SmartInfo []byte `plist:"Smart Info"`
SmartCriteria []byte `plist:"Smart Criteria"`
PlaylistItems []PlaylistItem `plist:"Playlist Items"`
}

func (p Playlist) SafeName() string {
return illegalChars.ReplaceAllString(p.Name, "_")
}

type PlaylistItem struct {
TrackId int `plist:"Track ID"`
}
Expand All @@ -107,8 +118,10 @@ func LoadLibrary(fileLocation string) (*Library, error) {
}

library.PlaylistMap = make(map[string]Playlist)
library.PlaylistIdMap = make(map[string]Playlist)
for _, value := range library.Playlists {
library.PlaylistMap[value.Name] = value
library.PlaylistIdMap[value.PlaylistPersistentId] = value
}

return &library, nil
Expand Down

0 comments on commit 2324a38

Please sign in to comment.