Adding thumbnails management (downloading, checking local versions...)

Finishing the browse/library tab of the web app.
This commit is contained in:
Zoe Roux 2019-08-19 01:34:01 +02:00
parent fe649df033
commit edc5def19a
12 changed files with 191 additions and 34 deletions

View File

@ -1,7 +1,7 @@
<div> <div class="container justify-content-center">
<a class="show" *ngFor="let show of this.shows" routerLink="/shows/{{show.slug}}"> <a class="show" *ngFor="let show of this.shows" routerLink="/shows/{{show.slug}}">
<img src="{{show.imgPrimary}}" /> <img [style.background-image]="getThumb(show.slug)"/>
<p>{{show.title}}</p> <p class="title">{{show.title}}</p>
<p class="date" *ngIf="show.endYear; else elseBlock">{{show.startYear}} - {{show.endYear}}</p> <p class="date" *ngIf="show.endYear; else elseBlock">{{show.startYear}} - {{show.endYear}}</p>
<ng-template #elseBlock><p class="date">{{show.startYear}}</p></ng-template> <ng-template #elseBlock><p class="date">{{show.startYear}}</p></ng-template>
</a> </a>

View File

@ -2,14 +2,20 @@
@import "~bootstrap/scss/variables"; @import "~bootstrap/scss/variables";
@import "~bootstrap/scss//mixins/breakpoints"; @import "~bootstrap/scss//mixins/breakpoints";
.container
{
display: flex;
flex-wrap: wrap;
}
.show .show
{ {
width: 33%; width: 33%;
list-style: none; list-style: none;
padding: 1em; padding: 1em;
display: inline-block;
text-decoration: none; text-decoration: none;
color: inherit; color: inherit;
outline: none;
@include media-breakpoint-up(md) @include media-breakpoint-up(md)
{ {
@ -26,10 +32,26 @@
width: 18%; width: 18%;
} }
&:focus, &:hover
{
> img
{
outline: solid var(--accentColor);
}
> .title
{
text-decoration: underline;
}
}
> img > img
{ {
max-width: 100%; width: 100%;
max-height: 100%; height: 0;
padding-top: 147.0588%;
background-size: cover;
background-color: #333333;
} }
> p > p
@ -39,6 +61,12 @@
text-overflow: ellipsis; text-overflow: ellipsis;
text-align: center; text-align: center;
margin-bottom: 0px; margin-bottom: 0px;
&.date
{
opacity: 0.8;
font-size: 0.8em;
}
} }
&:hover &:hover
@ -46,9 +74,3 @@
cursor: pointer; cursor: pointer;
} }
} }
.date
{
opacity: 0.8;
font-size: 0.8em;
}

View File

@ -1,6 +1,7 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { DomSanitizer, SafeStyle } from '@angular/platform-browser';
@Component({ @Component({
selector: 'app-browse', selector: 'app-browse',
@ -13,7 +14,7 @@ export class BrowseComponent implements OnInit
private watch: any; private watch: any;
constructor(private http: HttpClient, private route: ActivatedRoute) { } constructor(private http: HttpClient, private route: ActivatedRoute, private sanitizer: DomSanitizer) {}
ngOnInit() ngOnInit()
{ {
@ -42,4 +43,9 @@ export class BrowseComponent implements OnInit
{ {
this.watch.unsubscribe(); this.watch.unsubscribe();
} }
getThumb(slug: string)
{
return this.sanitizer.bypassSecurityTrustStyle("url(/thumb/" + slug + ")");
}
} }

View File

@ -1 +1,2 @@
<p>Should display show details of: {{this.show.title}}</p> <p *ngIf="this.show; else elseBlock">Should display show details of: {{this.show.title}}</p>
<ng-template #elseBlock>Loading</ng-template>

View File

@ -0,0 +1,25 @@
using Kyoo.InternalAPI;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.FileProviders;
// For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
namespace Kyoo.Controllers
{
public class ThumbnailController : Controller
{
private ILibraryManager libraryManager;
public ThumbnailController(ILibraryManager libraryManager)
{
this.libraryManager = libraryManager;
}
[HttpGet("thumb/{showSlug}")]
public IActionResult GetShowThumb(string showSlug)
{
string thumbPath = libraryManager.GetShowBySlug(showSlug).ImgPrimary;
return new PhysicalFileResult(thumbPath, "image/jpg");
}
}
}

View File

@ -42,7 +42,7 @@ namespace Kyoo.InternalAPI
public async void Scan(string folderPath) public async void Scan(string folderPath)
{ {
string[] files = Directory.GetFiles(folderPath); string[] files = Directory.GetFiles(folderPath, "*", SearchOption.AllDirectories);
foreach (string file in files) foreach (string file in files)
{ {
@ -133,10 +133,9 @@ namespace Kyoo.InternalAPI
seasonID = libraryManager.RegisterSeason(season); seasonID = libraryManager.RegisterSeason(season);
} }
Episode episode = await metadataProvider.GetEpisode(showProviderIDs, seasonNumber, episodeNumber); Episode episode = await metadataProvider.GetEpisode(showProviderIDs, seasonNumber, episodeNumber, path);
episode.ShowID = showID; episode.ShowID = showID;
episode.SeasonID = seasonID; episode.SeasonID = seasonID;
episode.Path = path;
libraryManager.RegisterEpisode(episode); libraryManager.RegisterEpisode(episode);
} }
} }

View File

@ -15,6 +15,6 @@ namespace Kyoo.InternalAPI
Task<string> GetSeasonImage(string showName, long seasonNumber); Task<string> GetSeasonImage(string showName, long seasonNumber);
//For the episodes //For the episodes
Task<Episode> GetEpisode(string externalIDs, long seasonNumber, long episodeNumber); Task<Episode> GetEpisode(string externalIDs, long seasonNumber, long episodeNumber, string episodePath);
} }
} }

