Files
music-rename/main.go
2025-05-24 21:23:21 -05:00

337 lines
6.9 KiB
Go

package main
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"log"
"os"
"path"
"strings"
"text/template"
"slices"
"github.com/dhowden/tag"
"github.com/joho/godotenv"
)
type DownloadDirectoryComplete struct {
Version int `json:"version"`
LocalDirectoryName string `json:"localDirectoryName"`
RemoteDirectoryName string `json:"remoteDirectoryName"`
Username string `json:"username"`
}
func (d *DownloadDirectoryComplete) WarnVersion() {
supportedVersions := []int{ 0 }
if !slices.Contains(supportedVersions, d.Version) {
supportedVersionsStr := strings.Trim(strings.Join(strings.Fields(fmt.Sprint(supportedVersions)), ","), "[]")
log.Println("WARNING: Given data version not supported.")
log.Printf( "WARNING: Supported versions: %s\n", supportedVersionsStr)
log.Printf( "WARNING: Given version: %d\n", d.Version)
log.Println("WARNING: Continuing, but there may be demons")
}
}
func main() {
err := mainErr()
if err != nil {
log.Fatal("ERROR: ", err)
}
}
func mainErr() error {
if len(os.Args) < 2 {
return errors.New("missing data argument")
}
if err := godotenv.Load(); err != nil {
return err
}
execPath, _ := path.Split(os.Args[0])
localConfPath := path.Join(execPath, "music-rename.env")
if _, err := os.Stat(localConfPath); err == nil {
if err := godotenv.Load(localConfPath); err != nil {
return err
}
}
if err := testEnv(); err != nil {
return err
}
// parse $DATA
log.Println("Parsing slskd data")
dataArg := os.Args[1]
var data DownloadDirectoryComplete
if err := json.Unmarshal([]byte(dataArg), &data); err != nil {
return err
}
data.WarnVersion()
files, err := os.ReadDir(data.LocalDirectoryName)
if err != nil {
return err
}
if len(files) == 0 {
return errors.New("no files found")
}
// read tags
tags, _ := getTagsInDir(data.LocalDirectoryName)
// move files
if tags != nil {
log.Println("found tags, moving based on tags")
err := doMoveTags(data.LocalDirectoryName, tags)
if err != nil {
return err
}
} else {
log.Println("no tags found, moving to notags folder")
err := doMoveNotags(data.LocalDirectoryName)
if err != nil {
return err
}
}
return nil
}
func testEnv() error {
_, ok := os.LookupEnv("LIBRARY_ROOT")
if !ok {
return errors.New("missing LIBRARY_ROOT")
}
_, ok = os.LookupEnv("LIBRARY_FILETEMPLATE_PATH")
if !ok {
return errors.New("missing LIBRARY_FILETEMPLATE_PATH")
}
_, ok = os.LookupEnv("LIBRARY_FILETEMPLATE_FILE")
if !ok {
return errors.New("missing LIBRARY_FILETEMPLATE_FILE")
}
_, ok = os.LookupEnv("LIBRARY_NOTAGS_ROOT")
if !ok {
return errors.New("missing LIBRARY_NOTAGS_ROOT")
}
return nil
}
func getTagsInDir(directory string) (tag.Metadata, error) {
files, err := os.ReadDir(directory)
if err != nil {
return nil, err
}
log.Printf("DIRECTORY: %s\n", directory)
for _, file := range files {
fname := path.Join(directory, file.Name())
log.Printf(" FILE: %s\n", fname)
if file.IsDir() {
log.Print(" IS DIRECTORY\n")
continue
}
file, err := os.Open(fname)
if err != nil {
log.Print(" CANT OPEN\n")
continue
}
defer file.Close()
tags, err := tag.ReadFrom(file)
if err != nil {
log.Print(" NO TAGS\n")
continue
}
// found tag data
return tags, nil
}
// no tag data in any file
return nil, errors.New("missing tag data")
}
func doMoveNotags(directory string) error {
newRoot := os.Getenv("LIBRARY_NOTAGS_ROOT")
_, folderName := path.Split(directory)
toDirectory := path.Join(newRoot, folderName)
return rename(directory, toDirectory)
}
func doMoveTags(directory string, albumTags tag.Metadata) error {
// move all tagged files
files, err := os.ReadDir(directory)
if err != nil {
return err
}
for _, file := range files {
if file.IsDir() {
continue
}
fname := path.Join(directory, file.Name())
file, err := os.Open(fname)
if err != nil {
continue
}
defer file.Close()
tags, err := tag.ReadFrom(file)
if err != nil {
continue
}
err = doMoveTagged(fname, tags)
if err != nil {
return err
}
}
// move the rest
files, err = os.ReadDir(directory)
if err != nil {
return err
}
for _, file := range files {
fname := path.Join(directory, file.Name())
err = doMoveTaggedPathonly(fname, albumTags)
if err != nil {
return err
}
}
// delete folder
return os.Remove(directory)
}
type LibTemplatePath struct {
Album string
AlbumArtist string
Genre string
Year int
}
type LibTemplateFile struct {
Title string
Album string
Artist string
AlbumArtist string
Composer string
Genre string
Year int
Track int
TrackCount int
Disc int
DiscCount int
}
func doMoveTagged(filePath string, fileTags tag.Metadata) error {
libRoot := os.Getenv("LIBRARY_ROOT")
libTemplatePath := os.Getenv("LIBRARY_FILETEMPLATE_PATH")
libTemplateFile := os.Getenv("LIBRARY_FILETEMPLATE_FILE")
fileExt := path.Ext(filePath)
track, trackCount := fileTags.Track()
disc, discCount := fileTags.Disc()
tmplPath := template.Must(template.New("libPath").Parse(libTemplatePath))
tmplFile := template.Must(template.New("libFile").Parse(libTemplateFile))
tmplDataFile := LibTemplateFile{
Title: fileTags.Title(),
Album: fileTags.Album(),
Artist: fileTags.Artist(),
AlbumArtist: fileTags.AlbumArtist(),
Composer: fileTags.Composer(),
Genre: fileTags.Genre(),
Year: fileTags.Year(),
Track: track,
TrackCount: trackCount,
Disc: disc,
DiscCount: discCount,
}
tmplDataPath := LibTemplatePath{
Album: fileTags.Album(),
AlbumArtist: fileTags.AlbumArtist(),
Genre: fileTags.Genre(),
Year: fileTags.Year(),
}
bufPath := bytes.NewBufferString("")
err := tmplPath.Execute(bufPath, tmplDataPath)
if err != nil {
return err
}
bufFile := bytes.NewBufferString("")
err = tmplFile.Execute(bufFile, tmplDataFile)
if err != nil {
return err
}
libFile := path.Join(libRoot, bufPath.String(), bufFile.String() + fileExt)
return rename(filePath, libFile)
}
func doMoveTaggedPathonly(filePath string, albumTags tag.Metadata) error {
libRoot := os.Getenv("LIBRARY_ROOT")
libTemplatePath := os.Getenv("LIBRARY_FILETEMPLATE_PATH")
_, fileName := path.Split(filePath)
tmplPath := template.Must(template.New("libPath").Parse(libTemplatePath))
tmplData := LibTemplatePath{
Album: albumTags.Album(),
AlbumArtist: albumTags.AlbumArtist(),
Genre: albumTags.Genre(),
Year: albumTags.Year(),
}
bufPath := bytes.NewBufferString("")
err := tmplPath.Execute(bufPath, tmplData)
if err != nil {
return err
}
libFile := path.Join(libRoot, bufPath.String(), fileName)
return rename(filePath, libFile)
}
func ensureDirectoryPath(filePath string) error {
directoryPath, _ := path.Split(filePath)
return os.MkdirAll(directoryPath, 0777)
}
func rename(from, to string) error {
if _, err := os.Stat(to); err == nil {
return fmt.Errorf("file already exists: %s", to)
}
if err := ensureDirectoryPath(to); err != nil {
return err
}
fmt.Printf("renaming \"%s\" => \"%s\"\n", from, to)
return os.Rename(from, to)
}