More work on music!

pull/3895/head
Jamie Rees 5 years ago
parent 98346d1c86
commit 12a80c7903

@ -1,6 +1,8 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Hqub.MusicBrainz.API.Entities; using Hqub.MusicBrainz.API.Entities;
using Ombi.Api.MusicBrainz.Models;
namespace Ombi.Api.MusicBrainz namespace Ombi.Api.MusicBrainz
{ {
@ -9,5 +11,6 @@ namespace Ombi.Api.MusicBrainz
Task<IEnumerable<Artist>> SearchArtist(string artistQuery); Task<IEnumerable<Artist>> SearchArtist(string artistQuery);
Task<IEnumerable<Release>> GetReleaseForArtist(string artistId); Task<IEnumerable<Release>> GetReleaseForArtist(string artistId);
Task<Artist> GetArtistInformation(string artistId); Task<Artist> GetArtistInformation(string artistId);
Task<ReleaseGroupArt> GetCoverArtForReleaseGroup(string musicBrainzId, CancellationToken token);
} }
} }

@ -0,0 +1,37 @@
using Newtonsoft.Json;
namespace Ombi.Api.MusicBrainz.Models
{
public class ReleaseGroupArt
{
public string release { get; set; }
public Image[] images { get; set; }
}
public class Image
{
public int edit { get; set; }
public string id { get; set; }
public string image { get; set; }
public Thumbnails thumbnails { get; set; }
public string comment { get; set; }
public bool approved { get; set; }
public bool front { get; set; }
public string[] types { get; set; }
public bool back { get; set; }
}
public class Thumbnails
{
//[JsonProperty("250")]
//public string px250 { get; set; }
//[JsonProperty("500")]
//public string px500 { get; set; }
//[JsonProperty("1200")]
//public string px1200 { get; set; }
public string small { get; set; }
public string large { get; set; }
}
}

@ -2,14 +2,24 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Hqub.MusicBrainz.API; using Hqub.MusicBrainz.API;
using Hqub.MusicBrainz.API.Entities; using Hqub.MusicBrainz.API.Entities;
using Newtonsoft.Json;
using Ombi.Api.MusicBrainz.Models;
namespace Ombi.Api.MusicBrainz namespace Ombi.Api.MusicBrainz
{ {
public class MusicBrainzApi : IMusicBrainzApi public class MusicBrainzApi : IMusicBrainzApi
{ {
private readonly IApi _api;
public MusicBrainzApi(IApi api)
{
_api = api;
}
public async Task<IEnumerable<Artist>> SearchArtist(string artistQuery) public async Task<IEnumerable<Artist>> SearchArtist(string artistQuery)
{ {
var artist = await Artist.SearchAsync(artistQuery, 10); var artist = await Artist.SearchAsync(artistQuery, 10);
@ -34,10 +44,21 @@ namespace Ombi.Api.MusicBrainz
// Search for a release by title. // Search for a release by title.
var releases = await Release.SearchAsync(query); var releases = await Release.SearchAsync(query);
return releases.Items; return releases.Items;
} }
public async Task<ReleaseGroupArt> GetCoverArtForReleaseGroup(string musicBrainzId, CancellationToken token)
{
var request = new Request($"release-group/{musicBrainzId}", "http://coverartarchive.org", HttpMethod.Get);
var result = await _api.Request(request, token);
if (result.IsSuccessStatusCode)
{
var jsonContent = await result.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<ReleaseGroupArt>(jsonContent, Api.Settings);
}
return null;
}
private void AddHeaders(Request req) private void AddHeaders(Request req)
{ {
req.AddHeader("Accept", "application/json"); req.AddHeader("Accept", "application/json");

@ -25,7 +25,7 @@ namespace Ombi.Api
private ILogger<Api> Logger { get; } private ILogger<Api> Logger { get; }
private readonly IOmbiHttpClient _client; private readonly IOmbiHttpClient _client;
private static readonly JsonSerializerSettings Settings = new JsonSerializerSettings public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
{ {
NullValueHandling = NullValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore,
ContractResolver = new PluralPropertyContractResolver() ContractResolver = new PluralPropertyContractResolver()
@ -66,7 +66,7 @@ namespace Ombi.Api
{ {
using (var req = await httpRequestMessage.Clone()) using (var req = await httpRequestMessage.Clone())
{ {
return await _client.SendAsync(req); return await _client.SendAsync(req, cancellationToken);
} }
}); });
} }
@ -119,12 +119,12 @@ namespace Ombi.Api
} }
public async Task Request(Request request) public async Task<HttpResponseMessage> Request(Request request, CancellationToken token = default(CancellationToken))
{ {
using (var httpRequestMessage = new HttpRequestMessage(request.HttpMethod, request.FullUri)) using (var httpRequestMessage = new HttpRequestMessage(request.HttpMethod, request.FullUri))
{ {
AddHeadersBody(request, httpRequestMessage); AddHeadersBody(request, httpRequestMessage);
var httpResponseMessage = await _client.SendAsync(httpRequestMessage); var httpResponseMessage = await _client.SendAsync(httpRequestMessage, token);
await LogDebugContent(httpResponseMessage); await LogDebugContent(httpResponseMessage);
if (!httpResponseMessage.IsSuccessStatusCode) if (!httpResponseMessage.IsSuccessStatusCode)
{ {
@ -133,6 +133,8 @@ namespace Ombi.Api
await LogError(request, httpResponseMessage); await LogError(request, httpResponseMessage);
} }
} }
return httpResponseMessage;
} }
} }