View File

@ -269,7 +269,7 @@ namespace Kyoo.InternalAPI.MetadataProvider
return null; return null;
} }
public async Task<Episode> GetEpisode(string externalIDs, long seasonNumber, long episodeNumber) public async Task<Episode> GetEpisode(string externalIDs, long seasonNumber, long episodeNumber, string episodePath)
{ {
string id = GetID(externalIDs); string id = GetID(externalIDs);

View File

@ -1,9 +1,11 @@
using Kyoo.InternalAPI.MetadataProvider; using Kyoo.InternalAPI.MetadataProvider;
using Kyoo.InternalAPI.ThumbnailsManager;
using Kyoo.Models; using Kyoo.Models;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -12,10 +14,12 @@ namespace Kyoo.InternalAPI
public class ProviderManager : IMetadataProvider public class ProviderManager : IMetadataProvider
{ {
private readonly List<IMetadataProvider> providers = new List<IMetadataProvider>(); private readonly List<IMetadataProvider> providers = new List<IMetadataProvider>();
private readonly IThumbnailsManager thumbnailsManager;
private readonly IConfiguration config; private readonly IConfiguration config;
public ProviderManager(IConfiguration configuration) public ProviderManager(IThumbnailsManager thumbnailsManager, IConfiguration configuration)
{ {
this.thumbnailsManager = thumbnailsManager;
config = configuration; config = configuration;
LoadProviders(); LoadProviders();
} }
@ -58,12 +62,21 @@ namespace Kyoo.InternalAPI
} }
} }
//public Show MergeShows(Show baseShow, Show newShow) public Show Merge(IEnumerable<Show> shows)
//{ {
return shows.FirstOrDefault();
}
//} public Season Merge(IEnumerable<Season> seasons)
{
return seasons.FirstOrDefault();
}
public Episode Merge(IEnumerable<Episode> episodes)
{
return episodes.FirstOrDefault();
}
//For all the following methods, it should use all providers and merge the data. //For all the following methods, it should use all providers and merge the data.
public Task<Show> GetImages(Show show) public Task<Show> GetImages(Show show)
@ -71,34 +84,69 @@ namespace Kyoo.InternalAPI
return providers[0].GetImages(show); return providers[0].GetImages(show);
} }
public Task<Season> GetSeason(string showName, int seasonNumber) public async Task<Season> GetSeason(string showName, int seasonNumber)
{ {
return providers[0].GetSeason(showName, seasonNumber); List<Season> datas = new List<Season>();
for (int i = 0; i < providers.Count; i++)
{
datas.Add(await providers[i].GetSeason(showName, seasonNumber));
}
return Merge(datas);
} }
public Task<Show> GetShowByID(string id) public async Task<Show> GetShowByID(string id)
{ {
return providers[0].GetShowByID(id); List<Show> datas = new List<Show>();
for (int i = 0; i < providers.Count; i++)
{
datas.Add(await providers[i].GetShowByID(id));
}
return Merge(datas);
} }
public Task<Show> GetShowFromName(string showName, string showPath) public async Task<Show> GetShowFromName(string showName, string showPath)
{ {
return providers[0].GetShowFromName(showName, showPath); List<Show> datas = new List<Show>();
for (int i = 0; i < providers.Count; i++)
{
datas.Add(await providers[i].GetShowFromName(showName, showPath));
}
Show show = Merge(datas);
return thumbnailsManager.Validate(show);
} }
public Task<Season> GetSeason(string showName, long seasonNumber) public async Task<Season> GetSeason(string showName, long seasonNumber)
{ {
return providers[0].GetSeason(showName, seasonNumber); List<Season> datas = new List<Season>();
for (int i = 0; i < providers.Count; i++)
{
datas.Add(await providers[i].GetSeason(showName, seasonNumber));
}
return Merge(datas);
} }
public Task<string> GetSeasonImage(string showName, long seasonNumber) public Task<string> GetSeasonImage(string showName, long seasonNumber)
{ {
//Should select the best provider for this show.
return providers[0].GetSeasonImage(showName, seasonNumber); return providers[0].GetSeasonImage(showName, seasonNumber);
} }
public Task<Episode> GetEpisode(string externalIDs, long seasonNumber, long episodeNumber) public async Task<Episode> GetEpisode(string externalIDs, long seasonNumber, long episodeNumber, string episodePath)
{ {
return providers[0].GetEpisode(externalIDs, seasonNumber, episodeNumber); List<Episode> datas = new List<Episode>();
for (int i = 0; i < providers.Count; i++)
{
datas.Add(await providers[i].GetEpisode(externalIDs, seasonNumber, episodeNumber, episodePath));
}
Episode episode = Merge(datas);
episode.Path = episodePath;
return thumbnailsManager.Validate(episode);
} }
} }
} }

