initial commit

This commit is contained in:
Daniel Sommer 2024-05-28 13:48:19 +02:00
commit 82efc8994e
8 changed files with 381 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
tagcleaner

16
.vscode/launch.json vendored Normal file
View file

@ -0,0 +1,16 @@
{
"version": "0.0.1",
"configurations": [
{
"name": "id3tool",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}/main.go",
"args": [
"--debug",
"/home/velvettear/downloads/test"
]
}
]
}

20
LICENSE.md Normal file
View file

@ -0,0 +1,20 @@
# MIT License
**Copyright (c) 2024 Daniel Sommer \<daniel.sommer@velvettear.de\>**
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
**THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.**

139
README.md Normal file
View file

@ -0,0 +1,139 @@
# id3tool
a simple cli tool to clean id3 tags (v23 / v24) from '.mp3' and '.flac' files.
## usage
id3tool [options] \<directory\>
### options
- -h | --help: print help
- -d | --debug: enable debug mode
- -f | --frames: frames to keep (comma separated list)
**note**:
if nothing is specified via the `--frames` option the following frames will be kept:
`APIC`, `TALB`, `TCON`, `TDAT`, `TDRC`, `TIT2`, `TPOS`, `TPE1`, `TPE2`, `TRCK`
### available frames
**v23:**
- Attached picture -> `APIC`
- Chapters -> `CHAP`
- Comments -> `COMM`
- Album/Movie/Show title -> `TALB`
- BPM -> `TBPM`
- Composer -> `TCOM`
- Content type -> `TCON`
- Copyright message -> `TCOP`
- Date -> `TDAT`
- Playlist delay -> `TDLY`
- Encoded by -> `TENC`
- Lyricist/Text writer -> `TEXT`
- File type -> `TFLT`
- Time -> `TIME`
- Content group description -> `TIT1`
- Title/Songname/Content description -> `TIT2`
- Subtitle/Description refinement -> `TIT3`
- Initial key -> `TKEY`
- Language -> `TLAN`
- Length -> `TLEN`
- Media type -> `TMED`
- Original album/movie/show title -> `TOAL`
- Original filename -> `TOFN`
- Original lyricist/text writer -> `TOLY`
- Original artist/performer -> `TOPE`
- Original release year -> `TORY`
- Popularimeter -> `POPM`
- File owner/licensee -> `TOWN`
- Lead artist/Lead performer/Soloist/Performing group -> `TPE1`
- Band/Orchestra/Accompaniment -> `TPE2`
- Conductor/performer refinement -> `TPE3`
- Interpreted, remixed, or otherwise modified by -> `TPE4`
- Part of a set -> `TPOS`
- Publisher -> `TPUB`
- Track number/Position in set -> `TRCK`
- Recording dates -> `TRDA`
- Internet radio station name -> `TRSN`
- Internet radio station owner -> `TRSO`
- Size -> `TSIZ`
- ISRC -> `TSRC`
- Software/Hardware and settings used for encoding -> `TSSE`
- Year -> `TYER`
- User defined text information frame -> `TXXX`
- Unique file identifier -> `UFID`
- Unsynchronised lyrics/text transcription -> `USLT`
**convenience:**
- Artist -> `TPE1`
- Title -> `TIT2`
- Genre -> `TCON`
**v24:**
- Attached picture -> `APIC`
- Chapters -> `CHAP`
- Comments -> `COMM`
- Album/Movie/Show title -> `TALB`
- BPM -> `TBPM`
- Composer -> `TCOM`
- Content type -> `TCON`
- Copyright message -> `TCOP`
- Encoding time -> `TDEN`
- Playlist delay -> `TDLY`
- Original release time -> `TDOR`
- Recording time -> `TDRC`
- Release time -> `TDRL`
- Tagging time -> `TDTG`
- Encoded by -> `TENC`
- Lyricist/Text writer -> `TEXT`
- File type -> `TFLT`
- Involved people list -> `TIPL`
- Content group description -> `TIT1`
- Title/Songname/Content description -> `TIT2`
- Subtitle/Description refinement -> `TIT3`
- Initial key -> `TKEY`
- Language -> `TLAN`
- Length -> `TLEN`
- Musician credits list -> `TMCL`
- Media type -> `TMED`
- Mood -> `TMOO`
- Original album/movie/show title -> `TOAL`
- Original filename -> `TOFN`
- Original lyricist/text writer -> `TOLY`
- Original artist/performer -> `TOPE`
- Popularimeter -> `POPM`
- File owner/licensee -> `TOWN`
- Lead artist/Lead performer/Soloist/Performing group -> `TPE1`
- Band/Orchestra/Accompaniment -> `TPE2`
- Conductor/performer refinement -> `TPE3`
- Interpreted, remixed, or otherwise modified by -> `TPE4`
- Part of a set -> `TPOS`
- Produced notice -> `TPRO`
- Publisher -> `TPUB`
- Track number/Position in set -> `TRCK`
- Internet radio station name -> `TRSN`
- Internet radio station owner -> `TRSO`
- Album sort order -> `TSOA`
- Performer sort order -> `TSOP`
- Title sort order -> `TSOT`
- ISRC -> `TSRC`
- Software/Hardware and settings used for encoding -> `TSSE`
- Set subtitle -> `TSST`
- User defined text information frame -> `TXXX`
- Unique file identifier -> `UFID`
- Unsynchronised lyrics/text transcription -> `USLT`
**fallback:**
- Date -> `TDRC`
- Time -> `TDRC`
- Original release year -> `TDOR`
- Recording dates -> `TDRC`
- Size -> ``
- Year -> `TDRC`
**convenience:**
- Artist -> `TPE1`
- Title -> `TIT2`
- Genre -> `TCON`

