fix: multivalue tags
This commit is contained in:
121
files/tags.go
121
files/tags.go
@@ -5,6 +5,8 @@ import (
|
||||
"encoding/base64"
|
||||
"io"
|
||||
"log"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/dhowden/tag"
|
||||
@@ -13,6 +15,7 @@ import (
|
||||
// ReadAudioTags extracts common audio metadata from the file and stores it in
|
||||
// the FileInfo.Tags map. It attempts to extract common fields and includes
|
||||
// embedded picture(s) as base64 where present.
|
||||
// Multi-valued tags like ARTISTS and ALBUMARTISTS are extracted as arrays.
|
||||
func (i *FileInfo) ReadAudioTags() error {
|
||||
if i.IsDir {
|
||||
return nil
|
||||
@@ -76,7 +79,20 @@ func (i *FileInfo) ReadAudioTags() error {
|
||||
}
|
||||
|
||||
// Keep raw metadata map if available (some formats expose additional fields)
|
||||
// Also extract multi-valued tags like ARTISTS and ALBUMARTISTS
|
||||
if raw := m.Raw(); raw != nil {
|
||||
// First pass: collect multi-valued tags (ARTISTS, ALBUMARTISTS)
|
||||
artists := extractMultiValuedTag(raw, "ARTISTS", "artists")
|
||||
albumArtists := extractMultiValuedTag(raw, "ALBUMARTISTS", "albumartists")
|
||||
|
||||
// Store multi-valued tags as arrays if present
|
||||
if len(artists) > 0 {
|
||||
tags["artists"] = artists
|
||||
}
|
||||
if len(albumArtists) > 0 {
|
||||
tags["albumartists"] = albumArtists
|
||||
}
|
||||
|
||||
for k, v := range raw {
|
||||
// Skip raw APIC entries (attached picture frame) to avoid
|
||||
// exposing large binary blobs. We already expose a friendly
|
||||
@@ -84,6 +100,11 @@ func (i *FileInfo) ReadAudioTags() error {
|
||||
if strings.EqualFold(k, "APIC") {
|
||||
continue
|
||||
}
|
||||
// Skip multi-valued tags we already handled
|
||||
kLower := strings.ToLower(k)
|
||||
if kLower == "artists" || kLower == "albumartists" {
|
||||
continue
|
||||
}
|
||||
// avoid overwriting already set common fields
|
||||
if _, ok := tags[k]; !ok {
|
||||
tags[k] = v
|
||||
@@ -91,6 +112,19 @@ func (i *FileInfo) ReadAudioTags() error {
|
||||
}
|
||||
}
|
||||
|
||||
// For FLAC files, use metaflac to read multi-valued tags properly
|
||||
// since dhowden/tag doesn't handle them correctly
|
||||
if realPath := i.RealPath(); realPath != "" && strings.EqualFold(filepath.Ext(realPath), ".flac") {
|
||||
if multiTags := readMultiValuedTagsWithMetaflac(realPath); multiTags != nil {
|
||||
if artists, ok := multiTags["ARTISTS"]; ok && len(artists) > 0 {
|
||||
tags["artists"] = artists
|
||||
}
|
||||
if albumArtists, ok := multiTags["ALBUMARTISTS"]; ok && len(albumArtists) > 0 {
|
||||
tags["albumartists"] = albumArtists
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Attach tags map
|
||||
if len(tags) > 0 {
|
||||
i.Tags = tags
|
||||
@@ -98,3 +132,90 @@ func (i *FileInfo) ReadAudioTags() error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// extractMultiValuedTag extracts values for a multi-valued tag from raw metadata.
|
||||
// It handles both Vorbis-style (multiple entries with same key) and ID3-style
|
||||
// (single string that may need splitting) formats.
|
||||
func extractMultiValuedTag(raw map[string]interface{}, keys ...string) []string {
|
||||
var result []string
|
||||
seen := make(map[string]bool)
|
||||
|
||||
for _, key := range keys {
|
||||
for rawKey, rawVal := range raw {
|
||||
if !strings.EqualFold(rawKey, key) {
|
||||
continue
|
||||
}
|
||||
// Handle different value types returned by the tag library
|
||||
switch v := rawVal.(type) {
|
||||
case string:
|
||||
v = strings.TrimSpace(v)
|
||||
if v != "" && !seen[v] {
|
||||
result = append(result, v)
|
||||
seen[v] = true
|
||||
}
|
||||
case []string:
|
||||
for _, s := range v {
|
||||
s = strings.TrimSpace(s)
|
||||
if s != "" && !seen[s] {
|
||||
result = append(result, s)
|
||||
seen[s] = true
|
||||
}
|
||||
}
|
||||
case []interface{}:
|
||||
for _, item := range v {
|
||||
if s, ok := item.(string); ok {
|
||||
s = strings.TrimSpace(s)
|
||||
if s != "" && !seen[s] {
|
||||
result = append(result, s)
|
||||
seen[s] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// readMultiValuedTagsWithMetaflac uses metaflac to read Vorbis comments from FLAC files.
|
||||
// This properly handles multi-valued tags (multiple entries with same key).
|
||||
func readMultiValuedTagsWithMetaflac(filepath string) map[string][]string {
|
||||
if _, err := exec.LookPath("metaflac"); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Use metaflac to export all tags
|
||||
cmd := exec.Command("metaflac", "--export-tags-to=-", filepath)
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
log.Printf("metaflac export failed for %s: %v", filepath, err)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parse output: each line is TAG=VALUE
|
||||
result := make(map[string][]string)
|
||||
for _, line := range strings.Split(string(out), "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
idx := strings.Index(line, "=")
|
||||
if idx < 0 {
|
||||
continue
|
||||
}
|
||||
key := strings.ToUpper(strings.TrimSpace(line[:idx]))
|
||||
value := strings.TrimSpace(line[idx+1:])
|
||||
if value == "" {
|
||||
continue
|
||||
}
|
||||
// Collect multi-valued artist tags (ARTISTS, ARTIST, ALBUMARTISTS)
|
||||
// Normalize key variations
|
||||
if key == "ARTISTS" || key == "ARTIST" {
|
||||
result["ARTISTS"] = append(result["ARTISTS"], value)
|
||||
} else if key == "ALBUMARTISTS" || key == "ALBUMARTIST" || key == "ALBUM ARTIST" || key == "ALBUM_ARTIST" {
|
||||
result["ALBUMARTISTS"] = append(result["ALBUMARTISTS"], value)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user