View File

@ -0,0 +1,10 @@
using Kyoo.Models;
namespace Kyoo.InternalAPI.ThumbnailsManager
{
public interface IThumbnailsManager
{
Show Validate(Show show);
Episode Validate(Episode episode);
}
}

View File

@ -0,0 +1,40 @@
using Kyoo.Models;
using System;
using System.IO;
using System.Net;
namespace Kyoo.InternalAPI.ThumbnailsManager
{
public class ThumbnailsManager : IThumbnailsManager
{
public Show Validate(Show show)
{
string localThumb = Path.Combine(show.Path, "poster.jpg");
if (!File.Exists(localThumb))
{
using (WebClient client = new WebClient())
{
client.DownloadFileAsync(new Uri(show.ImgPrimary), localThumb);
}
}
show.ImgPrimary = localThumb;
return show;
}
public Episode Validate(Episode episode)
{
string localThumb = Path.ChangeExtension(episode.Path, "jpg");
if (!File.Exists(localThumb))
{
using (WebClient client = new WebClient())
{
client.DownloadFileAsync(new Uri(episode.ImgPrimary), localThumb);
}
}
episode.ImgPrimary = localThumb;
return episode;
}
}
}

View File

@ -1,5 +1,6 @@
using Kyoo.InternalAPI; using Kyoo.InternalAPI;
using Kyoo.InternalAPI.MetadataProvider; using Kyoo.InternalAPI.MetadataProvider;
using Kyoo.InternalAPI.ThumbnailsManager;
using Kyoo.Models; using Kyoo.Models;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
@ -7,6 +8,7 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SpaServices.AngularCli; using Microsoft.AspNetCore.SpaServices.AngularCli;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using System.Diagnostics; using System.Diagnostics;
using System.Web.Http; using System.Web.Http;
@ -32,8 +34,12 @@ namespace Kyoo
configuration.RootPath = "ClientApp/dist"; configuration.RootPath = "ClientApp/dist";
}); });
//Services needed in the private and in the public API
services.AddSingleton<ILibraryManager, LibraryManager>(); services.AddSingleton<ILibraryManager, LibraryManager>();
//Services used to get informations about files and register them
services.AddHostedService<Crawler>(); services.AddHostedService<Crawler>();
services.AddSingleton<IThumbnailsManager, ThumbnailsManager>();
services.AddSingleton<IMetadataProvider, ProviderManager>(); services.AddSingleton<IMetadataProvider, ProviderManager>();
} }