Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
164 changes: 82 additions & 82 deletions Zune.Net.Catalog.Image/Controllers/ImageController.cs
Original file line number Diff line number Diff line change
@@ -1,82 +1,82 @@
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Net.Mime;
using System.Threading.Tasks;
using Zune.DB;
using Zune.Net.Helpers;

namespace Zune.Net.Catalog.Image.Controllers
{
[Route("/")]
[Produces(Atom.Constants.ATOM_MIMETYPE)]
public class ImageController(ZuneNetContext database, IdMapper idMapper) : Controller
{
private static readonly ConcurrentDictionary<int, JObject> DcArtistCache = new();
private static readonly int[] CaaSupportedSizes = [250, 500, 1200];

[HttpGet, Route("image/{id:guid}")]
public async Task<IActionResult> Image(Guid id,
[FromQuery] bool resize = false, [FromQuery(Name = "width")] int? requestedWidth = null,
[FromQuery] string contentType = MediaTypeNames.Image.Jpeg)
{
string? imageUrl = null;

var (idA, idB, idC) = id.GetGuidParts();
var imageEntry = await database.GetImageEntryAsync(id);

if (imageEntry is not null)
{
imageUrl = imageEntry.Url;
}
else if (idC == 0)
{
var dcid = unchecked((int)idA);

// Get or update cached artist
if (!DcArtistCache.TryGetValue(dcid, out var dcArtist))
{
// Artist not in cache
dcArtist = await Discogs.GetDCArtistByDCID(dcid);
DcArtistCache.AddOrUpdate(dcid, _ => dcArtist, (_, _) => dcArtist);
}

// Get URL for requested image
var images = dcArtist.Value<JArray>("images");
if (images != null && images.Count > idB)
imageUrl = images[idB].Value<string>("uri");
}
else
{
// Find nearest size supported by Cover Art Archive API
var targetWidth = requestedWidth ?? 500;
var width = CaaSupportedSizes.MinBy(x => Math.Abs(x - targetWidth));
imageUrl = $"https://coverartarchive.org/release/{id}/front-{width}";
}

return await this.GetAndResizeImageAsync(imageUrl, resize, requestedWidth, contentType);
}

[HttpGet, Route("music/artist/{id}/{type}")]
public async Task<IActionResult> ArtistImage(string id, string type,
[FromQuery] bool resize = false, [FromQuery(Name = "width")] int? requestedWidth = null,
[FromQuery] string contentType = MediaTypeNames.Image.Jpeg)
{
string? imageUrl = null;

if (type == "primaryImage")
{
var mbid = Guid.Parse(id);
var dcArtist = await Discogs.GetDCArtistByMBID(mbid, idMapper);

imageUrl = dcArtist.Value<JArray>("images")?
.FirstOrDefault(i => i.Value<string>("type") == "primary")?
.Value<string>("uri");
}

return await this.GetAndResizeImageAsync(imageUrl, true, requestedWidth, contentType);
}
}
}
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Net.Mime;
using System.Threading.Tasks;
using Zune.DB;
using Zune.Net.Helpers;
namespace Zune.Net.Catalog.Image.Controllers
{
[Route("/")]
[Produces(Atom.Constants.ATOM_MIMETYPE)]
public class ImageController(ZuneNetContext database, IdMapper idMapper) : Controller
{
private static readonly ConcurrentDictionary<int, JObject> DcArtistCache = new();
private static readonly int[] CaaSupportedSizes = [250, 500, 1200];
[HttpGet, Route("image/{id:guid}")]
public async Task<IActionResult> Image(Guid id,
[FromQuery] bool resize = false, [FromQuery(Name = "width")] int? requestedWidth = null,
[FromQuery] string contentType = MediaTypeNames.Image.Jpeg)
{
string? imageUrl = null;
var (idA, idB, idC) = id.GetGuidParts();
var imageEntry = await database.GetImageEntryAsync(id);
if (imageEntry is not null)
{
imageUrl = imageEntry.Url;
}
else if (idC == 0)
{
var dcid = unchecked((int)idA);
// Get or update cached artist
if (!DcArtistCache.TryGetValue(dcid, out var dcArtist))
{
// Artist not in cache
dcArtist = await Discogs.GetDCArtistByDCID(dcid);
DcArtistCache.AddOrUpdate(dcid, _ => dcArtist, (_, _) => dcArtist);
}
// Get URL for requested image
var images = dcArtist.Value<JArray>("images");
if (images != null && images.Count > idB)
imageUrl = images[idB].Value<string>("uri");
}
else
{
// Find nearest size supported by Cover Art Archive API
var targetWidth = requestedWidth ?? 500;
var width = CaaSupportedSizes.MinBy(x => Math.Abs(x - targetWidth));
imageUrl = $"https://coverartarchive.org/release/{id}/front-{width}";
}
return await this.GetAndResizeImageAsync(imageUrl, resize, requestedWidth, contentType);
}
[HttpGet, Route("music/artist/{id}/{type}")]
public async Task<IActionResult> ArtistImage(string id, string type,
[FromQuery] bool resize = false, [FromQuery(Name = "width")] int? requestedWidth = null,
[FromQuery] string contentType = MediaTypeNames.Image.Jpeg)
{
string? imageUrl = null;
if (type == "primaryImage")
{
var mbid = Guid.Parse(id);
var dcArtist = await Discogs.GetDCArtistByMBID(mbid, idMapper);
imageUrl = dcArtist.Value<JArray>("images")?
.FirstOrDefault(i => i.Value<string>("type") == "primary")?
.Value<string>("uri");
}
return await this.GetAndResizeImageAsync(imageUrl, true, requestedWidth, contentType);
}
}
}
117 changes: 63 additions & 54 deletions Zune.Net.Catalog.Image/Startup.cs
Original file line number Diff line number Diff line change
@@ -1,54 +1,63 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Zune.Net.Helpers;

