v1.0.0
This commit is contained in:
4
.env.example
Normal file
4
.env.example
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
LIBRARY_ROOT= # root directory for library
|
||||||
|
LIBRARY_FILETEMPLATE_PATH= # go text/template string that represents the path in the library
|
||||||
|
LIBRARY_FILETEMPLATE_FILE= # go text/template string that represents the file name in the library path
|
||||||
|
LIBRARY_NOTAGS_ROOT= # path to put files without tags
|
||||||
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
.env
|
||||||
8
go.mod
Normal file
8
go.mod
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
module zomo.land/slskd-rename
|
||||||
|
|
||||||
|
go 1.21.1
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/dhowden/tag v0.0.0-20240417053706-3d75831295e8 // indirect
|
||||||
|
github.com/joho/godotenv v1.5.1 // indirect
|
||||||
|
)
|
||||||
4
go.sum
Normal file
4
go.sum
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
github.com/dhowden/tag v0.0.0-20240417053706-3d75831295e8 h1:OtSeLS5y0Uy01jaKK4mA/WVIYtpzVm63vLVAPzJXigg=
|
||||||
|
github.com/dhowden/tag v0.0.0-20240417053706-3d75831295e8/go.mod h1:apkPC/CR3s48O2D7Y++n1XWEpgPNNCjXYga3PPbJe2E=
|
||||||
|
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
|
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
329
main.go
Normal file
329
main.go
Normal file
@@ -0,0 +1,329 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"github.com/dhowden/tag"
|
||||||
|
"github.com/joho/godotenv"
|
||||||
|
// _ "github.com/joho/godotenv/autoload"
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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\"", from, to)
|
||||||
|
return os.Rename(from, to)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user