14
go.mod Normal file
View file

@ -0,0 +1,14 @@
module git.velvettear.de/velvettear/id3tool
go 1.21
require git.velvettear.de/velvettear/loggo v0.0.0-20231113084149-980a00b4e084
require (
github.com/bogem/id3v2/v2 v2.1.4
github.com/fatih/color v1.15.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/text v0.15.0 // indirect
)

41
go.sum Normal file
View file

@ -0,0 +1,41 @@
git.velvettear.de/velvettear/loggo v0.0.0-20231113084149-980a00b4e084 h1:13S20q+usZ+yJr2cxxOpnPrOd5+4PwCbQg56RuWSkKk=
git.velvettear.de/velvettear/loggo v0.0.0-20231113084149-980a00b4e084/go.mod h1:Jjjno0vz7v1Y6tCnpQHnq2TVL2+5m7TXkmNNYYREIMo=
github.com/bogem/id3v2/v2 v2.1.4 h1:CEwe+lS2p6dd9UZRlPc1zbFNIha2mb2qzT1cCEoNWoI=
github.com/bogem/id3v2/v2 v2.1.4/go.mod h1:l+gR8MZ6rc9ryPTPkX77smS5Me/36gxkMgDayZ9G1vY=
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

55
internal/config/config.go Normal file
View file

@ -0,0 +1,55 @@
package config
import (
"os"
"strings"
"git.velvettear.de/velvettear/loggo"
)
var Directory string
var FramesToKeep []string
// check program arguments and set variables accordingly
func CheckArguments() {
argsCount := len(os.Args)
var framesToKeep []string
for index := 0; index < argsCount; index++ {
switch os.Args[index] {
case "-d":
fallthrough
case "--debug":
loggo.SetLogLevel(loggo.DebugLevel)
case "-f":
fallthrough
case "--frames":
framesToKeep = strings.Split(os.Args[index+1], ",")
case "-h":
fallthrough
case "--help":
printHelp()
}
}
Directory = os.Args[len(os.Args)-1]
if len(framesToKeep) == 0 {
FramesToKeep = []string{"APIC", "TALB", "TCON", "TDAT", "TDRC", "TIT2", "TPOS", "TPE1", "TPE2", "TRCK"}
} else {
for _, frame := range framesToKeep {
FramesToKeep = append(FramesToKeep, strings.ToUpper(strings.TrimSpace(frame)))
}
}
}
// print the help
func printHelp() {
loggo.SetLogFormat("# $MESSAGE$")
loggo.Info("tagcleaner")
loggo.Info("Daniel Sommer <daniel.sommer@velvettear.de>")
loggo.Info("\n")
loggo.Info("usage:\ttagcleaner [options] <directory>")
loggo.Info("\n")
loggo.Info("options:")
loggo.Info("\t-d | --debug\tenable debug mode")
loggo.Info("\t-f | --frames\tframes to keep")
os.Exit(0)
}

95
main.go Normal file
View file

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