refactor: decouple tag writers from deezer models
This commit is contained in:
@@ -16,7 +16,7 @@ import (
|
|||||||
"github.com/mathismqn/godeez/internal/deezer"
|
"github.com/mathismqn/godeez/internal/deezer"
|
||||||
"github.com/mathismqn/godeez/internal/fileutil"
|
"github.com/mathismqn/godeez/internal/fileutil"
|
||||||
"github.com/mathismqn/godeez/internal/store"
|
"github.com/mathismqn/godeez/internal/store"
|
||||||
"github.com/mathismqn/godeez/internal/tags"
|
"github.com/mathismqn/godeez/internal/tag"
|
||||||
)
|
)
|
||||||
|
|
||||||
const chunkSize = 2048
|
const chunkSize = 2048
|
||||||
@@ -234,7 +234,7 @@ func (c *Client) streamToFile(ctx context.Context, stream io.ReadCloser, outputP
|
|||||||
func (c *Client) finalizeDownload(resource deezer.Resource, track *deezer.Track, outputPath, mediaFormat, genre string, cover []byte, bpmKey bpmKey) []string {
|
func (c *Client) finalizeDownload(resource deezer.Resource, track *deezer.Track, outputPath, mediaFormat, genre string, cover []byte, bpmKey bpmKey) []string {
|
||||||
var warnings []string
|
var warnings []string
|
||||||
|
|
||||||
if err := tags.AddTags(resource, track, cover, outputPath, bpmKey.BPM, bpmKey.Key, genre); err != nil {
|
if err := tag.Write(outputPath, buildTagMetadata(resource, track, cover, bpmKey, genre)); err != nil {
|
||||||
warnings = append(warnings, fmt.Sprintf("failed to add tags: %v", err))
|
warnings = append(warnings, fmt.Sprintf("failed to add tags: %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package downloader
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/mathismqn/godeez/internal/deezer"
|
||||||
|
"github.com/mathismqn/godeez/internal/tag"
|
||||||
|
)
|
||||||
|
|
||||||
|
func buildTagMetadata(resource deezer.Resource, track *deezer.Track, cover []byte, bpm bpmKey, genre string) tag.Metadata {
|
||||||
|
m := tag.Metadata{
|
||||||
|
Title: track.GetTitle(),
|
||||||
|
Artists: strings.Join(track.Contributors.MainArtists, ", "),
|
||||||
|
Composers: strings.Join(track.Contributors.Composers, ", "),
|
||||||
|
Lyricists: strings.Join(track.Contributors.Authors, ", "),
|
||||||
|
Genre: genre,
|
||||||
|
BPM: bpm.BPM,
|
||||||
|
Key: bpm.Key,
|
||||||
|
TrackNumber: track.TrackNumber,
|
||||||
|
Duration: track.Duration,
|
||||||
|
Gain: track.Gain,
|
||||||
|
ISRC: track.ISRC,
|
||||||
|
Cover: cover,
|
||||||
|
}
|
||||||
|
|
||||||
|
if album, ok := resource.(*deezer.Album); ok {
|
||||||
|
data := album.Results.Data
|
||||||
|
m.Album = &tag.AlbumMetadata{
|
||||||
|
Artist: data.Artist,
|
||||||
|
Title: data.Title,
|
||||||
|
Label: data.Label,
|
||||||
|
OriginalReleaseDate: data.OriginalReleaseDate,
|
||||||
|
ReleaseDate: data.PhysicalReleaseDate,
|
||||||
|
ProducerLine: data.ProducerLine,
|
||||||
|
Copyright: data.Copyright,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
package tag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-flac/flacpicture/v2"
|
||||||
|
"github.com/go-flac/flacvorbis/v2"
|
||||||
|
"github.com/go-flac/go-flac/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type flacTagger struct {
|
||||||
|
file *flac.File
|
||||||
|
cmts *flacvorbis.MetaDataBlockVorbisComment
|
||||||
|
index int
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *flacTagger) write(m Metadata) error {
|
||||||
|
if m.Album != nil {
|
||||||
|
date := m.Album.ReleaseDate
|
||||||
|
if parts := strings.Split(date, "-"); len(parts) == 3 {
|
||||||
|
date = parts[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
t.addTag("TRACKNUMBER", m.TrackNumber)
|
||||||
|
t.addTag("ALBUMARTIST", m.Album.Artist)
|
||||||
|
t.addTag("ALBUM", m.Album.Title)
|
||||||
|
t.addTag("PUBLISHER", m.Album.Label)
|
||||||
|
t.addTag("ORIGINALDATE", m.Album.OriginalReleaseDate)
|
||||||
|
t.addTag("DATE", date)
|
||||||
|
t.addTag("COMMENT", m.Album.ProducerLine)
|
||||||
|
t.addTag("COPYRIGHT", m.Album.Copyright)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.addTag("ARTIST", m.Artists)
|
||||||
|
t.addTag("TITLE", m.Title)
|
||||||
|
t.addTag("COMPOSER", m.Composers)
|
||||||
|
t.addTag("LYRICIST", m.Lyricists)
|
||||||
|
t.addTag("GENRE", m.Genre)
|
||||||
|
t.addTag("REPLAYGAIN_TRACK_GAIN", m.Gain)
|
||||||
|
t.addTag("ISRC", m.ISRC)
|
||||||
|
t.addTag("BPM", m.BPM)
|
||||||
|
t.addTag("KEY", m.Key)
|
||||||
|
t.addTag("INITIALKEY", m.Key)
|
||||||
|
|
||||||
|
cmtsMeta := t.cmts.Marshal()
|
||||||
|
if t.index > 0 {
|
||||||
|
t.file.Meta[t.index] = &cmtsMeta
|
||||||
|
} else {
|
||||||
|
t.file.Meta = append(t.file.Meta, &cmtsMeta)
|
||||||
|
}
|
||||||
|
|
||||||
|
picture, err := flacpicture.NewFromImageData(flacpicture.PictureTypeFrontCover, "Front cover", m.Cover, "image/jpeg")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pictureMeta := picture.Marshal()
|
||||||
|
t.file.Meta = append(t.file.Meta, &pictureMeta)
|
||||||
|
|
||||||
|
tmpPath := t.path + ".tmp"
|
||||||
|
if err := t.file.Save(tmpPath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return os.Rename(tmpPath, t.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *flacTagger) addTag(name, value string) {
|
||||||
|
if value != "" {
|
||||||
|
t.cmts.Add(name, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractFLACComment(file *flac.File) (*flacvorbis.MetaDataBlockVorbisComment, int) {
|
||||||
|
for idx, meta := range file.Meta {
|
||||||
|
if meta.Type == flac.VorbisComment {
|
||||||
|
cmt, err := flacvorbis.ParseFromMetaDataBlock(*meta)
|
||||||
|
if err == nil {
|
||||||
|
return cmt, idx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, 0
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package tag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/bogem/id3v2/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type id3v2Tagger struct {
|
||||||
|
tag *id3v2.Tag
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *id3v2Tagger) write(m Metadata) error {
|
||||||
|
defer t.tag.Close()
|
||||||
|
|
||||||
|
duration, err := strconv.Atoi(m.Duration)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
length := fmt.Sprintf("%d", duration*1000)
|
||||||
|
|
||||||
|
if m.Album != nil {
|
||||||
|
t.addTag("TRCK", m.TrackNumber)
|
||||||
|
t.addTag("TPE2", m.Album.Artist)
|
||||||
|
t.addTag("TALB", m.Album.Title)
|
||||||
|
t.addTag("TPUB", m.Album.Label)
|
||||||
|
t.addTag("TDOR", m.Album.OriginalReleaseDate)
|
||||||
|
t.addTag("TYER", m.Album.ReleaseDate)
|
||||||
|
t.addTag("COMM", m.Album.ProducerLine)
|
||||||
|
t.addTag("TCOP", m.Album.Copyright)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.addTag("TPE1", m.Artists)
|
||||||
|
t.addTag("TIT2", m.Title)
|
||||||
|
t.addTag("TCOM", m.Composers)
|
||||||
|
t.addTag("TEXT", m.Lyricists)
|
||||||
|
t.addTag("TCON", m.Genre)
|
||||||
|
t.addTag("TLEN", length)
|
||||||
|
t.addTag("TBPM", m.BPM)
|
||||||
|
t.addTag("TKEY", m.Key)
|
||||||
|
t.addTXXX("GAIN", m.Gain)
|
||||||
|
t.addTXXX("ISRC", m.ISRC)
|
||||||
|
|
||||||
|
t.tag.AddAttachedPicture(id3v2.PictureFrame{
|
||||||
|
Encoding: t.tag.DefaultEncoding(),
|
||||||
|
MimeType: "image/jpeg",
|
||||||
|
PictureType: id3v2.PTFrontCover,
|
||||||
|
Description: "Cover",
|
||||||
|
Picture: m.Cover,
|
||||||
|
})
|
||||||
|
|
||||||
|
return t.tag.Save()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *id3v2Tagger) addTag(name, value string) {
|
||||||
|
if value != "" {
|
||||||
|
t.tag.AddTextFrame(name, t.tag.DefaultEncoding(), value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *id3v2Tagger) addTXXX(description, value string) {
|
||||||
|
if value != "" {
|
||||||
|
t.tag.AddUserDefinedTextFrame(id3v2.UserDefinedTextFrame{
|
||||||
|
Encoding: t.tag.DefaultEncoding(),
|
||||||
|
Description: description,
|
||||||
|
Value: value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package tag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"github.com/bogem/id3v2/v2"
|
||||||
|
"github.com/go-flac/flacvorbis/v2"
|
||||||
|
"github.com/go-flac/go-flac/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AlbumMetadata struct {
|
||||||
|
Artist string
|
||||||
|
Title string
|
||||||
|
Label string
|
||||||
|
OriginalReleaseDate string
|
||||||
|
ReleaseDate string
|
||||||
|
ProducerLine string
|
||||||
|
Copyright string
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTagger(filePath string) (tagger, error) {
|
||||||
|
if path.Ext(filePath) == ".mp3" {
|
||||||
|
tag, err := id3v2.Open(filePath, id3v2.Options{Parse: true})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &id3v2Tagger{tag: tag}, 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)
|
||||||
|
}
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
package tags
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/go-flac/flacpicture/v2"
|
|
||||||
"github.com/go-flac/flacvorbis/v2"
|
|
||||||
"github.com/go-flac/go-flac/v2"
|
|
||||||
"github.com/mathismqn/godeez/internal/deezer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type flacTagger struct {
|
|
||||||
file *flac.File
|
|
||||||
cmts *flacvorbis.MetaDataBlockVorbisComment
|
|
||||||
index int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *flacTagger) addTags(resource deezer.Resource, track *deezer.Track, cover []byte, path, tempo, key, genre string) error {
|
|
||||||
if album, ok := resource.(*deezer.Album); ok {
|
|
||||||
if parts := strings.Split(album.Results.Data.PhysicalReleaseDate, "-"); len(parts) == 3 {
|
|
||||||
album.Results.Data.PhysicalReleaseDate = parts[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
t.addTag("TRACKNUMBER", track.TrackNumber)
|
|
||||||
t.addTag("ALBUMARTIST", album.Results.Data.Artist)
|
|
||||||
t.addTag("ALBUM", album.Results.Data.Title)
|
|
||||||
t.addTag("PUBLISHER", album.Results.Data.Label)
|
|
||||||
t.addTag("ORIGINALDATE", album.Results.Data.OriginalReleaseDate)
|
|
||||||
t.addTag("DATE", album.Results.Data.PhysicalReleaseDate)
|
|
||||||
t.addTag("COMMENT", album.Results.Data.ProducerLine)
|
|
||||||
t.addTag("COPYRIGHT", album.Results.Data.Copyright)
|
|
||||||
}
|
|
||||||
|
|
||||||
t.addTag("ARTIST", strings.Join(track.Contributors.MainArtists, ", "))
|
|
||||||
t.addTag("TITLE", track.GetTitle())
|
|
||||||
t.addTag("COMPOSER", strings.Join(track.Contributors.Composers, ", "))
|
|
||||||
t.addTag("LYRICIST", strings.Join(track.Contributors.Authors, ", "))
|
|
||||||
t.addTag("GENRE", genre)
|
|
||||||
t.addTag("REPLAYGAIN_TRACK_GAIN", track.Gain)
|
|
||||||
t.addTag("ISRC", track.ISRC)
|
|
||||||
t.addTag("BPM", tempo)
|
|
||||||
t.addTag("KEY", key)
|
|
||||||
t.addTag("INITIALKEY", key)
|
|
||||||
|
|
||||||
cmtsMeta := t.cmts.Marshal()
|
|
||||||
if t.index > 0 {
|
|
||||||
t.file.Meta[t.index] = &cmtsMeta
|
|
||||||
} else {
|
|
||||||
t.file.Meta = append(t.file.Meta, &cmtsMeta)
|
|
||||||
}
|
|
||||||
|
|
||||||
picture, err := flacpicture.NewFromImageData(flacpicture.PictureTypeFrontCover, "Front cover", cover, "image/jpeg")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
pictureMeta := picture.Marshal()
|
|
||||||
t.file.Meta = append(t.file.Meta, &pictureMeta)
|
|
||||||
|
|
||||||
tmpPath := path + ".tmp"
|
|
||||||
if err := t.file.Save(tmpPath); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return os.Rename(tmpPath, path)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *flacTagger) addTag(name, value string) {
|
|
||||||
if value != "" {
|
|
||||||
t.cmts.Add(name, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func extractFLACComment(file *flac.File) (*flacvorbis.MetaDataBlockVorbisComment, int) {
|
|
||||||
for idx, meta := range file.Meta {
|
|
||||||
if meta.Type == flac.VorbisComment {
|
|
||||||
cmt, err := flacvorbis.ParseFromMetaDataBlock(*meta)
|
|
||||||
if err == nil {
|
|
||||||
return cmt, idx
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, 0
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
package tags
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/bogem/id3v2/v2"
|
|
||||||
"github.com/mathismqn/godeez/internal/deezer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type id3v2Tagger struct {
|
|
||||||
tag *id3v2.Tag
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *id3v2Tagger) addTags(resource deezer.Resource, track *deezer.Track, cover []byte, path, tempo, key, genre string) error {
|
|
||||||
defer t.tag.Close()
|
|
||||||
|
|
||||||
duration, err := strconv.Atoi(track.Duration)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
track.Duration = fmt.Sprintf("%d", duration*1000)
|
|
||||||
|
|
||||||
if album, ok := resource.(*deezer.Album); ok {
|
|
||||||
t.addTag("TRCK", track.TrackNumber)
|
|
||||||
t.addTag("TPE2", album.Results.Data.Artist)
|
|
||||||
t.addTag("TALB", album.Results.Data.Title)
|
|
||||||
t.addTag("TPUB", album.Results.Data.Label)
|
|
||||||
t.addTag("TDOR", album.Results.Data.OriginalReleaseDate)
|
|
||||||
t.addTag("TYER", album.Results.Data.PhysicalReleaseDate)
|
|
||||||
t.addTag("COMM", album.Results.Data.ProducerLine)
|
|
||||||
t.addTag("TCOP", album.Results.Data.Copyright)
|
|
||||||
}
|
|
||||||
|
|
||||||
t.addTag("TPE1", strings.Join(track.Contributors.MainArtists, ", "))
|
|
||||||
t.addTag("TIT2", track.GetTitle())
|
|
||||||
t.addTag("TCOM", strings.Join(track.Contributors.Composers, ", "))
|
|
||||||
t.addTag("TEXT", strings.Join(track.Contributors.Authors, ", "))
|
|
||||||
t.addTag("TCON", genre)
|
|
||||||
t.addTag("TLEN", track.Duration)
|
|
||||||
t.addTag("TBPM", tempo)
|
|
||||||
t.addTag("TKEY", key)
|
|
||||||
t.addTXXX("GAIN", track.Gain)
|
|
||||||
t.addTXXX("ISRC", track.ISRC)
|
|
||||||
|
|
||||||
t.tag.AddAttachedPicture(id3v2.PictureFrame{
|
|
||||||
Encoding: t.tag.DefaultEncoding(),
|
|
||||||
MimeType: "image/jpeg",
|
|
||||||
PictureType: id3v2.PTFrontCover,
|
|
||||||
Description: "Cover",
|
|
||||||
Picture: cover,
|
|
||||||
})
|
|
||||||
|
|
||||||
return t.tag.Save()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *id3v2Tagger) addTag(name, value string) {
|
|
||||||
if value != "" {
|
|
||||||
t.tag.AddTextFrame(name, t.tag.DefaultEncoding(), value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *id3v2Tagger) addTXXX(description, value string) {
|
|
||||||
if value != "" {
|
|
||||||
t.tag.AddUserDefinedTextFrame(id3v2.UserDefinedTextFrame{
|
|
||||||
Encoding: t.tag.DefaultEncoding(),
|
|
||||||
Description: description,
|
|
||||||
Value: value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
package tags
|
|
||||||
|
|
||||||
import (
|
|
||||||
"path"
|
|
||||||
|
|
||||||
"github.com/bogem/id3v2/v2"
|
|
||||||
"github.com/go-flac/flacvorbis/v2"
|
|
||||||
"github.com/go-flac/go-flac/v2"
|
|
||||||
"github.com/mathismqn/godeez/internal/deezer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type tagger interface {
|
|
||||||
addTags(resource deezer.Resource, track *deezer.Track, cover []byte, filePath, tempo, key, genre string) error
|
|
||||||
}
|
|
||||||
|
|
||||||
func newTagger(filePath string) (tagger, error) {
|
|
||||||
if path.Ext(filePath) == ".mp3" {
|
|
||||||
tag, err := id3v2.Open(filePath, id3v2.Options{Parse: true})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &id3v2Tagger{tag: tag}, 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}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func AddTags(resource deezer.Resource, track *deezer.Track, cover []byte, filePath, tempo, key, genre string) error {
|
|
||||||
t, err := newTagger(filePath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return t.addTags(resource, track, cover, filePath, tempo, key, genre)
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user