feat(tags): add tempo and key

This commit is contained in:
Mathis Maquenne
2025-04-26 00:01:19 +02:00
parent 6b8b17aacb
commit d0599f654e
8 changed files with 127 additions and 6 deletions
+3
View File
@@ -0,0 +1,3 @@
.DS_Store
build.sh
builds/
+9 -1
View File
@@ -147,7 +147,15 @@ func downloadContent(contentType string, args []string) {
}
fmt.Printf("\r Downloading %s... DONE\n", songTitle)
if err := tags.AddTags(resource, song, filePath); err != nil {
tempo, key, err := song.GetTempoAndKey()
if err != nil {
fmt.Fprintf(os.Stderr, "Error: could not get tempo and key: %v\n", err)
} else {
fmt.Printf(" Tempo: %s\n", tempo)
fmt.Printf(" Key: %s\n", key)
}
if err := tags.AddTags(resource, song, filePath, tempo, key); err != nil {
fmt.Fprintf(os.Stderr, "Error: could not add tags to song: %v\n", err)
}
}
+3
View File
@@ -8,6 +8,7 @@ require (
)
require (
github.com/andybalholm/cascadia v1.3.2 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
@@ -22,6 +23,7 @@ require (
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/net v0.29.0 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/text v0.19.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
@@ -29,6 +31,7 @@ require (
)
require (
github.com/PuerkitoBio/goquery v1.10.0
github.com/bogem/id3v2/v2 v2.1.4
github.com/flytam/filenamify v1.2.0
github.com/go-flac/flacpicture/v2 v2.0.2
+17
View File
@@ -1,3 +1,7 @@
github.com/PuerkitoBio/goquery v1.10.0 h1:6fiXdLuUvYs2OJSvNRqlNPoBm6YABE226xrbavY5Wv4=
github.com/PuerkitoBio/goquery v1.10.0/go.mod h1:TjZZl68Q3eGHNBA8CWaxAN7rOU1EbDz3CWuolcO5Yu4=
github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
github.com/bogem/id3v2/v2 v2.1.4 h1:CEwe+lS2p6dd9UZRlPc1zbFNIha2mb2qzT1cCEoNWoI=
github.com/bogem/id3v2/v2 v2.1.4/go.mod h1:l+gR8MZ6rc9ryPTPkX77smS5Me/36gxkMgDayZ9G1vY=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
@@ -79,29 +83,42 @@ golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
+83
View File
@@ -6,7 +6,11 @@ import (
"fmt"
"io"
"net/http"
"net/url"
"strconv"
"strings"
"github.com/PuerkitoBio/goquery"
"github.com/mathismqn/godeez/internal/config"
)
@@ -93,3 +97,82 @@ func (s *Song) GetCoverImage() ([]byte, error) {
return io.ReadAll(resp.Body)
}
func (s *Song) GetTempoAndKey() (string, string, error) {
reqUrl := "https://songbpm.com/searches"
values := url.Values{}
values.Add("query", fmt.Sprintf("%s %s %s", s.Artist, s.Title, s.Version))
client := &http.Client{}
req, err := http.NewRequest("POST", reqUrl, bytes.NewBufferString(values.Encode()))
if err != nil {
return "", "", err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Origin", "https://songbpm.com")
resp, err := client.Do(req)
if err != nil {
return "", "", err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return "", "", fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
doc, err := goquery.NewDocumentFromReader(resp.Body)
if err != nil {
return "", "", err
}
var key, bpm string
var found bool
doc.Find("a.flex.flex-col").Each(func(i int, selection *goquery.Selection) {
if found {
return
}
if strings.Contains(selection.Text(), s.Title) && strings.Contains(selection.Text(), s.Artist) {
foundArtist := selection.Find("p.text-sm.font-light.uppercase").Text()
foundTitle := selection.Find("p.pr-2.text-lg").Text()
if strings.Contains(strings.ToLower(foundArtist), strings.ToLower(s.Artist)) && strings.Contains(strings.ToLower(foundTitle), strings.ToLower(s.Title)) {
durationStr := strings.TrimSpace(selection.Find("div.flex-1.flex-col.items-center").Eq(1).Find("span.text-2xl").Text())
parts := strings.Split(durationStr, ":")
if len(parts) != 2 {
return
}
minutes, err := strconv.Atoi(parts[0])
if err != nil {
fmt.Println(err)
return
}
seconds, err := strconv.Atoi(parts[1])
if err != nil {
return
}
foundDuration := minutes*60 + seconds
duration, err := strconv.Atoi(s.Duration)
if err != nil {
return
}
if foundDuration > (duration-2) || foundDuration < (duration+2) {
key = selection.Find("div.flex-1.flex-col.items-center").Eq(0).Find("span.text-2xl").Text()
bpm = selection.Find("div.flex-1.flex-col.items-center").Eq(2).Find("span.text-2xl").Text()
found = true
}
}
}
})
if !found {
return "", "", fmt.Errorf("no data found")
}
return bpm, key, nil
}
+5 -1
View File
@@ -16,7 +16,7 @@ type FLACTagger struct {
Index int
}
func (t *FLACTagger) AddTags(resource deezer.Resource, song *deezer.Song, cover []byte, path string) error {
func (t *FLACTagger) AddTags(resource deezer.Resource, song *deezer.Song, cover []byte, path, tempo, key string) error {
if album, ok := resource.(*deezer.Album); ok {
dateParts := strings.Split(album.Data.PhysicalReleaseDate, "-")
if len(dateParts) == 3 {
@@ -39,6 +39,10 @@ func (t *FLACTagger) AddTags(resource deezer.Resource, song *deezer.Song, cover
t.addTag("REPLAYGAIN_TRACK_GAIN", song.Gain)
t.addTag("ISRC", song.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
+4 -1
View File
@@ -13,7 +13,7 @@ type ID3v2Tagger struct {
Tag *id3v2.Tag
}
func (t *ID3v2Tagger) AddTags(resource deezer.Resource, song *deezer.Song, cover []byte, path string) error {
func (t *ID3v2Tagger) AddTags(resource deezer.Resource, song *deezer.Song, cover []byte, path, tempo, key string) error {
defer t.Tag.Close()
duration, _ := strconv.Atoi(song.Duration)
@@ -37,6 +37,9 @@ func (t *ID3v2Tagger) AddTags(resource deezer.Resource, song *deezer.Song, cover
t.addTXXXTag("GAIN", song.Gain)
t.addTXXXTag("ISRC", song.ISRC)
t.addTag("TBPM", tempo)
t.addTag("TKEY", key)
frame := id3v2.PictureFrame{
Encoding: t.Tag.DefaultEncoding(),
MimeType: "image/jpeg",
+3 -3
View File
@@ -10,7 +10,7 @@ import (
)
type Tagger interface {
AddTags(resource deezer.Resource, song *deezer.Song, cover []byte, path string) error
AddTags(resource deezer.Resource, song *deezer.Song, cover []byte, path, tempo, key string) error
}
func NewTagger(filePath string) (Tagger, error) {
@@ -38,7 +38,7 @@ func NewTagger(filePath string) (Tagger, error) {
return &FLACTagger{File: file, Cmts: cmts, Index: idx}, nil
}
func AddTags(resource deezer.Resource, song *deezer.Song, filePath string) error {
func AddTags(resource deezer.Resource, song *deezer.Song, filePath, tempo, key string) error {
tagger, err := NewTagger(filePath)
if err != nil {
return err
@@ -48,5 +48,5 @@ func AddTags(resource deezer.Resource, song *deezer.Song, filePath string) error
return err
}
return tagger.AddTags(resource, song, cover, filePath)
return tagger.AddTags(resource, song, cover, filePath, tempo, key)
}