2024-05-28 13:48:19 +02:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
_ "embed"
|
|
|
|
"io/fs"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2024-05-28 16:16:08 +02:00
|
|
|
"runtime"
|
2024-05-28 13:48:19 +02:00
|
|
|
"strconv"
|
|
|
|
"strings"
|
2024-05-28 16:16:08 +02:00
|
|
|
"sync"
|
2024-05-28 13:48:19 +02:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"git.velvettear.de/velvettear/id3tool/internal/config"
|
|
|
|
"git.velvettear.de/velvettear/loggo"
|
|
|
|
"github.com/bogem/id3v2/v2"
|
|
|
|
)
|
|
|
|
|
2024-05-28 16:16:08 +02:00
|
|
|
var taskLimiter = make(chan struct{}, runtime.NumCPU())
|
|
|
|
var taskCommunication = make(chan task)
|
|
|
|
var waitGroup = sync.WaitGroup{}
|
|
|
|
var counter int
|
|
|
|
var taskState = make(map[int]task)
|
|
|
|
|
2024-05-28 13:48:19 +02:00
|
|
|
func main() {
|
|
|
|
loggo.SetLogFormat("[$LOGLEVEL$] > $MESSAGE$ ($EXTRAS$) [$TIMEDIFF$]")
|
|
|
|
|
|
|
|
config.CheckArguments()
|
|
|
|
|
|
|
|
if len(config.Directory) == 0 {
|
|
|
|
loggo.Fatal("no directory provided")
|
|
|
|
}
|
|
|
|
stats, err := os.Stat(config.Directory)
|
|
|
|
if err != nil {
|
|
|
|
loggo.Fatal("could not get stats for provided directory", err.Error())
|
|
|
|
}
|
|
|
|
if !stats.IsDir() {
|
|
|
|
loggo.Fatal("provided argument is not a valid directory")
|
|
|
|
}
|
|
|
|
|
|
|
|
filepath.WalkDir(config.Directory, filterValidFiles)
|
2024-05-28 16:16:08 +02:00
|
|
|
waitGroup.Wait()
|
2024-05-28 13:48:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func filterValidFiles(path string, dir fs.DirEntry, err error) error {
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2024-05-28 16:16:08 +02:00
|
|
|
|
2024-05-28 13:48:19 +02:00
|
|
|
extension := filepath.Ext(path)
|
|
|
|
if dir.IsDir() || (extension != ".flac" && extension != ".mp3") {
|
|
|
|
return nil
|
|
|
|
}
|
2024-05-28 16:16:08 +02:00
|
|
|
|
|
|
|
counter++
|
|
|
|
waitGroup.Add(1)
|
|
|
|
taskLimiter <- struct{}{}
|
|
|
|
tmp := task{counter, path, false}
|
|
|
|
go func(tmp task) {
|
|
|
|
go func(tmp task) {
|
|
|
|
time.Sleep(time.Duration(config.Timeout) * time.Second)
|
|
|
|
tmp.timeout = true
|
|
|
|
taskCommunication <- tmp
|
|
|
|
<-taskLimiter
|
|
|
|
}(tmp)
|
|
|
|
cleanFrames(tmp.id, tmp.file)
|
|
|
|
tmp.timeout = false
|
|
|
|
taskCommunication <- tmp
|
|
|
|
<-taskLimiter
|
|
|
|
}(tmp)
|
|
|
|
|
|
|
|
result := <-taskCommunication
|
|
|
|
check := taskState[result.id]
|
|
|
|
if check.id == 0 {
|
|
|
|
taskState[result.id] = result
|
|
|
|
if result.timeout {
|
|
|
|
loggo.Warning("(" + strconv.Itoa(result.id) + ") cleaning of file's '" + result.file + "' id3 tags timed out")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
waitGroup.Done()
|
2024-05-28 13:48:19 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-05-28 16:16:08 +02:00
|
|
|
func cleanFrames(id int, file string) {
|
2024-05-28 13:48:19 +02:00
|
|
|
if len(file) == 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
timestamp := time.Now()
|
2024-05-28 16:16:08 +02:00
|
|
|
loggo.Info("(" + strconv.Itoa(id) + ") cleaning file's '" + file + "' id3 tags...")
|
2024-05-28 13:48:19 +02:00
|
|
|
|
|
|
|
tag, err := id3v2.Open(file, id3v2.Options{Parse: true})
|
|
|
|
if err != nil {
|
2024-05-28 16:16:08 +02:00
|
|
|
loggo.Error("("+strconv.Itoa(id)+") encountered an error opening the file '"+file+"' for reading the id3 tags", err.Error())
|
2024-05-28 13:48:19 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
defer tag.Close()
|
|
|
|
|
|
|
|
deleted := 0
|
|
|
|
for key, _ := range tag.AllFrames() {
|
|
|
|
filter := frameFilter(key)
|
|
|
|
if !filter {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
tag.DeleteFrames(key)
|
|
|
|
deleted++
|
|
|
|
|
|
|
|
}
|
|
|
|
if deleted > 0 {
|
2024-05-28 16:16:08 +02:00
|
|
|
loggo.Debug("(" + strconv.Itoa(id) + ") saving modified id3 tags (" + strconv.Itoa(deleted) + " deleted) to file '" + file + "'...")
|
2024-05-28 13:48:19 +02:00
|
|
|
err := tag.Save()
|
|
|
|
if err != nil {
|
2024-05-28 16:16:08 +02:00
|
|
|
loggo.Error("("+strconv.Itoa(id)+") encountered an error saving the modified id3 tags to file '"+file+"'", err.Error())
|
2024-05-28 13:48:19 +02:00
|
|
|
return
|
|
|
|
}
|
2024-05-28 16:16:08 +02:00
|
|
|
loggo.DebugTimed("("+strconv.Itoa(id)+") deleted "+strconv.Itoa(deleted)+" tag(s) from file '"+file+"'", timestamp.UnixMilli())
|
2024-05-28 13:48:19 +02:00
|
|
|
}
|
|
|
|
|
2024-05-28 16:16:08 +02:00
|
|
|
loggo.DebugTimed("("+strconv.Itoa(id)+") finished cleaning file's '"+file+"' id3 tags", timestamp.UnixMilli())
|
2024-05-28 13:48:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func frameFilter(frame string) bool {
|
|
|
|
for _, frameToKeep := range config.FramesToKeep {
|
|
|
|
if strings.EqualFold(frameToKeep, frame) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
2024-05-28 16:16:08 +02:00
|
|
|
|
|
|
|
type task struct {
|
|
|
|
id int
|
|
|
|
file string
|
|
|
|
timeout bool
|
|
|
|
}
|