update metadata tags when sorting

This commit is contained in:
2026-04-15 16:03:59 +02:00
parent b4e3f2f372
commit a92b632234
6 changed files with 149 additions and 43 deletions

View File

@@ -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
}

View 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
}

View File

@@ -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
}

View File

@@ -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 {