Add GetAlbum RPC with track details and persist metadata on discovery
This commit is contained in:
@@ -14,6 +14,7 @@ type MetadataService struct {
|
|||||||
client metadataPb.MetadataServiceClient
|
client metadataPb.MetadataServiceClient
|
||||||
artists *database.ArtistRepository
|
artists *database.ArtistRepository
|
||||||
albums *database.AlbumRepository
|
albums *database.AlbumRepository
|
||||||
|
tracks *database.TrackRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMetadataService(client metadataPb.MetadataServiceClient, db *database.DB) *MetadataService {
|
func NewMetadataService(client metadataPb.MetadataServiceClient, db *database.DB) *MetadataService {
|
||||||
@@ -21,6 +22,7 @@ func NewMetadataService(client metadataPb.MetadataServiceClient, db *database.DB
|
|||||||
client: client,
|
client: client,
|
||||||
artists: database.NewArtistRepository(db.Pool),
|
artists: database.NewArtistRepository(db.Pool),
|
||||||
albums: database.NewAlbumRepository(db.Pool),
|
albums: database.NewAlbumRepository(db.Pool),
|
||||||
|
tracks: database.NewTrackRepository(db.Pool),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,8 +37,8 @@ func (s *MetadataService) GetAlbum(ctx context.Context, albumID string) (*metada
|
|||||||
album := resp.GetAlbum()
|
album := resp.GetAlbum()
|
||||||
|
|
||||||
if _, err := s.albums.GetByExternalID(ctx, album.GetId()); err != nil {
|
if _, err := s.albums.GetByExternalID(ctx, album.GetId()); err != nil {
|
||||||
s.persistArtist(ctx, album)
|
s.PersistArtist(ctx, album, database.Monitored)
|
||||||
s.persistAlbum(ctx, album)
|
s.PersistAlbum(ctx, album, database.Monitored)
|
||||||
}
|
}
|
||||||
|
|
||||||
return album, nil
|
return album, nil
|
||||||
@@ -52,10 +54,24 @@ func (s *MetadataService) GetArtistAlbums(ctx context.Context, artistExternalID
|
|||||||
return resp.GetAlbums(), nil
|
return resp.GetAlbums(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *MetadataService) GetAlbumTracks(ctx context.Context, albumExternalID string) ([]*metadataPb.Track, error) {
|
||||||
|
resp, err := s.client.GetAlbumTracks(ctx, &metadataPb.GetAlbumTracksRequest{
|
||||||
|
AlbumId: albumExternalID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("fetching album tracks: %w", err)
|
||||||
|
}
|
||||||
|
return resp.GetTracks(), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *MetadataService) GetArtistByExternalID(ctx context.Context, externalID string) (*database.Artist, error) {
|
func (s *MetadataService) GetArtistByExternalID(ctx context.Context, externalID string) (*database.Artist, error) {
|
||||||
return s.artists.GetByExternalID(ctx, externalID)
|
return s.artists.GetByExternalID(ctx, externalID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *MetadataService) GetAlbumByID(ctx context.Context, id string) (*database.Album, error) {
|
||||||
|
return s.albums.GetByID(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *MetadataService) GetAlbumByExternalID(ctx context.Context, externalID string) (*database.Album, error) {
|
func (s *MetadataService) GetAlbumByExternalID(ctx context.Context, externalID string) (*database.Album, error) {
|
||||||
return s.albums.GetByExternalID(ctx, externalID)
|
return s.albums.GetByExternalID(ctx, externalID)
|
||||||
}
|
}
|
||||||
@@ -64,7 +80,11 @@ func (s *MetadataService) GetAlbumsByArtistID(ctx context.Context, artistID stri
|
|||||||
return s.albums.GetByArtistID(ctx, artistID)
|
return s.albums.GetByArtistID(ctx, artistID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MetadataService) persistArtist(ctx context.Context, album *metadataPb.Album) {
|
func (s *MetadataService) GetTracksByAlbumID(ctx context.Context, albumID string) ([]*database.Track, error) {
|
||||||
|
return s.tracks.GetByAlbumID(ctx, albumID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *MetadataService) PersistArtist(ctx context.Context, album *metadataPb.Album, state database.MonitorState) {
|
||||||
if len(album.GetArtists()) == 0 {
|
if len(album.GetArtists()) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -82,14 +102,14 @@ func (s *MetadataService) persistArtist(ctx context.Context, album *metadataPb.A
|
|||||||
Country: artist.GetCountry(),
|
Country: artist.GetCountry(),
|
||||||
Genres: genres,
|
Genres: genres,
|
||||||
ImageURL: artist.GetImageUrl(),
|
ImageURL: artist.GetImageUrl(),
|
||||||
MonitorState: database.Monitored,
|
MonitorState: state,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn().Err(err).Str("name", artist.GetName()).Msg("failed to persist artist")
|
log.Warn().Err(err).Str("name", artist.GetName()).Msg("failed to persist artist")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MetadataService) persistAlbum(ctx context.Context, album *metadataPb.Album) {
|
func (s *MetadataService) PersistAlbum(ctx context.Context, album *metadataPb.Album, state database.MonitorState) {
|
||||||
artistID := ""
|
artistID := ""
|
||||||
if len(album.GetArtists()) > 0 {
|
if len(album.GetArtists()) > 0 {
|
||||||
a, err := s.artists.GetByExternalID(ctx, album.GetArtists()[0].GetArtist().GetId())
|
a, err := s.artists.GetByExternalID(ctx, album.GetArtists()[0].GetArtist().GetId())
|
||||||
@@ -123,9 +143,26 @@ func (s *MetadataService) persistAlbum(ctx context.Context, album *metadataPb.Al
|
|||||||
Label: labelName,
|
Label: labelName,
|
||||||
Genres: genres,
|
Genres: genres,
|
||||||
CoverURL: album.GetCoverUrl(),
|
CoverURL: album.GetCoverUrl(),
|
||||||
MonitorState: database.Monitored,
|
MonitorState: state,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn().Err(err).Str("title", album.GetTitle()).Msg("failed to persist album")
|
log.Warn().Err(err).Str("title", album.GetTitle()).Msg("failed to persist album")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *MetadataService) PersistTracks(ctx context.Context, albumDBID string, tracks []*metadataPb.Track) {
|
||||||
|
for _, t := range tracks {
|
||||||
|
err := s.tracks.Create(ctx, &database.Track{
|
||||||
|
ExternalID: t.GetId(),
|
||||||
|
AlbumID: albumDBID,
|
||||||
|
Title: t.GetTitle(),
|
||||||
|
DurationMS: int(t.GetDurationMs()),
|
||||||
|
ISRC: t.GetIsrc(),
|
||||||
|
DiscNumber: int(t.GetDiscNumber()),
|
||||||
|
TrackNumber: int(t.GetTrackNumber()),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Warn().Err(err).Str("title", t.GetTitle()).Msg("failed to persist track")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -33,6 +33,10 @@ func (s *MusicAgregatorServer) GetArtists(ctx context.Context, req *pb.GetArtist
|
|||||||
return s.service.GetArtists(ctx, req)
|
return s.service.GetArtists(ctx, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *MusicAgregatorServer) GetAlbum(ctx context.Context, req *pb.GetAlbumRequest) (*pb.GetAlbumResponse, error) {
|
||||||
|
return s.service.GetAlbum(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *MusicAgregatorServer) MonitorAlbum(ctx context.Context, req *pb.MonitorAlbumRequest) (*pb.MonitorAlbumResponse, error) {
|
func (s *MusicAgregatorServer) MonitorAlbum(ctx context.Context, req *pb.MonitorAlbumRequest) (*pb.MonitorAlbumResponse, error) {
|
||||||
return s.service.MonitorAlbum(ctx, req)
|
return s.service.MonitorAlbum(ctx, req)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ type MusicAgregatorService struct {
|
|||||||
torrents *database.TorrentRepository
|
torrents *database.TorrentRepository
|
||||||
downloads *database.DownloadRepository
|
downloads *database.DownloadRepository
|
||||||
artists *database.ArtistRepository
|
artists *database.ArtistRepository
|
||||||
|
downloadFiles *database.DownloadFileRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMusicAgregatorService(cfg config.Config, riverClient *river.Client[pgx.Tx], db *database.DB) (*MusicAgregatorService, error) {
|
func NewMusicAgregatorService(cfg config.Config, riverClient *river.Client[pgx.Tx], db *database.DB) (*MusicAgregatorService, error) {
|
||||||
@@ -78,6 +79,7 @@ func NewMusicAgregatorService(cfg config.Config, riverClient *river.Client[pgx.T
|
|||||||
torrents: database.NewTorrentRepository(db.Pool),
|
torrents: database.NewTorrentRepository(db.Pool),
|
||||||
downloads: database.NewDownloadRepository(db.Pool),
|
downloads: database.NewDownloadRepository(db.Pool),
|
||||||
artists: database.NewArtistRepository(db.Pool),
|
artists: database.NewArtistRepository(db.Pool),
|
||||||
|
downloadFiles: database.NewDownloadFileRepository(db.Pool),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,6 +125,10 @@ func (service *MusicAgregatorService) buildAlbumsForArtist(ctx context.Context,
|
|||||||
return nil, fmt.Errorf("fetching metadata albums: %w", err)
|
return nil, fmt.Errorf("fetching metadata albums: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, ma := range metadataAlbums {
|
||||||
|
service.metadata.PersistAlbum(ctx, ma, database.Unmonitored)
|
||||||
|
}
|
||||||
|
|
||||||
dbAlbums, err := service.metadata.GetAlbumsByArtistID(ctx, artist.ID)
|
dbAlbums, err := service.metadata.GetAlbumsByArtistID(ctx, artist.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn().Err(err).Str("artist_id", artist.ID).Msg("failed to get local albums")
|
log.Warn().Err(err).Str("artist_id", artist.ID).Msg("failed to get local albums")
|
||||||
@@ -177,6 +183,113 @@ func (service *MusicAgregatorService) buildAlbumsForArtist(ctx context.Context,
|
|||||||
return albums, nil
|
return albums, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (service *MusicAgregatorService) GetAlbum(ctx context.Context, req *pb.GetAlbumRequest) (*pb.GetAlbumResponse, error) {
|
||||||
|
dbAlbum, err := service.metadata.GetAlbumByID(ctx, req.GetAlbumId())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("album not found: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
metadataAlbum, err := service.metadata.GetAlbum(ctx, dbAlbum.ExternalID)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Str("album_id", dbAlbum.ExternalID).Msg("failed to get album from metadata")
|
||||||
|
return nil, fmt.Errorf("fetching album: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
metadataTracks, err := service.metadata.GetAlbumTracks(ctx, dbAlbum.ExternalID)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn().Err(err).Str("album_id", dbAlbum.ExternalID).Msg("failed to get tracks from metadata")
|
||||||
|
}
|
||||||
|
|
||||||
|
service.metadata.PersistTracks(ctx, dbAlbum.ID, metadataTracks)
|
||||||
|
|
||||||
|
album := &pb.AlbumDetail{
|
||||||
|
Id: dbAlbum.ID,
|
||||||
|
ExternalId: metadataAlbum.GetId(),
|
||||||
|
Title: metadataAlbum.GetTitle(),
|
||||||
|
AlbumType: metadataAlbum.GetAlbumType(),
|
||||||
|
ReleaseDate: metadataAlbum.GetReleaseDate(),
|
||||||
|
TotalTracks: metadataAlbum.GetTotalTracks(),
|
||||||
|
TotalDiscs: metadataAlbum.GetTotalDiscs(),
|
||||||
|
CoverUrl: metadataAlbum.GetCoverUrl(),
|
||||||
|
MonitorState: toProtoMonitorState(dbAlbum.MonitorState),
|
||||||
|
}
|
||||||
|
|
||||||
|
if metadataAlbum.GetLabel() != nil {
|
||||||
|
album.Label = metadataAlbum.GetLabel().GetName()
|
||||||
|
}
|
||||||
|
for _, g := range metadataAlbum.GetGenres() {
|
||||||
|
album.Genres = append(album.Genres, g.GetName())
|
||||||
|
}
|
||||||
|
|
||||||
|
downloads, err := service.downloads.GetByAlbumID(ctx, dbAlbum.ID)
|
||||||
|
if err == nil && len(downloads) > 0 {
|
||||||
|
best := downloads[0]
|
||||||
|
album.Download = &pb.DownloadInfo{
|
||||||
|
State: best.State,
|
||||||
|
Format: best.Format,
|
||||||
|
Quality: best.Quality,
|
||||||
|
SavePath: best.SavePath,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var downloadFilesByTrackID map[string]*database.DownloadFile
|
||||||
|
if album.Download != nil {
|
||||||
|
files, err := service.downloadFiles.GetByDownloadID(ctx, downloads[0].ID)
|
||||||
|
if err == nil {
|
||||||
|
downloadFilesByTrackID = make(map[string]*database.DownloadFile, len(files))
|
||||||
|
for _, f := range files {
|
||||||
|
if f.TrackID != nil {
|
||||||
|
downloadFilesByTrackID[*f.TrackID] = f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dbTracks, _ := service.metadata.GetTracksByAlbumID(ctx, dbAlbum.ID)
|
||||||
|
dbTracksByExternalID := make(map[string]*database.Track, len(dbTracks))
|
||||||
|
for _, t := range dbTracks {
|
||||||
|
dbTracksByExternalID[t.ExternalID] = t
|
||||||
|
}
|
||||||
|
|
||||||
|
tracks := make([]*pb.TrackDetail, 0, len(metadataTracks))
|
||||||
|
for _, mt := range metadataTracks {
|
||||||
|
td := &pb.TrackDetail{
|
||||||
|
ExternalId: mt.GetId(),
|
||||||
|
Title: mt.GetTitle(),
|
||||||
|
DurationMs: mt.GetDurationMs(),
|
||||||
|
DiscNumber: mt.GetDiscNumber(),
|
||||||
|
TrackNumber: mt.GetTrackNumber(),
|
||||||
|
Isrc: mt.GetIsrc(),
|
||||||
|
Explicit: mt.GetExplicit(),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ac := range mt.GetArtists() {
|
||||||
|
td.Artists = append(td.Artists, &pb.ArtistCredit{
|
||||||
|
Id: ac.GetArtist().GetId(),
|
||||||
|
Name: ac.GetArtist().GetName(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if dbTrack, ok := dbTracksByExternalID[mt.GetId()]; ok {
|
||||||
|
td.Id = dbTrack.ID
|
||||||
|
if df, ok := downloadFilesByTrackID[dbTrack.ID]; ok {
|
||||||
|
td.File = &pb.TrackFile{
|
||||||
|
Path: df.FilePath,
|
||||||
|
Format: df.FileType,
|
||||||
|
Size: df.FileSize,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tracks = append(tracks, td)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &pb.GetAlbumResponse{
|
||||||
|
Album: album,
|
||||||
|
Tracks: tracks,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (service *MusicAgregatorService) MonitorAlbum(ctx context.Context, req *pb.MonitorAlbumRequest) (*pb.MonitorAlbumResponse, error) {
|
func (service *MusicAgregatorService) MonitorAlbum(ctx context.Context, req *pb.MonitorAlbumRequest) (*pb.MonitorAlbumResponse, error) {
|
||||||
album, err := service.metadata.GetAlbum(ctx, req.GetAlbumId())
|
album, err := service.metadata.GetAlbum(ctx, req.GetAlbumId())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ option go_package = "homelab.lan/music-agregator/gen/music_agregator/v1/";
|
|||||||
service MusicAgregatorService {
|
service MusicAgregatorService {
|
||||||
rpc MonitorAlbum(MonitorAlbumRequest) returns (MonitorAlbumResponse) {}
|
rpc MonitorAlbum(MonitorAlbumRequest) returns (MonitorAlbumResponse) {}
|
||||||
rpc GetArtists(GetArtistsRequest) returns (GetArtistsResponse) {}
|
rpc GetArtists(GetArtistsRequest) returns (GetArtistsResponse) {}
|
||||||
|
rpc GetAlbum(GetAlbumRequest) returns (GetAlbumResponse) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
message MonitorAlbumRequest {
|
message MonitorAlbumRequest {
|
||||||
@@ -74,6 +75,39 @@ message DownloadInfo {
|
|||||||
string save_path = 4;
|
string save_path = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message GetAlbumRequest {
|
||||||
|
string album_id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetAlbumResponse {
|
||||||
|
AlbumDetail album = 1;
|
||||||
|
repeated TrackDetail tracks = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message TrackDetail {
|
||||||
|
string id = 1;
|
||||||
|
string external_id = 2;
|
||||||
|
string title = 3;
|
||||||
|
int32 duration_ms = 4;
|
||||||
|
int32 disc_number = 5;
|
||||||
|
int32 track_number = 6;
|
||||||
|
string isrc = 7;
|
||||||
|
bool explicit = 8;
|
||||||
|
repeated ArtistCredit artists = 9;
|
||||||
|
TrackFile file = 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ArtistCredit {
|
||||||
|
string id = 1;
|
||||||
|
string name = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message TrackFile {
|
||||||
|
string path = 1;
|
||||||
|
string format = 2;
|
||||||
|
int64 size = 3;
|
||||||
|
}
|
||||||
|
|
||||||
message MonitoredRelease {
|
message MonitoredRelease {
|
||||||
string info_hash = 1;
|
string info_hash = 1;
|
||||||
string artist = 2;
|
string artist = 2;
|
||||||
|
|||||||
Reference in New Issue
Block a user