initial commit
This commit is contained in:
commit
82efc8994e
8 changed files with 381 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
tagcleaner
|
16
.vscode/launch.json
vendored
Normal file
16
.vscode/launch.json
vendored
Normal 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
20
LICENSE.md
Normal 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
139
README.md
Normal 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
14
go.mod
Normal 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
41
go.sum
Normal 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
55
internal/config/config.go
Normal 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
95
main.go
Normal 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
|
||||
}
|
Loading…
Reference in a new issue