refactor: move BPM fetching logic into provider package
This commit is contained in:
@@ -12,12 +12,12 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/briandowns/spinner"
|
"github.com/briandowns/spinner"
|
||||||
"github.com/mathismqn/godeez/internal/bpm"
|
|
||||||
"github.com/mathismqn/godeez/internal/config"
|
"github.com/mathismqn/godeez/internal/config"
|
||||||
"github.com/mathismqn/godeez/internal/crypto"
|
"github.com/mathismqn/godeez/internal/crypto"
|
||||||
"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/logger"
|
"github.com/mathismqn/godeez/internal/logger"
|
||||||
|
"github.com/mathismqn/godeez/internal/provider"
|
||||||
"github.com/mathismqn/godeez/internal/store"
|
"github.com/mathismqn/godeez/internal/store"
|
||||||
"github.com/mathismqn/godeez/internal/tags"
|
"github.com/mathismqn/godeez/internal/tags"
|
||||||
)
|
)
|
||||||
@@ -192,19 +192,20 @@ func (c *Client) downloadSong(ctx context.Context, resource deezer.Resource, son
|
|||||||
return nil, SkipError{Path: path}
|
return nil, SkipError{Path: path}
|
||||||
}
|
}
|
||||||
|
|
||||||
var metricsChan chan *bpm.Metrics
|
var bpmChan chan provider.BPMKey
|
||||||
var errChan chan error
|
var errChan chan error
|
||||||
if opts.BPM {
|
if opts.BPM {
|
||||||
metricsChan = make(chan *bpm.Metrics, 1)
|
bpmChan = make(chan provider.BPMKey, 1)
|
||||||
errChan = make(chan error, 1)
|
errChan = make(chan error, 1)
|
||||||
go func() {
|
go func() {
|
||||||
metrics, err := bpm.FetchMetrics(ctx, c.deezerClient.Session.HttpClient, song.Artist, song.Title, song.Duration)
|
p := provider.BPMProvider{}
|
||||||
|
bpmKey, err := p.Fetch(ctx, c.deezerClient.Session.HttpClient, song.Artist, song.Title, song.Duration)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errChan <- err
|
errChan <- err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
metricsChan <- metrics
|
bpmChan <- bpmKey
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,10 +231,10 @@ func (c *Client) downloadSong(ctx context.Context, resource deezer.Resource, son
|
|||||||
warnings = append(warnings, fmt.Sprintf("requested quality '%s' not available, using '%s' instead", opts.Quality, strings.ToLower(mediaFormat)))
|
warnings = append(warnings, fmt.Sprintf("requested quality '%s' not available, using '%s' instead", opts.Quality, strings.ToLower(mediaFormat)))
|
||||||
}
|
}
|
||||||
|
|
||||||
metrics := &bpm.Metrics{}
|
bpmKey := provider.BPMKey{}
|
||||||
if opts.BPM {
|
if opts.BPM {
|
||||||
select {
|
select {
|
||||||
case metrics = <-metricsChan:
|
case bpmKey = <-bpmChan:
|
||||||
case err := <-errChan:
|
case err := <-errChan:
|
||||||
if !errors.Is(err, context.Canceled) {
|
if !errors.Is(err, context.Canceled) {
|
||||||
warnings = append(warnings, fmt.Sprintf("failed to fetch BPM and key: %v", err))
|
warnings = append(warnings, fmt.Sprintf("failed to fetch BPM and key: %v", err))
|
||||||
@@ -246,7 +247,7 @@ func (c *Client) downloadSong(ctx context.Context, resource deezer.Resource, son
|
|||||||
warnings = append(warnings, fmt.Sprintf("failed to fetch cover image: %v", err))
|
warnings = append(warnings, fmt.Sprintf("failed to fetch cover image: %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
warnings = append(warnings, c.finalizeDownload(resource, song, outputPath, mediaFormat, cover, metrics)...)
|
warnings = append(warnings, c.finalizeDownload(resource, song, outputPath, mediaFormat, cover, bpmKey)...)
|
||||||
|
|
||||||
return warnings, nil
|
return warnings, nil
|
||||||
}
|
}
|
||||||
@@ -308,10 +309,10 @@ func (c *Client) streamToFile(ctx context.Context, stream io.ReadCloser, outputP
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) finalizeDownload(resource deezer.Resource, song *deezer.Song, outputPath, mediaFormat string, cover []byte, metrics *bpm.Metrics) []string {
|
func (c *Client) finalizeDownload(resource deezer.Resource, song *deezer.Song, outputPath, mediaFormat string, cover []byte, bpmKey provider.BPMKey) []string {
|
||||||
var warnings []string
|
var warnings []string
|
||||||
|
|
||||||
if err := tags.AddTags(resource, song, cover, outputPath, metrics.BPM, metrics.Key); err != nil {
|
if err := tags.AddTags(resource, song, cover, outputPath, bpmKey.BPM, bpmKey.Key); err != nil {
|
||||||
warnings = append(warnings, fmt.Sprintf("failed to add tags: %v", err))
|
warnings = append(warnings, fmt.Sprintf("failed to add tags: %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package bpm
|
package provider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
neturl "net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -14,30 +14,32 @@ import (
|
|||||||
"github.com/PuerkitoBio/goquery"
|
"github.com/PuerkitoBio/goquery"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Metrics struct {
|
type BPMProvider struct{}
|
||||||
|
|
||||||
|
type BPMKey struct {
|
||||||
BPM string
|
BPM string
|
||||||
Key string
|
Key string
|
||||||
}
|
}
|
||||||
|
|
||||||
func FetchMetrics(ctx context.Context, httpClient *http.Client, artist, title, duration string) (*Metrics, error) {
|
func (p BPMProvider) Fetch(ctx context.Context, httpClient *http.Client, artist, title, duration string) (BPMKey, error) {
|
||||||
url, err := findSongURL(ctx, httpClient, artist, title, duration)
|
url, err := p.findSongURL(ctx, httpClient, artist, title, duration)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return BPMKey{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
html, err := fetchPage(ctx, httpClient, url)
|
html, err := p.fetchPage(ctx, httpClient, url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return BPMKey{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return parseMetrics(html)
|
return p.parse(html)
|
||||||
}
|
}
|
||||||
|
|
||||||
func findSongURL(ctx context.Context, httpClient *http.Client, artist, title, duration string) (string, error) {
|
func (p BPMProvider) findSongURL(ctx context.Context, httpClient *http.Client, artist, title, duration string) (string, error) {
|
||||||
rootUrl := "https://songbpm.com"
|
rootUrl := "https://songbpm.com"
|
||||||
reqUrl := rootUrl + "/searches"
|
reqUrl := rootUrl + "/searches"
|
||||||
|
|
||||||
values := url.Values{}
|
values := neturl.Values{}
|
||||||
values.Add("query", fmt.Sprintf("%s %s", artist, title))
|
values.Add("query", fmt.Sprintf("%s %s", artist, title))
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, "POST", reqUrl, bytes.NewBufferString(values.Encode()))
|
req, err := http.NewRequestWithContext(ctx, "POST", reqUrl, bytes.NewBufferString(values.Encode()))
|
||||||
@@ -90,12 +92,13 @@ func findSongURL(ctx context.Context, httpClient *http.Client, artist, title, du
|
|||||||
}
|
}
|
||||||
|
|
||||||
foundDuration := minutes*60 + seconds
|
foundDuration := minutes*60 + seconds
|
||||||
duration, err := strconv.Atoi(duration)
|
wantDuration, err := strconv.Atoi(duration)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if foundDuration <= (duration-2) || foundDuration >= (duration+2) {
|
const durationToleranceSec = 2
|
||||||
|
if foundDuration <= (wantDuration-durationToleranceSec) || foundDuration >= (wantDuration+durationToleranceSec) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,7 +115,7 @@ func findSongURL(ctx context.Context, httpClient *http.Client, artist, title, du
|
|||||||
return rootUrl + url, nil
|
return rootUrl + url, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchPage(ctx context.Context, httpClient *http.Client, url string) (string, error) {
|
func (p BPMProvider) fetchPage(ctx context.Context, httpClient *http.Client, url string) (string, error) {
|
||||||
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -136,7 +139,7 @@ func fetchPage(ctx context.Context, httpClient *http.Client, url string) (string
|
|||||||
return string(body), nil
|
return string(body), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseMetrics(html string) (*Metrics, error) {
|
func (p BPMProvider) parse(html string) (BPMKey, error) {
|
||||||
bpmRegex := regexp.MustCompile(`tempo of <span[^>]*>(\d+) BPM`)
|
bpmRegex := regexp.MustCompile(`tempo of <span[^>]*>(\d+) BPM`)
|
||||||
bpmMatch := bpmRegex.FindStringSubmatch(html)
|
bpmMatch := bpmRegex.FindStringSubmatch(html)
|
||||||
|
|
||||||
@@ -147,7 +150,7 @@ func parseMetrics(html string) (*Metrics, error) {
|
|||||||
modeMatch := modeRegex.FindStringSubmatch(html)
|
modeMatch := modeRegex.FindStringSubmatch(html)
|
||||||
|
|
||||||
if len(bpmMatch) != 2 || len(keyMatch) != 2 || len(modeMatch) != 2 {
|
if len(bpmMatch) != 2 || len(keyMatch) != 2 || len(modeMatch) != 2 {
|
||||||
return nil, fmt.Errorf("no data found")
|
return BPMKey{}, fmt.Errorf("no data found")
|
||||||
}
|
}
|
||||||
|
|
||||||
isMinor := false
|
isMinor := false
|
||||||
@@ -169,8 +172,9 @@ func parseMetrics(html string) (*Metrics, error) {
|
|||||||
key += "m"
|
key += "m"
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Metrics{
|
return BPMKey{
|
||||||
BPM: bpm,
|
BPM: bpm,
|
||||||
Key: key,
|
Key: key,
|
||||||
}, nil
|
}, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user