92 lines
2.5 KiB
Go
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)
|
|
}
|