namespace Zune.Net.Catalog.Image
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}

public IConfiguration Configuration { get; }

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews(o => o.UseZestFormatters());

services.AddCors(options =>
{
options.AddPolicy("AllowAll", Extensions.PermissiveCorsPolicy);
});
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
MusicBrainz.Initialize(env);

if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}

app.UseCors("AllowAll");

app.UseRequestBuffering();

app.UseCommonRouting();
app.UseRouting();

app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHomeRoute();
});
}
}
}
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Zune.Net.Helpers;

namespace Zune.Net.Catalog.Image
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}

public IConfiguration Configuration { get; }

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews(o => o.UseZestFormatters());

services.AddCors(options =>
{
options.AddPolicy("AllowAll", Extensions.PermissiveCorsPolicy);
});
}


// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
MusicBrainz.Initialize(env);

if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}

app.Use(async (context, next) =>
{
context.Response.Headers.Append("Expires", "Sun, 19 Apr 2071 10:00:00 GMT");
Comment thread
yoshiask marked this conversation as resolved.
context.Response.Headers.Append("Keep-Alive", "timeout=150000, max=10");
await next.Invoke();
});

app.UseCors("AllowAll");

app.UseRequestBuffering();

app.UseCommonRouting();
app.UseRouting();

app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHomeRoute();
});
}
}
}
31 changes: 16 additions & 15 deletions Zune.Net.Catalog.Image/Zune.Net.Catalog.Image.csproj
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>$(ServerTargetFramework)</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\Atom\Atom.csproj" />
<ProjectReference Include="..\Zune.Net.Shared\Zune.Net.Shared.csproj" />

<PackageReference Include="Microsoft.NET.Build.Containers" Version="9.0.102" />
</ItemGroup>

</Project>
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>$(ServerTargetFramework)</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.11" />
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Isn't this already pulled in as a transient dependency of Zune.Net.Shared?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apologies, forgot to take that out when merging. It should no longer be needed.

<ProjectReference Include="..\Atom\Atom.csproj" />
<ProjectReference Include="..\Zune.Net.Shared\Zune.Net.Shared.csproj" />

<PackageReference Include="Microsoft.NET.Build.Containers" Version="9.0.102" />
</ItemGroup>

</Project>
40 changes: 36 additions & 4 deletions Zune.Net.Catalog/Controllers/Music/ArtistController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net.Mime;
Expand Down Expand Up @@ -30,14 +31,18 @@ public ActionResult<Feed<Artist>> Search()
[HttpGet, Route("{mbid}")]
public async Task<ActionResult<Artist>> Details(Guid mbid)
{
var (version, culture) = this.GetCurrentVersionAndCulture();
(var dc_artist, var mb_artist) = await Discogs.GetDCArtistByMBID(mbid);
Artist artist = MusicBrainz.MBArtistToArtist(mb_artist);
artist.Images = new() { new() { Id = mbid } };

artist.Images = new() { new() { Id = new Guid(dc_artist.Value<int>("id"), 0, 0, new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }) } };

if (dc_artist != null)
{
artist.BiographyLink = $"/v{version}/{culture}{Request.Path.Value}biography";
artist.Biography = dc_artist.Value<string>("profile");
artist.Links.Add(new(Request.Path.Value + "biography", relation: "zune://artist/biography"));
artist.Links.Add(new($"/v{version}/{culture}{Request.Path.Value}", relation: "self"));
artist.Links.Add(new($"/v{version}/{culture}{Request.Path.Value}biography", relation: "zune://artist/biography"));

var dc_artist_image = dc_artist.Value<JArray>("images")?.FirstOrDefault(i => i.Value<string>("type") == "primary");
if (dc_artist_image != null)
Expand Down Expand Up @@ -192,7 +197,7 @@ public async Task<ActionResult<Feed<Image>>> Images(Guid mbid)
var images = dc_artist.Value<JToken>("images");
if (images != null)
{
feed.Entries = images.Select((j, idx) =>
feed.Entries = images.Select((j, idx) =>
{
// Encode DCID and image index in ID
Guid imgId = new(dcid, (short)idx, 0, zero);
Expand Down Expand Up @@ -221,7 +226,34 @@ public async Task<ActionResult<Feed<Image>>> Images(Guid mbid)
[HttpGet, Route("{mbid}/similarArtists")]
public async Task<ActionResult<Feed<Artist>>> SimilarArtists(Guid mbid)
{
return await LastFM.GetSimilarArtistsByMBID(mbid);
var relatedArtists = await LastFM.GetSimilarArtist(mbid);
var feed = LastFM.CreateFeed<Artist>($"/artist/{mbid}/similarArtists", "Similar");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can the path here be pulled from Request.Path.Value like the Details endpoint?

IdMapper mapped = new IdMapper();
List<Guid> mbidList = [];

foreach (var artist in relatedArtists)
{
if(artist.Id != null)
mbidList.Add(new Guid(artist.Id));
}

var artistIdList = await mapped.BatchGetArtistIdsAsync(mbidList, WikidataProperty.MBArtistId).ToListAsync();

foreach (var artist in relatedArtists)
{
foreach (var artistId in artistIdList)
{
if(artist.Id != null && artistId.Discogs != null)
{
if(artistId.MusicBrainz == new Guid(artist.Id))
artist.Images = new() { new() { Id = new Guid(int.Parse(artistId.Discogs), 0, 0, new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }) } };
}
}

feed.Entries.Add(artist);
}

return feed;
}
}
}
Loading