Files
SpotiFLAC-Mobile/go_backend/lyrics_matching.go
T

136 lines
3.4 KiB
Go

package gobackend
import (
"math"
"regexp"
"strings"
)
var simplifyTrackNamePatterns = func() []*regexp.Regexp {
patterns := []string{
`\s*\(feat\.?.*?\)`,
`\s*\(ft\.?.*?\)`,
`\s*\(featuring.*?\)`,
`\s*\(with.*?\)`,
`\s*-\s*Remaster(ed)?.*$`,
`\s*-\s*\d{4}\s*Remaster.*$`,
`\s*\(Remaster(ed)?.*?\)`,
`\s*\(Deluxe.*?\)`,
`\s*\(Bonus.*?\)`,
`\s*\(Live.*?\)`,
`\s*\(Acoustic.*?\)`,
`\s*\(Radio Edit\)`,
`\s*\(Single Version\)`,
}
compiled := make([]*regexp.Regexp, len(patterns))
for i, pattern := range patterns {
compiled[i] = regexp.MustCompile("(?i)" + pattern)
}
return compiled
}()
func simplifyTrackName(name string) string {
result := name
for _, re := range simplifyTrackNamePatterns {
result = re.ReplaceAllString(result, "")
}
result = strings.TrimSpace(result)
if result == "" {
return result
}
if loose := normalizeLooseTitle(result); loose != "" {
return loose
}
return result
}
func normalizedLyricsSearchTitle(name string) string {
return strings.ToLower(strings.TrimSpace(simplifyTrackName(name)))
}
func containsWordSequence(value, sequence string) bool {
valueWords := strings.Fields(value)
sequenceWords := strings.Fields(sequence)
if len(valueWords) == 0 || len(sequenceWords) == 0 || len(sequenceWords) > len(valueWords) {
return false
}
for start := 0; start <= len(valueWords)-len(sequenceWords); start++ {
matches := true
for offset := range sequenceWords {
if valueWords[start+offset] != sequenceWords[offset] {
matches = false
break
}
}
if matches {
return true
}
}
return false
}
func lyricsSearchTitlesMatch(candidateTrack, trackName string, allowDecoratedCandidate bool) bool {
expected := normalizedLyricsSearchTitle(trackName)
candidate := normalizedLyricsSearchTitle(candidateTrack)
if expected == "" || candidate == "" {
return false
}
if candidate == expected {
return true
}
return allowDecoratedCandidate && containsWordSequence(candidate, expected)
}
func lyricsSearchArtistsMatch(candidateArtist, artistName string) bool {
expected := normalizeLooseArtistName(normalizeArtistName(artistName))
if expected == "" {
return true
}
candidate := normalizeLooseArtistName(normalizeArtistName(candidateArtist))
if candidate == "" {
return false
}
return candidate == expected || sameWordsUnordered(candidate, expected)
}
func lyricsSearchDurationMatches(candidateDuration, durationSec float64) bool {
if candidateDuration <= 0 || durationSec <= 0 {
return true
}
return math.Abs(candidateDuration-durationSec) <= durationToleranceSec
}
func lyricsSearchArtistAppearsInTitle(candidateTrack, artistName string) bool {
expectedArtist := normalizeLooseArtistName(normalizeArtistName(artistName))
candidateTitle := normalizeLooseArtistName(candidateTrack)
return expectedArtist != "" &&
candidateTitle != "" &&
containsWordSequence(candidateTitle, expectedArtist)
}
func normalizeArtistName(name string) string {
separators := []string{", ", "; ", " & ", " feat. ", " ft. ", " featuring ", " with "}
result := name
for _, sep := range separators {
if idx := strings.Index(strings.ToLower(result), strings.ToLower(sep)); idx > 0 {
result = result[:idx]
break
}
}
return strings.TrimSpace(result)
}
func isLikelyInstrumentalTrack(name string) bool {
trimmed := strings.TrimSpace(name)
if trimmed == "" {
return false
}
return instrumentalTrackPattern.MatchString(trimmed)
}