@ -1,11 +1,12 @@
using System.Threading; using System.Net.Http;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Ombi.Api namespace Ombi.Api
{ {
public interface IApi public interface IApi
{ {
Task Request(Request request); Task<HttpResponseMessage> Request(Request request, CancellationToken token = default(CancellationToken));
Task<T> Request<T>(Request request, CancellationToken cancellationToken = default(CancellationToken)); Task<T> Request<T>(Request request, CancellationToken cancellationToken = default(CancellationToken));
Task<string> RequestContent(Request request); Task<string> RequestContent(Request request);
T DeserializeXml<T>(string receivedString); T DeserializeXml<T>(string receivedString);

@ -1,3 +1,4 @@
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Ombi.Core.Models.Search.V2.Music; using Ombi.Core.Models.Search.V2.Music;
@ -6,5 +7,6 @@ namespace Ombi.Core.Engine.Interfaces
public interface IMusicSearchEngineV2 public interface IMusicSearchEngineV2
{ {
Task<ArtistInformation> GetArtistInformation(string artistId); Task<ArtistInformation> GetArtistInformation(string artistId);
Task<AlbumArt> GetReleaseGroupArt(string musicBrainzId, CancellationToken token);
} }
} }

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Security.Principal; using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Ombi.Api.Lidarr; using Ombi.Api.Lidarr;
using Ombi.Api.Lidarr.Models; using Ombi.Api.Lidarr.Models;
@ -81,7 +82,7 @@ namespace Ombi.Core.Engine.V2
if (lidarrArtistTask != null) if (lidarrArtistTask != null)
{ {
var artistResult = await lidarrArtistTask; var artistResult = await lidarrArtistTask;
info.Banner = artistResult.images?.FirstOrDefault(x => x.coverType.Equals("banner", StringComparison.InvariantCultureIgnoreCase))?.url.Replace("http","https"); info.Banner = artistResult.images?.FirstOrDefault(x => x.coverType.Equals("banner", StringComparison.InvariantCultureIgnoreCase))?.url.Replace("http", "https");
info.Logo = artistResult.images?.FirstOrDefault(x => x.coverType.Equals("logo", StringComparison.InvariantCultureIgnoreCase))?.url.Replace("http", "https"); info.Logo = artistResult.images?.FirstOrDefault(x => x.coverType.Equals("logo", StringComparison.InvariantCultureIgnoreCase))?.url.Replace("http", "https");
info.Poster = artistResult.images?.FirstOrDefault(x => x.coverType.Equals("poster", StringComparison.InvariantCultureIgnoreCase))?.url.Replace("http", "https"); info.Poster = artistResult.images?.FirstOrDefault(x => x.coverType.Equals("poster", StringComparison.InvariantCultureIgnoreCase))?.url.Replace("http", "https");
info.FanArt = artistResult.images?.FirstOrDefault(x => x.coverType.Equals("fanart", StringComparison.InvariantCultureIgnoreCase))?.url.Replace("http", "https"); info.FanArt = artistResult.images?.FirstOrDefault(x => x.coverType.Equals("fanart", StringComparison.InvariantCultureIgnoreCase))?.url.Replace("http", "https");
@ -90,7 +91,39 @@ namespace Ombi.Core.Engine.V2
return info; return info;
} }
public async Task<AlbumArt> GetReleaseGroupArt(string musicBrainzId, CancellationToken token)
{
var art = await _musicBrainzApi.GetCoverArtForReleaseGroup(musicBrainzId, token);
if (art == null || !art.images.Any())
{
return new AlbumArt();
}
foreach (var cover in art.images)
{
//if ((cover.thumbnails?.px250 ?? string.Empty).HasValue())
//{
// return new AlbumArt(cover.thumbnails.px250);
//}
if ((cover.thumbnails?.small ?? string.Empty).HasValue())
{
return new AlbumArt(cover.thumbnails.small);
}
//if ((cover.thumbnails?.px500 ?? string.Empty).HasValue())
//{
// return new AlbumArt(cover.thumbnails.px500);
//}
if ((cover.thumbnails?.large ?? string.Empty).HasValue())
{
return new AlbumArt(cover.thumbnails.large);
}
}
return new AlbumArt();
}
private List<BandMember> GetBandMembers(Artist artist) private List<BandMember> GetBandMembers(Artist artist)
{ {
var members = new List<BandMember>(); var members = new List<BandMember>();

@ -0,0 +1,16 @@
namespace Ombi.Core.Models.Search.V2.Music
{
public class AlbumArt
{
public AlbumArt()
{
}
public AlbumArt(string url)
{
Image = url;
}
public string Image { get; set; }
}
}

@ -24,6 +24,8 @@ export interface IReleaseGroups {
title: string; title: string;
releaseDate: string; releaseDate: string;
type: string; type: string;
image: string; // Set by another api call
} }
export interface IArtistLinks { export interface IArtistLinks {
@ -53,4 +55,8 @@ export interface IBandMembers {
isCurrentMember: boolean; isCurrentMember: boolean;
start: string; start: string;
end: string; end: string;
}
export interface IAlbumArt {
image: string;
} }

@ -1,71 +1,28 @@
<mat-accordion class="mat-elevation-z8 spacing-below">
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title>
{{'MediaDetails.AlbumsTitle' | translate}}
</mat-panel-title>
</mat-expansion-panel-header>
<div class="row card-spacer">
<div class="col-md-2" *ngFor="let r of albums">
<div class="sidebar affixable affix-top preview-poster">
<div class="poster">
<!-- <img class="real grow" matTooltip="{{r.title}}"
src="{{r.title}}" alt="Poster"
style="display: block;"> -->
{{r.title}} | {{r.type}}
</div>
</div>
</div>
</div>
</mat-expansion-panel>
<mat-expansion-panel *ngIf="ep">
<mat-expansion-panel-header>
<mat-panel-title>
{{'MediaDetails.SinglesTitle' | translate}}
</mat-panel-title>
</mat-expansion-panel-header>
<div class="row card-spacer">
<div class="col-md-2" *ngFor="let r of singles">
<div class="sidebar affixable affix-top preview-poster">
<div class="poster">
<!-- <img class="real grow" matTooltip="{{r.title}}"
src="{{r.title}}" alt="Poster"
style="display: block;"> -->
{{r.title}} | {{r.type}}
</div>
</div>
<mat-card class="example-card">
<mat-card-header>
<mat-card-title>
{{'MediaDetails.AlbumsTitle' | translate}}
</mat-card-title>
</mat-card-header>
<mat-card-content>
<div class="row card-spacer">
<span *ngFor="let r of albums">
<div class="col-md-2" *ngIf="r.image" >
<div class="sidebar affixable affix-top preview-poster">
<div class="poster">
<img class="real grow" matTooltip="{{r.title}}"
src="{{r.image}}" alt="Poster"
style="display: block;">
</div> </div>
</div> </div>
</mat-expansion-panel>
</div>
<mat-expansion-panel> </span>
<mat-expansion-panel-header> </div>
<mat-panel-title> </mat-card-content>
{{'MediaDetails.EpTitle' | translate}} </mat-card>
</mat-panel-title>
</mat-expansion-panel-header>
<div class="row card-spacer">
<div class="col-md-2" *ngFor="let r of ep">
<div class="sidebar affixable affix-top preview-poster">
<div class="poster">
<!-- <img class="real grow" matTooltip="{{r.title}}"
src="{{r.title}}" alt="Poster"
style="display: block;"> -->
{{r.title}} | {{r.type}}
</div>
</div>
</div>
</div>
</mat-expansion-panel>
</mat-accordion>

@ -1,5 +1,6 @@
import { Component, Input, ViewEncapsulation, OnInit } from "@angular/core"; import { Component, Input, ViewEncapsulation, OnInit } from "@angular/core";
import { IReleaseGroups } from "../../../../../interfaces/IMusicSearchResultV2"; import { IReleaseGroups } from "../../../../../interfaces/IMusicSearchResultV2";
import { SearchV2Service } from "../../../../../services/searchV2.service";
@Component({ @Component({
templateUrl: "./artist-release-panel.component.html", templateUrl: "./artist-release-panel.component.html",
@ -12,12 +13,14 @@ export class ArtistReleasePanel implements OnInit {
@Input() public releases: IReleaseGroups[]; @Input() public releases: IReleaseGroups[];
public albums: IReleaseGroups[]; public albums: IReleaseGroups[];
public singles: IReleaseGroups[];
public ep: IReleaseGroups[];
public ngOnInit(): void { constructor(private searchService: SearchV2Service) { }
public ngOnInit() {
this.albums = this.releases.filter(x => x.type === "Album"); this.albums = this.releases.filter(x => x.type === "Album");
this.singles = this.releases.filter(x => x.type === "Single");
this.ep = this.releases.filter(x => x.type === "EP"); this.albums.forEach(a => {
this.searchService.getReleaseGroupArt(a.id).subscribe(x => a.image = x.image);
});
} }
} }

@ -9,7 +9,7 @@ import { ServiceHelpers } from "./service.helpers";
import { ISearchMovieResultV2 } from "../interfaces/ISearchMovieResultV2"; import { ISearchMovieResultV2 } from "../interfaces/ISearchMovieResultV2";
import { ISearchTvResultV2, IMovieCollectionsViewModel, IActorCredits } from "../interfaces/ISearchTvResultV2"; import { ISearchTvResultV2, IMovieCollectionsViewModel, IActorCredits } from "../interfaces/ISearchTvResultV2";
import { IArtistSearchResult } from "../interfaces/IMusicSearchResultV2"; import { IArtistSearchResult, IAlbumArt } from "../interfaces/IMusicSearchResultV2";
@Injectable() @Injectable()
export class SearchV2Service extends ServiceHelpers { export class SearchV2Service extends ServiceHelpers {
@ -103,4 +103,8 @@ export class SearchV2Service extends ServiceHelpers {
public getArtistInformation(artistId: string): Observable<IArtistSearchResult> { public getArtistInformation(artistId: string): Observable<IArtistSearchResult> {
return this.http.get<IArtistSearchResult>(`${this.url}/artist/${artistId}`); return this.http.get<IArtistSearchResult>(`${this.url}/artist/${artistId}`);
} }
public getReleaseGroupArt(mbid: string): Observable<IAlbumArt> {
return this.http.get<IAlbumArt>(`${this.url}/releasegroupart/${mbid}`);
}
} }

@ -94,7 +94,7 @@
</mat-option> </mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
<div><button mat-raised-button (click)="getLanguaggetMetadataProfileseProfiles(form)" color="primary">Load Metadata <span <div><button mat-raised-button (click)="getMetadataProfiles(form)" color="primary">Load Metadata <span
*ngIf="metadataRunning" class="fa fa-spinner fa-spin"></span></button></div> *ngIf="metadataRunning" class="fa fa-spinner fa-spin"></span></button></div>
</div> </div>

@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading;
using Ombi.Core; using Ombi.Core;
using Ombi.Api.TheMovieDb.Models; using Ombi.Api.TheMovieDb.Models;
using Ombi.Core.Engine.V2; using Ombi.Core.Engine.V2;
@ -343,5 +344,12 @@ namespace Ombi.Controllers.V2
return await _musicEngine.GetArtistInformation(artistId); return await _musicEngine.GetArtistInformation(artistId);
} }
[HttpGet("releasegroupart/{musicBrainzId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesDefaultResponseType]
public async Task<AlbumArt> GetReleaseGroupARt(string musicBrainzId)
{
return await _musicEngine.GetReleaseGroupArt(musicBrainzId, CancellationToken);
}
} }
} }

@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Authorization; using System.Threading;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace Ombi.Controllers.V2 namespace Ombi.Controllers.V2
@ -9,6 +10,6 @@ namespace Ombi.Controllers.V2
[ApiController] [ApiController]
public class V2Controller : ControllerBase public class V2Controller : ControllerBase
{ {
public CancellationToken CancellationToken => HttpContext.RequestAborted;
} }
} }

@ -203,6 +203,7 @@
"RecommendationsTitle": "Recommendations", "RecommendationsTitle": "Recommendations",
"SimilarTitle": "Similar", "SimilarTitle": "Similar",
"VideosTitle": "Videos", "VideosTitle": "Videos",
"AlbumsTitle":"Albums",
"Casts": { "Casts": {
"CastTitle": "Cast", "CastTitle": "Cast",
"Character": "Character", "Character": "Character",

Loading…
Cancel
Save