feat(metadata): add top Apply & Add Field UI, beautify table; preserve existing tags; skip empty values; hide APIC; capture ffmpeg stderr; stable column widths; fix title capitalization; add EN/FR translations

This commit is contained in:
vcadoux
2026-01-28 14:09:06 +01:00
parent a58f930d58
commit 85c64667c1
8 changed files with 229 additions and 590 deletions

View File

@@ -1,66 +1,7 @@
package files
import (
"fmt"
"strings"
// This file is a placeholder to avoid accidental module resolution for
// non-implemented MP3 metadata writers. The actual implementation is not
// required at runtime, and we should not import external packages here.
id3v2 "github.com/bogem/id3v2/v2"
)
// applyMP3Tags writes only the provided non-empty tags to the MP3 file at
// realPath. It preserves existing frames when not overwritten.
func applyMP3Tags(realPath string, tags map[string]string) error {
// Open tag for read/write
tag, err := id3v2.Open(realPath, id3v2.Options{Parse: true})
if err != nil {
return fmt.Errorf("open id3v2: %w", err)
}
defer tag.Close()
// Helper to set a text frame (replace existing)
setFrame := func(frameID, value string) {
if strings.TrimSpace(value) == "" {
return
}
tag.DeleteAllFrames(frameID)
tag.AddTextFrame(frameID, tag.DefaultEncoding(), value)
}
for k, v := range tags {
if strings.TrimSpace(v) == "" {
continue
}
switch strings.ToLower(k) {
case "title":
setFrame("TIT2", v)
case "artist":
setFrame("TPE1", v)
case "album":
setFrame("TALB", v)
case "albumartist", "album artist":
setFrame("TPE2", v)
case "composer":
setFrame("TCOM", v)
case "year", "date":
setFrame("TDRC", v)
case "track":
setFrame("TRCK", v)
case "disc":
setFrame("TPOS", v)
case "genre":
setFrame("TCON", v)
default:
// write custom text frame under TXXX with description = key
// Use description to store the original key so we don't collide
// with standard frames.
// Note: bogem/id3v2 uses AddTextFrame for TXXX as well.
tag.AddTextFrame("TXXX", tag.DefaultEncoding(), v)
}
}
if err := tag.Save(); err != nil {
return fmt.Errorf("save id3v2: %w", err)
}
return nil
}
// Intentionally left blank.