feat: add album management endpoints (sections 2.1, 2.2, 2.3)
This commit is contained in:
@@ -0,0 +1,200 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/fujin/music-agregator/internal/database"
|
||||
"github.com/fujin/music-agregator/internal/indexer"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type RankedSearchResult struct {
|
||||
indexer.SearchResult
|
||||
Quality string `json:"quality"`
|
||||
Score float64 `json:"score"`
|
||||
}
|
||||
|
||||
type AlbumSearchResult struct {
|
||||
AlbumID string `json:"album_id"`
|
||||
AlbumTitle string `json:"album_title"`
|
||||
ArtistName string `json:"artist_name"`
|
||||
Results []RankedSearchResult `json:"results"`
|
||||
TotalResults int `json:"total_results"`
|
||||
}
|
||||
|
||||
func SearchAlbum(
|
||||
ctx context.Context,
|
||||
albumID uuid.UUID,
|
||||
db *database.DB,
|
||||
indexerService *IndexerService,
|
||||
) (*AlbumSearchResult, error) {
|
||||
album, err := db.GetAlbumDetailByID(ctx, albumID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var year *uint32
|
||||
if album.ReleaseDate != nil {
|
||||
y := uint32(album.ReleaseDate.Year())
|
||||
year = &y
|
||||
}
|
||||
|
||||
criteria := &indexer.MusicSearchCriteria{
|
||||
Artist: album.ArtistName,
|
||||
Album: &album.Title,
|
||||
Year: year,
|
||||
Limit: 100,
|
||||
Offset: 0,
|
||||
}
|
||||
|
||||
results, err := indexerService.Search(ctx, criteria, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var rankedResults []RankedSearchResult
|
||||
for _, r := range results {
|
||||
blocked, _ := db.IsBlocklisted(ctx, r.Title, r.Infohash)
|
||||
if blocked {
|
||||
continue
|
||||
}
|
||||
|
||||
quality := detectQuality(r.Title)
|
||||
score := calculateScore(quality, r.Seeders)
|
||||
|
||||
rankedResults = append(rankedResults, RankedSearchResult{
|
||||
SearchResult: r,
|
||||
Quality: quality,
|
||||
Score: score,
|
||||
})
|
||||
}
|
||||
|
||||
sort.Slice(rankedResults, func(i, j int) bool {
|
||||
return rankedResults[i].Score > rankedResults[j].Score
|
||||
})
|
||||
|
||||
return &AlbumSearchResult{
|
||||
AlbumID: albumID.String(),
|
||||
AlbumTitle: album.Title,
|
||||
ArtistName: album.ArtistName,
|
||||
Results: rankedResults,
|
||||
TotalResults: len(rankedResults),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func detectQuality(title string) string {
|
||||
titleLower := strings.ToLower(title)
|
||||
|
||||
if strings.Contains(titleLower, "flac") || strings.Contains(titleLower, "lossless") {
|
||||
return "FLAC"
|
||||
}
|
||||
if strings.Contains(titleLower, "24bit") || strings.Contains(titleLower, "24-bit") || strings.Contains(titleLower, "hi-res") {
|
||||
return "FLAC-24bit"
|
||||
}
|
||||
if strings.Contains(titleLower, "320") || strings.Contains(titleLower, "mp3-320") {
|
||||
return "MP3-320"
|
||||
}
|
||||
if strings.Contains(titleLower, "v0") || strings.Contains(titleLower, "vbr") {
|
||||
return "MP3-VBR"
|
||||
}
|
||||
if strings.Contains(titleLower, "mp3") {
|
||||
return "MP3"
|
||||
}
|
||||
if strings.Contains(titleLower, "aac") {
|
||||
return "AAC"
|
||||
}
|
||||
if strings.Contains(titleLower, "ogg") {
|
||||
return "OGG"
|
||||
}
|
||||
return "Unknown"
|
||||
}
|
||||
|
||||
func calculateScore(quality string, seeders *int) float64 {
|
||||
var score float64
|
||||
|
||||
switch quality {
|
||||
case "FLAC-24bit":
|
||||
score = 1000
|
||||
case "FLAC":
|
||||
score = 900
|
||||
case "MP3-320":
|
||||
score = 700
|
||||
case "MP3-VBR":
|
||||
score = 600
|
||||
case "MP3":
|
||||
score = 500
|
||||
case "AAC":
|
||||
score = 400
|
||||
case "OGG":
|
||||
score = 350
|
||||
default:
|
||||
score = 100
|
||||
}
|
||||
|
||||
if seeders != nil && *seeders > 0 {
|
||||
score += float64(*seeders) * 0.1
|
||||
if *seeders > 100 {
|
||||
score += 50
|
||||
}
|
||||
}
|
||||
|
||||
return score
|
||||
}
|
||||
|
||||
type ArtistSearchResult struct {
|
||||
ArtistID string `json:"artist_id"`
|
||||
ArtistName string `json:"artist_name"`
|
||||
AlbumsSearched int `json:"albums_searched"`
|
||||
Results []AlbumBriefResult `json:"results"`
|
||||
}
|
||||
|
||||
type AlbumBriefResult struct {
|
||||
AlbumID string `json:"album_id"`
|
||||
AlbumTitle string `json:"album_title"`
|
||||
ResultsCount int `json:"results_count"`
|
||||
}
|
||||
|
||||
func SearchArtistAlbums(
|
||||
ctx context.Context,
|
||||
foreignArtistID string,
|
||||
db *database.DB,
|
||||
indexerService *IndexerService,
|
||||
) (*ArtistSearchResult, error) {
|
||||
artist, err := db.GetArtistMetadataByForeignID(ctx, foreignArtistID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
albums, err := db.GetMonitoredAlbumsByArtist(ctx, artist.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var results []AlbumBriefResult
|
||||
for _, album := range albums {
|
||||
searchResult, err := SearchAlbum(ctx, album.ID, db, indexerService)
|
||||
if err != nil {
|
||||
results = append(results, AlbumBriefResult{
|
||||
AlbumID: album.ID.String(),
|
||||
AlbumTitle: album.Title,
|
||||
ResultsCount: 0,
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
results = append(results, AlbumBriefResult{
|
||||
AlbumID: album.ID.String(),
|
||||
AlbumTitle: album.Title,
|
||||
ResultsCount: searchResult.TotalResults,
|
||||
})
|
||||
}
|
||||
|
||||
return &ArtistSearchResult{
|
||||
ArtistID: foreignArtistID,
|
||||
ArtistName: artist.Name,
|
||||
AlbumsSearched: len(albums),
|
||||
Results: results,
|
||||
}, nil
|
||||
}
|
||||
Reference in New Issue
Block a user