Files

92 lines
2.5 KiB
Go

// Package tag writes track metadata into finished audio files.
//
// Every container stores metadata differently: mp3 uses ID3v2 frames, flac
// uses Vorbis comments, and wav carries an ID3 chunk plus a RIFF LIST/INFO
// chunk for players that read only one of the two. Write hides that behind a
// single Metadata struct and dispatches on the file extension.
//
// The taggers are written for freshly downloaded files. Empty fields are
// skipped rather than written as blanks, and each tagger writes through a
// temporary file so a failure part way cannot corrupt the audio.
package tag
import (
"path/filepath"
"github.com/bogem/id3v2/v2"
"github.com/go-flac/flacvorbis/v2"
"github.com/go-flac/go-flac/v2"
)
// AlbumMetadata is the subset of tags that only make sense for a track that
// belongs to an album. It is nil on a standalone single.
type AlbumMetadata struct {
Artist string
Title string
Label string
OriginalReleaseDate string
ReleaseDate string
ProducerLine string
Copyright string
}
// Metadata is the container-independent tag set. Every field is a string
// because the underlying formats store them as text; conversions such as
// Duration to milliseconds happen inside the individual taggers.
type Metadata struct {
Title string
Artists string
Composers string
Lyricists string
Genre string
BPM string
Key string
TrackNumber string
Duration string
Gain string
ISRC string
Cover []byte
Album *AlbumMetadata
}
type tagger interface {
write(m Metadata) error
}
// newTagger picks an implementation from the file extension. Anything that is
// not mp3 or wav is attempted as flac rather than rejected, so an unexpected
// extension fails with a parse error from the flac library instead of a
// generic unsupported-format message.
func newTagger(filePath string) (tagger, error) {
switch filepath.Ext(filePath) {
case ".mp3":
tag, err := id3v2.Open(filePath, id3v2.Options{Parse: true})
if err != nil {
return nil, err
}
return &id3v2Tagger{tag: tag}, nil
case ".wav":
return &wavTagger{path: filePath}, nil
}
file, err := flac.ParseFile(filePath)
if err != nil {
return nil, err
}
cmts, idx := extractFLACComment(file)
if cmts == nil {
cmts = flacvorbis.New()
}
return &flacTagger{file: file, cmts: cmts, index: idx, path: filePath}, nil
}
func Write(filePath string, m Metadata) error {
t, err := newTagger(filePath)
if err != nil {
return err
}
return t.write(m)
}