-
Notifications
You must be signed in to change notification settings - Fork 12
Add support for Zune HD #14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
e56ac8c
1a9eff0
c59a41b
7c1a5f2
a55f67e
252d81e
8332c05
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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); | ||
| } | ||
| } | ||
| } |
| 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"); | ||
| 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(); | ||
| }); | ||
| } | ||
| } | ||
| } | ||
| 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" /> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: Isn't this already pulled in as a transient dependency of
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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; | ||
|
|
@@ -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) | ||
|
|
@@ -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); | ||
|
|
@@ -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"); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can the path here be pulled from |
||
| 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; | ||
| } | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.