feat(song): add FLAC tags
This commit is contained in:
+2
-1
@@ -7,6 +7,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/mathismqn/godeez/internal/deezer"
|
"github.com/mathismqn/godeez/internal/deezer"
|
||||||
|
"github.com/mathismqn/godeez/internal/tags"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -102,7 +103,7 @@ var downloadCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
fmt.Printf("\r Downloading %s... DONE\n", songTitle)
|
fmt.Printf("\r Downloading %s... DONE\n", songTitle)
|
||||||
|
|
||||||
if err := deezer.AddTags(album, song, filePath); err != nil {
|
if err := tags.Add(album, song, filePath); err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Error: could not add tags to song: %v\n", err)
|
fmt.Fprintf(os.Stderr, "Error: could not add tags to song: %v\n", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,9 @@ require (
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/bogem/id3v2/v2 v2.1.4
|
github.com/bogem/id3v2/v2 v2.1.4
|
||||||
|
github.com/go-flac/flacpicture/v2 v2.0.2
|
||||||
|
github.com/go-flac/flacvorbis/v2 v2.0.2
|
||||||
|
github.com/go-flac/go-flac/v2 v2.0.1
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
golang.org/x/crypto v0.28.0
|
golang.org/x/crypto v0.28.0
|
||||||
|
|||||||
@@ -9,6 +9,12 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk
|
|||||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||||
|
github.com/go-flac/flacpicture/v2 v2.0.2 h1:HCaJIVZpxnpdWs6G3ECEVRelzqS5xOi1Ba1AGmtXbzE=
|
||||||
|
github.com/go-flac/flacpicture/v2 v2.0.2/go.mod h1:DMZBPWPAmdLqNhqFSy5ZBs9wyBzOekXutGfP7/TFCuo=
|
||||||
|
github.com/go-flac/flacvorbis/v2 v2.0.2 h1:xCL3OhxrxWkHrbWUBvGNe+6FQ03yLmBbz0v5z4V2PoQ=
|
||||||
|
github.com/go-flac/flacvorbis/v2 v2.0.2/go.mod h1:SwTB5gs13VaM/N7rstwPoUsPibiMKklgwybYP9dYo2g=
|
||||||
|
github.com/go-flac/go-flac/v2 v2.0.1 h1:1zilNkbmmpK9DLsz2NbjLHG8avOmthYqUfVc9YKB/Ps=
|
||||||
|
github.com/go-flac/go-flac/v2 v2.0.1/go.mod h1:hvgeR2hElLbwk0Q1/vMazIDmIc2LAFSd9Bx/Fk6ViKo=
|
||||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||||
|
|||||||
@@ -51,3 +51,18 @@ func GetAlbumData(id string) (*Album, error) {
|
|||||||
|
|
||||||
return &album, nil
|
return &album, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *Album) GetCoverImage() ([]byte, error) {
|
||||||
|
url := fmt.Sprintf("https://e-cdn-images.dzcdn.net/images/cover/%s/500x500-000000-80-0-0.jpg", a.Data.CoverID)
|
||||||
|
resp, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
return io.ReadAll(resp.Body)
|
||||||
|
}
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ type Song struct {
|
|||||||
Duration string `json:"DURATION"`
|
Duration string `json:"DURATION"`
|
||||||
Gain string `json:"GAIN"`
|
Gain string `json:"GAIN"`
|
||||||
ISRC string `json:"ISRC"`
|
ISRC string `json:"ISRC"`
|
||||||
ExplicitLyrics string `json:"EXPLICIT_LYRICS"`
|
|
||||||
TrackNumber string `json:"TRACK_NUMBER"`
|
TrackNumber string `json:"TRACK_NUMBER"`
|
||||||
TrackToken string `json:"TRACK_TOKEN"`
|
TrackToken string `json:"TRACK_TOKEN"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,101 +0,0 @@
|
|||||||
package deezer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"path"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/bogem/id3v2/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
func AddTags(album *Album, song *Song, pathFile string) error {
|
|
||||||
cover, err := getCoverImage(album.Data.CoverID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ext := path.Ext(pathFile)
|
|
||||||
if ext == ".mp3" {
|
|
||||||
tag, err := id3v2.Open(pathFile, id3v2.Options{Parse: true})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer tag.Close()
|
|
||||||
|
|
||||||
var year string
|
|
||||||
duration, _ := strconv.Atoi(song.Duration)
|
|
||||||
dateParts := strings.Split(album.Data.PhysicalReleaseDate, "-")
|
|
||||||
if len(dateParts) == 3 {
|
|
||||||
year = dateParts[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
addTag(tag, "TALB", album.Data.Name)
|
|
||||||
addTag(tag, "TPE2", album.Data.Artist)
|
|
||||||
addTag(tag, "TPUB", album.Data.Label)
|
|
||||||
addTag(tag, "TDOR", album.Data.OriginalReleaseDate)
|
|
||||||
addTag(tag, "COMM", album.Data.ProducerLine)
|
|
||||||
if year != "" {
|
|
||||||
addTag(tag, "TYER", year)
|
|
||||||
}
|
|
||||||
|
|
||||||
addTag(tag, "TIT2", song.Title)
|
|
||||||
addTag(tag, "TPE1", strings.Join(song.Contributors.MainArtists, " / "))
|
|
||||||
addTag(tag, "TCOM", strings.Join(song.Contributors.Composers, " / "))
|
|
||||||
addTag(tag, "TEXT", strings.Join(song.Contributors.Authors, " / "))
|
|
||||||
addTag(tag, "TRCK", song.TrackNumber)
|
|
||||||
if duration > 0 {
|
|
||||||
addTag(tag, "TLEN", fmt.Sprintf("%d", duration*1000))
|
|
||||||
}
|
|
||||||
addTXXXTag(tag, "GAIN", song.Gain)
|
|
||||||
addTXXXTag(tag, "ISRC", song.ISRC)
|
|
||||||
addTXXXTag(tag, "Explicit lyrics", song.ExplicitLyrics)
|
|
||||||
|
|
||||||
frame := id3v2.PictureFrame{
|
|
||||||
Encoding: tag.DefaultEncoding(),
|
|
||||||
MimeType: "image/jpeg",
|
|
||||||
PictureType: id3v2.PTFrontCover,
|
|
||||||
Description: "Cover",
|
|
||||||
Picture: cover,
|
|
||||||
}
|
|
||||||
tag.AddAttachedPicture(frame)
|
|
||||||
|
|
||||||
return tag.Save()
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getCoverImage(id string) ([]byte, error) {
|
|
||||||
url := fmt.Sprintf("https://e-cdn-images.dzcdn.net/images/cover/%s/500x500-000000-80-0-0.jpg", id)
|
|
||||||
resp, err := http.Get(url)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
return io.ReadAll(resp.Body)
|
|
||||||
}
|
|
||||||
|
|
||||||
func addTag(tag *id3v2.Tag, name, value string) {
|
|
||||||
if value != "" {
|
|
||||||
tag.AddTextFrame(name, tag.DefaultEncoding(), value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func addTXXXTag(tag *id3v2.Tag, description, value string) {
|
|
||||||
if value != "" {
|
|
||||||
udf := id3v2.UserDefinedTextFrame{
|
|
||||||
Encoding: tag.DefaultEncoding(),
|
|
||||||
Description: description,
|
|
||||||
Value: value,
|
|
||||||
}
|
|
||||||
tag.AddUserDefinedTextFrame(udf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
package flac
|
||||||
|
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AddTags(album *deezer.Album, song *deezer.Song, cover []byte, path string) error {
|
||||||
|
file, err := flac.ParseFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cmts, idx, err := extractFLACComment(file)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if cmts == nil && idx > 0 {
|
||||||
|
cmts = flacvorbis.New()
|
||||||
|
}
|
||||||
|
|
||||||
|
addTag(cmts, "ALBUM", album.Data.Name)
|
||||||
|
addTag(cmts, "ALBUMARTIST", album.Data.Artist)
|
||||||
|
addTag(cmts, "PUBLISHER", album.Data.Label)
|
||||||
|
addTag(cmts, "ORIGINALDATE", album.Data.OriginalReleaseDate)
|
||||||
|
addTag(cmts, "DATE", album.Data.PhysicalReleaseDate)
|
||||||
|
addTag(cmts, "COMMENT", album.Data.ProducerLine)
|
||||||
|
|
||||||
|
addTag(cmts, "TITLE", song.Title)
|
||||||
|
addTag(cmts, "ARTIST", strings.Join(song.Contributors.MainArtists, " / "))
|
||||||
|
addTag(cmts, "COMPOSER", strings.Join(song.Contributors.Composers, " / "))
|
||||||
|
addTag(cmts, "LYRICIST", strings.Join(song.Contributors.Authors, " / "))
|
||||||
|
addTag(cmts, "TRACKNUMBER", song.TrackNumber)
|
||||||
|
addTag(cmts, "REPLAYGAIN_TRACK_GAIN", song.Gain)
|
||||||
|
addTag(cmts, "ISRC", song.ISRC)
|
||||||
|
|
||||||
|
cmtsmeta := cmts.Marshal()
|
||||||
|
if idx > 0 {
|
||||||
|
file.Meta[idx] = &cmtsmeta
|
||||||
|
} else {
|
||||||
|
file.Meta = append(file.Meta, &cmtsmeta)
|
||||||
|
}
|
||||||
|
|
||||||
|
picture, err := flacpicture.NewFromImageData(flacpicture.PictureTypeFrontCover, "Front cover", cover, "image/jpeg")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
picturemeta := picture.Marshal()
|
||||||
|
file.Meta = append(file.Meta, &picturemeta)
|
||||||
|
|
||||||
|
return saveTags(file, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addTag(cmts *flacvorbis.MetaDataBlockVorbisComment, name, value string) {
|
||||||
|
if value != "" {
|
||||||
|
cmts.Add(name, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractFLACComment(file *flac.File) (*flacvorbis.MetaDataBlockVorbisComment, int, error) {
|
||||||
|
var cmt *flacvorbis.MetaDataBlockVorbisComment
|
||||||
|
var cmtIdx int
|
||||||
|
var err error
|
||||||
|
for idx, meta := range file.Meta {
|
||||||
|
if meta.Type == flac.VorbisComment {
|
||||||
|
cmt, err = flacvorbis.ParseFromMetaDataBlock(*meta)
|
||||||
|
cmtIdx = idx
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmt, cmtIdx, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func saveTags(file *flac.File, path string) error {
|
||||||
|
tempPath := path + ".tmp"
|
||||||
|
file.Save(tempPath)
|
||||||
|
|
||||||
|
return os.Rename(tempPath, path)
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package id3v2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/bogem/id3v2/v2"
|
||||||
|
"github.com/mathismqn/godeez/internal/deezer"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AddTags(album *deezer.Album, song *deezer.Song, cover []byte, path string) error {
|
||||||
|
tag, err := id3v2.Open(path, id3v2.Options{Parse: true})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer tag.Close()
|
||||||
|
|
||||||
|
addTag(tag, "TALB", album.Data.Name)
|
||||||
|
addTag(tag, "TPE2", album.Data.Artist)
|
||||||
|
addTag(tag, "TPUB", album.Data.Label)
|
||||||
|
addTag(tag, "TDOR", album.Data.OriginalReleaseDate)
|
||||||
|
addTag(tag, "TYER", album.Data.PhysicalReleaseDate)
|
||||||
|
addTag(tag, "COMM", album.Data.ProducerLine)
|
||||||
|
|
||||||
|
addTag(tag, "TIT2", song.Title)
|
||||||
|
addTag(tag, "TPE1", strings.Join(song.Contributors.MainArtists, " / "))
|
||||||
|
addTag(tag, "TCOM", strings.Join(song.Contributors.Composers, " / "))
|
||||||
|
addTag(tag, "TEXT", strings.Join(song.Contributors.Authors, " / "))
|
||||||
|
addTag(tag, "TRCK", song.TrackNumber)
|
||||||
|
addTag(tag, "TLEN", song.Duration)
|
||||||
|
addTXXXTag(tag, "GAIN", song.Gain)
|
||||||
|
addTXXXTag(tag, "ISRC", song.ISRC)
|
||||||
|
|
||||||
|
frame := id3v2.PictureFrame{
|
||||||
|
Encoding: tag.DefaultEncoding(),
|
||||||
|
MimeType: "image/jpeg",
|
||||||
|
PictureType: id3v2.PTFrontCover,
|
||||||
|
Description: "Cover",
|
||||||
|
Picture: cover,
|
||||||
|
}
|
||||||
|
tag.AddAttachedPicture(frame)
|
||||||
|
|
||||||
|
return tag.Save()
|
||||||
|
}
|
||||||
|
|
||||||
|
func addTag(tag *id3v2.Tag, name, value string) {
|
||||||
|
if value != "" {
|
||||||
|
tag.AddTextFrame(name, tag.DefaultEncoding(), value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func addTXXXTag(tag *id3v2.Tag, description, value string) {
|
||||||
|
if value != "" {
|
||||||
|
udf := id3v2.UserDefinedTextFrame{
|
||||||
|
Encoding: tag.DefaultEncoding(),
|
||||||
|
Description: description,
|
||||||
|
Value: value,
|
||||||
|
}
|
||||||
|
tag.AddUserDefinedTextFrame(udf)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package tags
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/mathismqn/godeez/internal/deezer"
|
||||||
|
"github.com/mathismqn/godeez/internal/tags/flac"
|
||||||
|
"github.com/mathismqn/godeez/internal/tags/id3v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Add(album *deezer.Album, song *deezer.Song, pathFile string) error {
|
||||||
|
cover, err := album.GetCoverImage()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
duration, _ := strconv.Atoi(song.Duration)
|
||||||
|
song.Duration = fmt.Sprintf("%d", duration*1000)
|
||||||
|
dateParts := strings.Split(album.Data.PhysicalReleaseDate, "-")
|
||||||
|
if len(dateParts) == 3 {
|
||||||
|
album.Data.PhysicalReleaseDate = dateParts[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
ext := path.Ext(pathFile)
|
||||||
|
if ext == ".mp3" {
|
||||||
|
return id3v2.AddTags(album, song, cover, pathFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
return flac.AddTags(album, song, cover, pathFile)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user