update metadata tags when sorting
This commit is contained in:
9
go.mod
9
go.mod
@@ -3,7 +3,10 @@ module git.kapelle.org/niklas/ripsort
|
||||
go 1.25.7
|
||||
|
||||
require (
|
||||
github.com/alexflint/go-arg v1.6.1 // indirect
|
||||
github.com/alexflint/go-scalar v1.2.0 // indirect
|
||||
github.com/dhowden/tag v0.0.0-20240417053706-3d75831295e8 // indirect
|
||||
github.com/alexflint/go-arg v1.6.1
|
||||
github.com/dhowden/tag v0.0.0-20240417053706-3d75831295e8
|
||||
github.com/go-flac/flacvorbis/v2 v2.0.2
|
||||
github.com/go-flac/go-flac/v2 v2.0.4
|
||||
)
|
||||
|
||||
require github.com/alexflint/go-scalar v1.2.0 // indirect
|
||||
|
||||
10
go.sum
10
go.sum
@@ -2,8 +2,18 @@ github.com/alexflint/go-arg v1.6.1 h1:uZogJ6VDBjcuosydKgvYYRhh9sRCusjOvoOLZopBln
|
||||
github.com/alexflint/go-arg v1.6.1/go.mod h1:nQ0LFYftLJ6njcaee0sU+G0iS2+2XJQfA8I062D0LGc=
|
||||
github.com/alexflint/go-scalar v1.2.0 h1:WR7JPKkeNpnYIOfHRa7ivM21aWAdHD0gEWHCx+WQBRw=
|
||||
github.com/alexflint/go-scalar v1.2.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dhowden/tag v0.0.0-20240417053706-3d75831295e8 h1:OtSeLS5y0Uy01jaKK4mA/WVIYtpzVm63vLVAPzJXigg=
|
||||
github.com/dhowden/tag v0.0.0-20240417053706-3d75831295e8/go.mod h1:apkPC/CR3s48O2D7Y++n1XWEpgPNNCjXYga3PPbJe2E=
|
||||
github.com/go-flac/flacvorbis/v2 v2.0.2 h1:xCL3OhxrxWkHrbWUBvGNe+6FQ03yLmBbz0v5z4V2PoQ=
|
||||
github.com/go-flac/flacvorbis/v2 v2.0.2/go.mod h1:SwTB5gs13VaM/N7rstwPoUsPibiMKklgwybYP9dYo2g=
|
||||
github.com/go-flac/go-flac/v2 v2.0.4 h1:atf/kFa8U9idtkA//NO22XGr+MzQLeXZecnmP9sYBf0=
|
||||
github.com/go-flac/go-flac/v2 v2.0.4/go.mod h1:sYOlTKxutMW0RDYF+KlD6Zn+VOCZlIFQG/r/usPveCs=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
@@ -1,33 +1,28 @@
|
||||
package ripsort
|
||||
package metadata
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/dhowden/tag"
|
||||
)
|
||||
|
||||
type metadata struct {
|
||||
Format tag.Format
|
||||
FileType tag.FileType
|
||||
type Metadata struct {
|
||||
Title string
|
||||
Artist []string
|
||||
Album string
|
||||
AlbumArtist []string
|
||||
Composer []string
|
||||
Genre string
|
||||
Year int
|
||||
Track int
|
||||
TotalTracks int
|
||||
Disc int
|
||||
TotalDiscs int
|
||||
Lyrics string
|
||||
Comment string
|
||||
HasPicture bool
|
||||
}
|
||||
|
||||
func ReadAudioTags(filePath string) (*metadata, error) {
|
||||
func ReadAudioTags(filePath string) (*Metadata, error) {
|
||||
f, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open file: %w", err)
|
||||
@@ -42,24 +37,42 @@ func ReadAudioTags(filePath string) (*metadata, error) {
|
||||
track, totalTracks := m.Track()
|
||||
disc, totalDiscs := m.Disc()
|
||||
|
||||
info := &metadata{
|
||||
Format: m.Format(),
|
||||
FileType: m.FileType(),
|
||||
info := &Metadata{
|
||||
Title: m.Title(),
|
||||
Artist: strings.Split(m.Artist(), ";"),
|
||||
Artist: parseSeperatedTag(m.Artist()),
|
||||
Album: m.Album(),
|
||||
AlbumArtist: strings.Split(m.AlbumArtist(), ";"),
|
||||
Composer: strings.Split(m.Composer(), ";"),
|
||||
Genre: m.Genre(),
|
||||
Year: m.Year(),
|
||||
AlbumArtist: parseSeperatedTag(m.AlbumArtist()),
|
||||
Track: track,
|
||||
TotalTracks: totalTracks,
|
||||
Disc: disc,
|
||||
TotalDiscs: totalDiscs,
|
||||
Lyrics: m.Lyrics(),
|
||||
Comment: m.Comment(),
|
||||
HasPicture: m.Picture() != nil,
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func parseSeperatedTag(s string) []string {
|
||||
parsed := make([]string, 0)
|
||||
|
||||
for element := range strings.SplitSeq(s, ";") {
|
||||
parsed = append(parsed, strings.TrimSpace(element))
|
||||
}
|
||||
|
||||
return parsed
|
||||
}
|
||||
|
||||
func UpdateMetadata(src, dst string, m Metadata) error {
|
||||
|
||||
ext := strings.ToLower(filepath.Ext(src))
|
||||
|
||||
switch {
|
||||
case ext == ".flac":
|
||||
return updateFlacMetadata(m, src, dst)
|
||||
default:
|
||||
slog.Warn("Unsupported format for updating metadata")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
57
internal/metadata/vobis.go
Normal file
57
internal/metadata/vobis.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package metadata
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-flac/flacvorbis/v2"
|
||||
"github.com/go-flac/go-flac/v2"
|
||||
)
|
||||
|
||||
func updateFlacMetadata(m Metadata, input, output string) error {
|
||||
f, err := flac.ParseFile(input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
vorbisMeta := createVorbisMetaBlock(m)
|
||||
meta := vorbisMeta.Marshal()
|
||||
|
||||
replaced := false
|
||||
for i, block := range f.Meta {
|
||||
if block.Type == flac.VorbisComment {
|
||||
f.Meta[i] = &meta
|
||||
replaced = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !replaced {
|
||||
f.Meta = append(f.Meta, &meta)
|
||||
}
|
||||
|
||||
return f.Save(output)
|
||||
}
|
||||
|
||||
func createVorbisMetaBlock(m Metadata) flacvorbis.MetaDataBlockVorbisComment {
|
||||
vorbisMeta := flacvorbis.New()
|
||||
|
||||
vorbisMeta.Add(flacvorbis.FIELD_TITLE, m.Title)
|
||||
vorbisMeta.Add(flacvorbis.FIELD_ALBUM, m.Album)
|
||||
vorbisMeta.Add("COMMENT", m.Comment)
|
||||
vorbisMeta.Add("TRACKNUMBER", strconv.Itoa(m.Track))
|
||||
vorbisMeta.Add("TOTALTRACKS", strconv.Itoa(m.TotalTracks))
|
||||
vorbisMeta.Add("DISCNUMBER", strconv.Itoa(m.Disc))
|
||||
vorbisMeta.Add("TOTALDISCS", strconv.Itoa(m.TotalDiscs))
|
||||
|
||||
for _, artist := range m.Artist {
|
||||
vorbisMeta.Add(flacvorbis.FIELD_ARTIST, artist)
|
||||
slog.Debug("Added ARTIST filed to metadata", "artist", artist)
|
||||
}
|
||||
|
||||
for _, albumArtist := range m.AlbumArtist {
|
||||
vorbisMeta.Add("ALBUMARTIST", albumArtist)
|
||||
}
|
||||
|
||||
return *vorbisMeta
|
||||
}
|
||||
@@ -6,30 +6,25 @@ import (
|
||||
"log/slog"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"git.kapelle.org/niklas/ripsort/internal/metadata"
|
||||
)
|
||||
|
||||
func Scan(filePath string) {
|
||||
info, err := ReadAudioTags(filePath)
|
||||
info, err := metadata.ReadAudioTags(filePath)
|
||||
|
||||
if err != nil {
|
||||
slog.Error("Reading metadata", "file", filePath, "err", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Printf("Format: %v\n", info.Format)
|
||||
fmt.Printf("File Type: %v\n", info.FileType)
|
||||
fmt.Printf("Title: %s\n", info.Title)
|
||||
fmt.Printf("Artist: %s\n", info.Artist)
|
||||
fmt.Printf("Album: %s\n", info.Album)
|
||||
fmt.Printf("Album Artist: %s\n", info.AlbumArtist)
|
||||
fmt.Printf("Composer: %s\n", info.Composer)
|
||||
fmt.Printf("Genre: %s\n", info.Genre)
|
||||
fmt.Printf("Year: %d\n", info.Year)
|
||||
fmt.Printf("Track: %d/%d\n", info.Track, info.TotalTracks)
|
||||
fmt.Printf("Disc: %d/%d\n", info.Disc, info.TotalDiscs)
|
||||
fmt.Printf("Lyrics: %s\n", info.Lyrics)
|
||||
fmt.Printf("Comment: %s\n", info.Comment)
|
||||
fmt.Printf("Has Picture: %v\n", info.HasPicture)
|
||||
|
||||
sortPath := pathForFile(filePath, *info)
|
||||
fmt.Printf("Sort path: %s\n", sortPath)
|
||||
@@ -48,7 +43,7 @@ func Sort(dst, path string) {
|
||||
slog.Error("Unsupported file format", "file", path)
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := sortSong(path, dst); err != nil {
|
||||
if err := sortSong(path, dst, true); err != nil {
|
||||
slog.Error("Failed to sort single file", "file", path, "err", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
@@ -66,7 +61,7 @@ func Sort(dst, path string) {
|
||||
if !fileSupported(p) {
|
||||
return nil
|
||||
}
|
||||
if err := sortSong(p, dst); err != nil {
|
||||
if err := sortSong(p, dst, true); err != nil {
|
||||
slog.Error("Failed to sort file", "file", p, "err", err)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"git.kapelle.org/niklas/ripsort/internal/metadata"
|
||||
)
|
||||
|
||||
func fileSupported(file string) bool {
|
||||
@@ -19,8 +21,8 @@ func fileSupported(file string) bool {
|
||||
return supported[strings.ToLower(filepath.Ext(file))]
|
||||
}
|
||||
|
||||
func sortSong(src, dst string) error {
|
||||
m, err := ReadAudioTags(src)
|
||||
func sortSong(src, dst string, updateMeta bool) error {
|
||||
m, err := metadata.ReadAudioTags(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -28,10 +30,20 @@ func sortSong(src, dst string) error {
|
||||
dstPath := pathForFile(src, *m)
|
||||
finalPath := filepath.Join(dst, dstPath)
|
||||
|
||||
slog.Info("Copying song", "file", src, "dst", dstPath)
|
||||
err = copyFile(src, finalPath)
|
||||
if err != nil {
|
||||
return err
|
||||
if updateMeta {
|
||||
|
||||
slog.Info("Copying song with updated metadata", "file", src, "dst", dstPath)
|
||||
err = copyFileUpdateMetadata(src, finalPath, *m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
|
||||
slog.Info("Copying song", "file", src, "dst", dstPath)
|
||||
err = copyFile(src, finalPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
extraFiles := checkForExtraFiles(src)
|
||||
@@ -66,7 +78,7 @@ func sanitizeName(name string) *string {
|
||||
return &name
|
||||
}
|
||||
|
||||
func getArtistName(m metadata) string {
|
||||
func getArtistName(m metadata.Metadata) string {
|
||||
var artist *string
|
||||
|
||||
if len(m.Artist) > 0 {
|
||||
@@ -86,7 +98,7 @@ func getArtistName(m metadata) string {
|
||||
return *artist
|
||||
}
|
||||
|
||||
func getAlbumName(m metadata) string {
|
||||
func getAlbumName(m metadata.Metadata) string {
|
||||
album := sanitizeName(m.Album)
|
||||
if album == nil {
|
||||
return "Unknown Album"
|
||||
@@ -94,7 +106,7 @@ func getAlbumName(m metadata) string {
|
||||
return *album
|
||||
}
|
||||
|
||||
func getTitle(src string, m metadata) string {
|
||||
func getTitle(src string, m metadata.Metadata) string {
|
||||
title := sanitizeName(m.Title)
|
||||
if title == nil {
|
||||
return *sanitizeName(strings.TrimSuffix(filepath.Base(src), filepath.Ext(src)))
|
||||
@@ -102,7 +114,7 @@ func getTitle(src string, m metadata) string {
|
||||
return *title
|
||||
}
|
||||
|
||||
func pathForFile(src string, m metadata) string {
|
||||
func pathForFile(src string, m metadata.Metadata) string {
|
||||
ext := strings.ToLower(filepath.Ext(src))
|
||||
|
||||
artist := getArtistName(m)
|
||||
@@ -114,6 +126,22 @@ func pathForFile(src string, m metadata) string {
|
||||
return filepath.Join(artist, album, filename)
|
||||
}
|
||||
|
||||
func copyFileUpdateMetadata(src, dst string, m metadata.Metadata) error {
|
||||
ext := strings.ToLower(filepath.Ext(src))
|
||||
|
||||
if ext != ".flac" {
|
||||
slog.Warn("Copying file without updating metadata. Unsupported format", "file", src)
|
||||
return copyFile(src, dst)
|
||||
}
|
||||
|
||||
dir := filepath.Dir(dst)
|
||||
if err := os.MkdirAll(dir, 0o755); err != nil {
|
||||
return fmt.Errorf("create destination directory: %w", err)
|
||||
}
|
||||
|
||||
return metadata.UpdateMetadata(src, dst, m)
|
||||
}
|
||||
|
||||
func copyFile(src, dst string) error {
|
||||
in, err := os.Open(src)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user