Compare commits
4 Commits
73502669ea
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4de0cee7c6 | ||
|
|
ca76fea1da | ||
| c6956fe3d9 | |||
| c22772a0af |
@@ -2,3 +2,5 @@ LIBRARY_ROOT= # root directory for library
|
|||||||
LIBRARY_FILETEMPLATE_PATH= # go text/template string that represents the path in the 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_FILETEMPLATE_FILE= # go text/template string that represents the file name in the library path
|
||||||
LIBRARY_NOTAGS_ROOT= # path to put files without tags
|
LIBRARY_NOTAGS_ROOT= # path to put files without tags
|
||||||
|
|
||||||
|
FILENAME_LIMIT= # max length of a filename, default 255
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -9,4 +9,4 @@ require (
|
|||||||
github.com/joho/godotenv v1.5.1
|
github.com/joho/godotenv v1.5.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require golang.org/x/text v0.26.0 // indirect
|
require golang.org/x/text v0.28.0
|
||||||
|
|||||||
4
go.sum
4
go.sum
@@ -2,5 +2,5 @@ github.com/dhowden/tag v0.0.0-20240417053706-3d75831295e8 h1:OtSeLS5y0Uy01jaKK4m
|
|||||||
github.com/dhowden/tag v0.0.0-20240417053706-3d75831295e8/go.mod h1:apkPC/CR3s48O2D7Y++n1XWEpgPNNCjXYga3PPbJe2E=
|
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 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||||
|
|||||||
144
main.go
144
main.go
@@ -9,7 +9,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"regexp"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
@@ -93,7 +93,6 @@ func getDir() (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func mainPath(directory string) error {
|
func mainPath(directory string) error {
|
||||||
|
|
||||||
if err := testEnv(); err != nil {
|
if err := testEnv(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -109,8 +108,8 @@ func mainPath(directory string) error {
|
|||||||
// read tags
|
// read tags
|
||||||
tags, _ := getTagsInDir(directory)
|
tags, _ := getTagsInDir(directory)
|
||||||
|
|
||||||
// move files
|
|
||||||
if tags != nil {
|
if tags != nil {
|
||||||
|
// move tagged folder
|
||||||
if printLogs {
|
if printLogs {
|
||||||
log.Println("found tags, moving based on tags")
|
log.Println("found tags, moving based on tags")
|
||||||
}
|
}
|
||||||
@@ -119,6 +118,7 @@ func mainPath(directory string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// move untagged folder
|
||||||
if printLogs {
|
if printLogs {
|
||||||
log.Println("no tags found, moving to notags folder")
|
log.Println("no tags found, moving to notags folder")
|
||||||
}
|
}
|
||||||
@@ -127,6 +127,17 @@ func mainPath(directory string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// delete folder
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
files, err = os.ReadDir(directory)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(files) == 0 {
|
||||||
|
return os.Remove(directory)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -152,9 +163,34 @@ func testEnv() error {
|
|||||||
return errors.New("missing LIBRARY_NOTAGS_ROOT")
|
return errors.New("missing LIBRARY_NOTAGS_ROOT")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !testEnvFilenameLimit() {
|
||||||
|
return errors.New("unable to parse FILENAME_LIMIT")
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testEnvFilenameLimit() bool {
|
||||||
|
limit, ok := os.LookupEnv("FILENAME_LIMIT")
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
_, err := strconv.Atoi(limit)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getEnvFilenameLimit() int {
|
||||||
|
limit, ok := os.LookupEnv("FILENAME_LIMIT")
|
||||||
|
if !ok {
|
||||||
|
return 255
|
||||||
|
}
|
||||||
|
limit_i, err := strconv.Atoi(limit)
|
||||||
|
if err != nil {
|
||||||
|
return 255
|
||||||
|
}
|
||||||
|
return limit_i
|
||||||
|
}
|
||||||
|
|
||||||
func getTagsInDir(directory string) (tag.Metadata, error) {
|
func getTagsInDir(directory string) (tag.Metadata, error) {
|
||||||
files, err := os.ReadDir(directory)
|
files, err := os.ReadDir(directory)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -210,8 +246,31 @@ func doMoveNotags(directory string) error {
|
|||||||
newRoot := os.Getenv("LIBRARY_NOTAGS_ROOT")
|
newRoot := os.Getenv("LIBRARY_NOTAGS_ROOT")
|
||||||
_, folderName := path.Split(directory)
|
_, folderName := path.Split(directory)
|
||||||
toDirectory := path.Join(newRoot, folderName)
|
toDirectory := path.Join(newRoot, folderName)
|
||||||
|
|
||||||
|
files, err := os.ReadDir(directory)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return move(directory, toDirectory)
|
for _, file := range files {
|
||||||
|
if file.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if file.Name()[0] == '.' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
filefrom := path.Join(directory, file.Name())
|
||||||
|
fileto := path.Join(toDirectory, file.Name())
|
||||||
|
|
||||||
|
err = moveFile(filefrom, fileto)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func doMoveTags(directory string, albumTags tag.Metadata) error {
|
func doMoveTags(directory string, albumTags tag.Metadata) error {
|
||||||
@@ -242,7 +301,7 @@ func doMoveTags(directory string, albumTags tag.Metadata) error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
err = doMoveTagged(fname, tags)
|
err = doMoveTaggedFile(fname, tags)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -261,33 +320,12 @@ func doMoveTags(directory string, albumTags tag.Metadata) error {
|
|||||||
|
|
||||||
fname := path.Join(directory, file.Name())
|
fname := path.Join(directory, file.Name())
|
||||||
|
|
||||||
err = doMoveTaggedPathonly(fname, albumTags)
|
err = doMoveTaggedFileExtras(fname, albumTags)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// delete folder
|
|
||||||
files, err = os.ReadDir(directory)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(files) == 0 {
|
|
||||||
return os.Remove(directory)
|
|
||||||
}
|
|
||||||
|
|
||||||
// some files remain, wait a second to see if they go away
|
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
files, err = os.ReadDir(directory)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(files) == 0 {
|
|
||||||
return os.Remove(directory)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -312,7 +350,7 @@ type LibTemplateFile struct {
|
|||||||
DiscCount int
|
DiscCount int
|
||||||
}
|
}
|
||||||
|
|
||||||
func doMoveTagged(filePath string, fileTags tag.Metadata) error {
|
func doMoveTaggedFile(filePath string, fileTags tag.Metadata) error {
|
||||||
libRoot := os.Getenv("LIBRARY_ROOT")
|
libRoot := os.Getenv("LIBRARY_ROOT")
|
||||||
libTemplatePath := os.Getenv("LIBRARY_FILETEMPLATE_PATH")
|
libTemplatePath := os.Getenv("LIBRARY_FILETEMPLATE_PATH")
|
||||||
libTemplateFile := os.Getenv("LIBRARY_FILETEMPLATE_FILE")
|
libTemplateFile := os.Getenv("LIBRARY_FILETEMPLATE_FILE")
|
||||||
@@ -357,12 +395,18 @@ func doMoveTagged(filePath string, fileTags tag.Metadata) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
libFile := path.Join(libRoot, bufPath.String(), bufFile.String() + fileExt)
|
filenameLimit := getEnvFilenameLimit() - len(fileExt)
|
||||||
|
filename := bufFile.String()
|
||||||
|
if len(filename) > filenameLimit {
|
||||||
|
filename = filename[:filenameLimit]
|
||||||
|
}
|
||||||
|
|
||||||
return move(filePath, libFile)
|
libFile := path.Join(libRoot, bufPath.String(), filename + fileExt)
|
||||||
|
|
||||||
|
return moveFile(filePath, libFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
func doMoveTaggedPathonly(filePath string, albumTags tag.Metadata) error {
|
func doMoveTaggedFileExtras(filePath string, albumTags tag.Metadata) error {
|
||||||
libRoot := os.Getenv("LIBRARY_ROOT")
|
libRoot := os.Getenv("LIBRARY_ROOT")
|
||||||
libTemplatePath := os.Getenv("LIBRARY_FILETEMPLATE_PATH")
|
libTemplatePath := os.Getenv("LIBRARY_FILETEMPLATE_PATH")
|
||||||
_, fileName := path.Split(filePath)
|
_, fileName := path.Split(filePath)
|
||||||
@@ -384,7 +428,7 @@ func doMoveTaggedPathonly(filePath string, albumTags tag.Metadata) error {
|
|||||||
|
|
||||||
libFile := path.Join(libRoot, bufPath.String(), fileName)
|
libFile := path.Join(libRoot, bufPath.String(), fileName)
|
||||||
|
|
||||||
return move(filePath, libFile)
|
return moveFile(filePath, libFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ensureDirectoryPath(filePath string) error {
|
func ensureDirectoryPath(filePath string) error {
|
||||||
@@ -392,7 +436,7 @@ func ensureDirectoryPath(filePath string) error {
|
|||||||
return os.MkdirAll(directoryPath, 0777)
|
return os.MkdirAll(directoryPath, 0777)
|
||||||
}
|
}
|
||||||
|
|
||||||
func move(from, to string) error {
|
func moveFile(from, to string) error {
|
||||||
to = cleanPath(to)
|
to = cleanPath(to)
|
||||||
if _, err := os.Stat(to); err == nil {
|
if _, err := os.Stat(to); err == nil {
|
||||||
return fmt.Errorf("file already exists: %s", to)
|
return fmt.Errorf("file already exists: %s", to)
|
||||||
@@ -405,14 +449,11 @@ func move(from, to string) error {
|
|||||||
if printLogs {
|
if printLogs {
|
||||||
log.Printf("moving \"%s\" => \"%s\"\n", from, to)
|
log.Printf("moving \"%s\" => \"%s\"\n", from, to)
|
||||||
}
|
}
|
||||||
// os.Rename gives error "invalid cross-device link"
|
|
||||||
if err := os.Rename(from, to); err != nil {
|
return moveFileWrite(from, to)
|
||||||
return moveFile(from, to)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func moveFile(source, destination string) (err error) {
|
func moveFileWrite(source, destination string) (err error) {
|
||||||
src, err := os.Open(source)
|
src, err := os.Open(source)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -451,19 +492,28 @@ func moveFile(source, destination string) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func cleanPath(str string) string {
|
func cleanPath(str string) string {
|
||||||
return doClean(str, `[<>:"|?*]`)
|
return doClean(str, func(r rune) rune {
|
||||||
|
switch r {
|
||||||
|
case '<', '>', ':', '\'', '"', '|', '?', '*':
|
||||||
|
return '_'
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func cleanTemplateVariable(str string) string {
|
func cleanTemplateVariable(str string) string {
|
||||||
return doClean(str, `[<>:"|?*/\\.]`)
|
return doClean(str, func(r rune) rune {
|
||||||
|
switch r {
|
||||||
|
case '<', '>', ':', '\'', '"', '|', '?', '*', '/', '\\', '.':
|
||||||
|
return '_'
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func doClean(str string, reg string) string {
|
func doClean(str string, mapping func(rune) rune) string {
|
||||||
re := regexp.MustCompile(reg)
|
t := transform.Chain(norm.NFD, runes.Remove(runes.In(unicode.Mn)), norm.NFC, runes.Map(mapping))
|
||||||
s := re.ReplaceAllString(str, "")
|
result, _, err := transform.String(t, str)
|
||||||
|
|
||||||
t := transform.Chain(norm.NFD, runes.Remove(runes.In(unicode.Mn)), norm.NFC)
|
|
||||||
result, _, err := transform.String(t, s)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
music-rename
BIN
music-rename
Binary file not shown.
Reference in New Issue
Block a user