id3tool/main.go

136 lines
3.1 KiB
Go

package main
import (
_ "embed"
"io/fs"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
"sync"
"time"
"git.velvettear.de/velvettear/id3tool/internal/config"
"git.velvettear.de/velvettear/loggo"
"github.com/bogem/id3v2/v2"
)
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)
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)
waitGroup.Wait()
}
func filterValidFiles(path string, dir fs.DirEntry, err error) error {
if err != nil {
return err
}
extension := filepath.Ext(path)
if dir.IsDir() || (extension != ".flac" && extension != ".mp3") {
return nil
}
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()
return nil
}
func cleanFrames(id int, file string) {
if len(file) == 0 {
return
}
timestamp := time.Now()
loggo.Info("(" + strconv.Itoa(id) + ") cleaning file's '" + file + "' id3 tags...")
tag, err := id3v2.Open(file, id3v2.Options{Parse: true})
if err != nil {
loggo.Error("("+strconv.Itoa(id)+") encountered an error opening the file '"+file+"' for reading the id3 tags", err.Error())
return
}
defer tag.Close()
deleted := 0
for key, _ := range tag.AllFrames() {
filter := frameFilter(key)
if !filter {
continue
}
tag.DeleteFrames(key)
deleted++
}
if deleted > 0 {
loggo.Debug("(" + strconv.Itoa(id) + ") saving modified id3 tags (" + strconv.Itoa(deleted) + " deleted) to file '" + file + "'...")
err := tag.Save()
if err != nil {
loggo.Error("("+strconv.Itoa(id)+") encountered an error saving the modified id3 tags to file '"+file+"'", err.Error())
return
}
loggo.DebugTimed("("+strconv.Itoa(id)+") deleted "+strconv.Itoa(deleted)+" tag(s) from file '"+file+"'", timestamp.UnixMilli())
}
loggo.DebugTimed("("+strconv.Itoa(id)+") finished cleaning file's '"+file+"' id3 tags", timestamp.UnixMilli())
}
func frameFilter(frame string) bool {
for _, frameToKeep := range config.FramesToKeep {
if strings.EqualFold(frameToKeep, frame) {
return false
}
}
return true
}
type task struct {
id int
file string
timeout bool
}