docs: document packages, exported API and non-obvious logic

This commit is contained in:
Mathis Maquenne
2026-08-06 13:04:10 +02:00
parent 5dfd832d0d
commit b1ad9b4904
41 changed files with 757 additions and 7 deletions
+24
View File
@@ -15,6 +15,15 @@ import (
"github.com/PuerkitoBio/goquery"
)
// songbpm.com publishes the tempo and key as prose rather than as structured
// data, so these match the surrounding sentence instead of a CSS selector.
// The attribute wildcards absorb the utility classes the site regenerates on
// every deploy, but the wording itself is load bearing: if the sentence
// changes, the lookup starts returning no data. The double space in modeRegex
// is present in the real markup and is not a typo.
//
// The key pattern accepts both the typographic accidentals the page renders
// and their ASCII equivalents, since which one appears varies by track.
var (
bpmRegex = regexp.MustCompile(`tempo of <span[^>]*>(\d+) BPM`)
keyRegex = regexp.MustCompile(`with a <span[^>]*>([A-G](?:♯|#|♭|b)?(?:/[A-G](?:♯|#|♭|b)?)?)</span> key`)
@@ -35,6 +44,14 @@ func fetchBPM(ctx context.Context, httpClient *http.Client, artist, title, durat
return parseBPM(html)
}
// findTrackURL searches songbpm.com and returns the page for the track.
//
// Artist and title alone are not enough to identify a track, since the search
// happily returns remixes, live versions and covers under the same names.
// Duration is used as the tiebreaker, with a couple of seconds of tolerance
// to absorb the disagreement between Deezer's rounding and songbpm's. No
// match within tolerance is treated as not found rather than guessed at,
// because a wrong BPM is worse than a missing one.
func findTrackURL(ctx context.Context, httpClient *http.Client, artist, title, duration string) (string, error) {
const rootURL = "https://songbpm.com"
@@ -133,6 +150,13 @@ func fetchBPMPage(ctx context.Context, httpClient *http.Client, pageURL string)
return string(body), nil
}
// parseBPM extracts the tempo and musical key from a track page.
//
// All three patterns must match: a page with a tempo but no key is treated as
// no data, since a half filled tag is not worth writing. Enharmonic keys are
// published as pairs like "C#/Db" and only the first spelling is kept, the
// accidentals are folded to ASCII for tag compatibility, and a minor mode is
// encoded with a trailing "m" to match the convention DJ software expects.
func parseBPM(html string) (bpmKey, error) {
bpmMatch := bpmRegex.FindStringSubmatch(html)
keyMatch := keyRegex.FindStringSubmatch(html)