mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-07-09 03:04:20 -04:00
Merge pull request #5 from AnonymusRaccoon/Dev
Adding a plugin manager and moving TheTVDB metadata provider to a plugin.
This commit is contained in:
commit
d18000a9c8
233
Kyoo.Common/.gitignore
vendored
Normal file
233
Kyoo.Common/.gitignore
vendored
Normal file
@ -0,0 +1,233 @@
|
|||||||
|
## PROJECT CUSTOM IGNORES
|
||||||
|
|
||||||
|
## Ignore Visual Studio temporary files, build results, and
|
||||||
|
## files generated by popular Visual Studio add-ons.
|
||||||
|
|
||||||
|
# User-specific files
|
||||||
|
*.suo
|
||||||
|
*.user
|
||||||
|
*.userosscache
|
||||||
|
*.sln.docstates
|
||||||
|
|
||||||
|
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||||
|
*.userprefs
|
||||||
|
|
||||||
|
# Build results
|
||||||
|
[Dd]ebug/
|
||||||
|
[Dd]ebugPublic/
|
||||||
|
[Rr]elease/
|
||||||
|
[Rr]eleases/
|
||||||
|
x64/
|
||||||
|
x86/
|
||||||
|
build/
|
||||||
|
bld/
|
||||||
|
bin/
|
||||||
|
Bin/
|
||||||
|
obj/
|
||||||
|
Obj/
|
||||||
|
|
||||||
|
# Visual Studio 2015 cache/options directory
|
||||||
|
.vs/
|
||||||
|
|
||||||
|
# MSTest test Results
|
||||||
|
[Tt]est[Rr]esult*/
|
||||||
|
[Bb]uild[Ll]og.*
|
||||||
|
|
||||||
|
# NUNIT
|
||||||
|
*.VisualState.xml
|
||||||
|
TestResult.xml
|
||||||
|
|
||||||
|
# Build Results of an ATL Project
|
||||||
|
[Dd]ebugPS/
|
||||||
|
[Rr]eleasePS/
|
||||||
|
dlldata.c
|
||||||
|
|
||||||
|
*_i.c
|
||||||
|
*_p.c
|
||||||
|
*_i.h
|
||||||
|
*.ilk
|
||||||
|
*.meta
|
||||||
|
*.obj
|
||||||
|
*.pch
|
||||||
|
*.pdb
|
||||||
|
*.pgc
|
||||||
|
*.pgd
|
||||||
|
*.rsp
|
||||||
|
*.sbr
|
||||||
|
*.tlb
|
||||||
|
*.tli
|
||||||
|
*.tlh
|
||||||
|
*.tmp
|
||||||
|
*.tmp_proj
|
||||||
|
*.log
|
||||||
|
*.vspscc
|
||||||
|
*.vssscc
|
||||||
|
.builds
|
||||||
|
*.pidb
|
||||||
|
*.svclog
|
||||||
|
*.scc
|
||||||
|
|
||||||
|
# Chutzpah Test files
|
||||||
|
_Chutzpah*
|
||||||
|
|
||||||
|
# Visual C++ cache files
|
||||||
|
ipch/
|
||||||
|
*.aps
|
||||||
|
*.ncb
|
||||||
|
*.opendb
|
||||||
|
*.opensdf
|
||||||
|
*.sdf
|
||||||
|
*.cachefile
|
||||||
|
|
||||||
|
# Visual Studio profiler
|
||||||
|
*.psess
|
||||||
|
*.vsp
|
||||||
|
*.vspx
|
||||||
|
*.sap
|
||||||
|
|
||||||
|
# TFS 2012 Local Workspace
|
||||||
|
$tf/
|
||||||
|
|
||||||
|
# Guidance Automation Toolkit
|
||||||
|
*.gpState
|
||||||
|
|
||||||
|
# ReSharper is a .NET coding add-in
|
||||||
|
_ReSharper*/
|
||||||
|
*.[Rr]e[Ss]harper
|
||||||
|
*.DotSettings.user
|
||||||
|
|
||||||
|
# JustCode is a .NET coding add-in
|
||||||
|
.JustCode
|
||||||
|
|
||||||
|
# TeamCity is a build add-in
|
||||||
|
_TeamCity*
|
||||||
|
|
||||||
|
# DotCover is a Code Coverage Tool
|
||||||
|
*.dotCover
|
||||||
|
|
||||||
|
# NCrunch
|
||||||
|
_NCrunch_*
|
||||||
|
.*crunch*.local.xml
|
||||||
|
nCrunchTemp_*
|
||||||
|
|
||||||
|
# MightyMoose
|
||||||
|
*.mm.*
|
||||||
|
AutoTest.Net/
|
||||||
|
|
||||||
|
# Web workbench (sass)
|
||||||
|
.sass-cache/
|
||||||
|
|
||||||
|
# Installshield output folder
|
||||||
|
[Ee]xpress/
|
||||||
|
|
||||||
|
# DocProject is a documentation generator add-in
|
||||||
|
DocProject/buildhelp/
|
||||||
|
DocProject/Help/*.HxT
|
||||||
|
DocProject/Help/*.HxC
|
||||||
|
DocProject/Help/*.hhc
|
||||||
|
DocProject/Help/*.hhk
|
||||||
|
DocProject/Help/*.hhp
|
||||||
|
DocProject/Help/Html2
|
||||||
|
DocProject/Help/html
|
||||||
|
|
||||||
|
# Click-Once directory
|
||||||
|
publish/
|
||||||
|
|
||||||
|
# Publish Web Output
|
||||||
|
*.[Pp]ublish.xml
|
||||||
|
*.azurePubxml
|
||||||
|
# TODO: Comment the next line if you want to checkin your web deploy settings
|
||||||
|
# but database connection strings (with potential passwords) will be unencrypted
|
||||||
|
*.pubxml
|
||||||
|
*.publishproj
|
||||||
|
|
||||||
|
# NuGet Packages
|
||||||
|
*.nupkg
|
||||||
|
# The packages folder can be ignored because of Package Restore
|
||||||
|
**/packages/*
|
||||||
|
# except build/, which is used as an MSBuild target.
|
||||||
|
!**/packages/build/
|
||||||
|
# Uncomment if necessary however generally it will be regenerated when needed
|
||||||
|
#!**/packages/repositories.config
|
||||||
|
|
||||||
|
# Microsoft Azure Build Output
|
||||||
|
csx/
|
||||||
|
*.build.csdef
|
||||||
|
|
||||||
|
# Microsoft Azure Emulator
|
||||||
|
ecf/
|
||||||
|
rcf/
|
||||||
|
|
||||||
|
# Microsoft Azure ApplicationInsights config file
|
||||||
|
ApplicationInsights.config
|
||||||
|
|
||||||
|
# Windows Store app package directory
|
||||||
|
AppPackages/
|
||||||
|
BundleArtifacts/
|
||||||
|
|
||||||
|
# Visual Studio cache files
|
||||||
|
# files ending in .cache can be ignored
|
||||||
|
*.[Cc]ache
|
||||||
|
# but keep track of directories ending in .cache
|
||||||
|
!*.[Cc]ache/
|
||||||
|
|
||||||
|
# Others
|
||||||
|
ClientBin/
|
||||||
|
~$*
|
||||||
|
*~
|
||||||
|
*.dbmdl
|
||||||
|
*.dbproj.schemaview
|
||||||
|
*.pfx
|
||||||
|
*.publishsettings
|
||||||
|
orleans.codegen.cs
|
||||||
|
|
||||||
|
/node_modules
|
||||||
|
|
||||||
|
# RIA/Silverlight projects
|
||||||
|
Generated_Code/
|
||||||
|
|
||||||
|
# Backup & report files from converting an old project file
|
||||||
|
# to a newer Visual Studio version. Backup files are not needed,
|
||||||
|
# because we have git ;-)
|
||||||
|
_UpgradeReport_Files/
|
||||||
|
Backup*/
|
||||||
|
UpgradeLog*.XML
|
||||||
|
UpgradeLog*.htm
|
||||||
|
|
||||||
|
# SQL Server files
|
||||||
|
*.mdf
|
||||||
|
*.ldf
|
||||||
|
|
||||||
|
# Business Intelligence projects
|
||||||
|
*.rdl.data
|
||||||
|
*.bim.layout
|
||||||
|
*.bim_*.settings
|
||||||
|
|
||||||
|
# Microsoft Fakes
|
||||||
|
FakesAssemblies/
|
||||||
|
|
||||||
|
# GhostDoc plugin setting file
|
||||||
|
*.GhostDoc.xml
|
||||||
|
|
||||||
|
# Node.js Tools for Visual Studio
|
||||||
|
.ntvs_analysis.dat
|
||||||
|
|
||||||
|
# Visual Studio 6 build log
|
||||||
|
*.plg
|
||||||
|
|
||||||
|
# Visual Studio 6 workspace options file
|
||||||
|
*.opt
|
||||||
|
|
||||||
|
# Visual Studio LightSwitch build output
|
||||||
|
**/*.HTMLClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/ModelManifest.xml
|
||||||
|
**/*.Server/GeneratedArtifacts
|
||||||
|
**/*.Server/ModelManifest.xml
|
||||||
|
_Pvt_Extensions
|
||||||
|
|
||||||
|
# Paket dependency manager
|
||||||
|
.paket/paket.exe
|
||||||
|
|
||||||
|
# FAKE - F# Make
|
||||||
|
.fake/
|
@ -1,12 +1,12 @@
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Kyoo.InternalAPI
|
namespace Kyoo.Controllers
|
||||||
{
|
{
|
||||||
public interface ICrawler
|
public interface ICrawler
|
||||||
{
|
{
|
||||||
Task Start(bool watch);
|
void Start();
|
||||||
|
|
||||||
Task StopAsync();
|
void Cancel();
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,7 +2,7 @@
|
|||||||
using Kyoo.Models.Watch;
|
using Kyoo.Models.Watch;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Kyoo.InternalAPI
|
namespace Kyoo.Controllers
|
||||||
{
|
{
|
||||||
public interface ILibraryManager
|
public interface ILibraryManager
|
||||||
{
|
{
|
||||||
@ -63,9 +63,9 @@ namespace Kyoo.InternalAPI
|
|||||||
long GetOrCreateGenre(Genre genre);
|
long GetOrCreateGenre(Genre genre);
|
||||||
long GetOrCreateStudio(Studio studio);
|
long GetOrCreateStudio(Studio studio);
|
||||||
|
|
||||||
void RegisterShowPeople(long showID, List<People> actors);
|
void RegisterShowPeople(long showID, IEnumerable<People> actors);
|
||||||
void AddShowToCollection(long showID, long collectionID);
|
void AddShowToCollection(long showID, long collectionID);
|
||||||
void RegisterInLibrary(long showID, string libraryPath);
|
void RegisterInLibrary(long showID, Library library);
|
||||||
|
|
||||||
void RemoveEpisode(Episode episode);
|
void RemoveEpisode(Episode episode);
|
||||||
void ClearSubtitles(long episodeID);
|
void ClearSubtitles(long episodeID);
|
25
Kyoo.Common/Controllers/IMetadataProvider.cs
Normal file
25
Kyoo.Common/Controllers/IMetadataProvider.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
using Kyoo.Models;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Kyoo.Controllers
|
||||||
|
{
|
||||||
|
public interface IMetadataProvider
|
||||||
|
{
|
||||||
|
public string Name { get; }
|
||||||
|
|
||||||
|
//For the collection
|
||||||
|
Task<Collection> GetCollectionFromName(string name);
|
||||||
|
|
||||||
|
//For the show
|
||||||
|
Task<Show> GetShowByID(string id);
|
||||||
|
Task<Show> GetShowFromName(string showName);
|
||||||
|
Task<IEnumerable<People>> GetPeople(Show show);
|
||||||
|
|
||||||
|
//For the seasons
|
||||||
|
Task<Season> GetSeason(Show show, long seasonNumber);
|
||||||
|
|
||||||
|
//For the episodes
|
||||||
|
Task<Episode> GetEpisode(Show show, long seasonNumber, long episodeNumber, long absoluteNumber);
|
||||||
|
}
|
||||||
|
}
|
11
Kyoo.Common/Controllers/IPluginManager.cs
Normal file
11
Kyoo.Common/Controllers/IPluginManager.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Kyoo.Controllers
|
||||||
|
{
|
||||||
|
public interface IPluginManager
|
||||||
|
{
|
||||||
|
public T GetPlugin<T>(string name);
|
||||||
|
public IEnumerable<T> GetPlugins<T>();
|
||||||
|
public void ReloadPlugins();
|
||||||
|
}
|
||||||
|
}
|
15
Kyoo.Common/Controllers/IProviderManager.cs
Normal file
15
Kyoo.Common/Controllers/IProviderManager.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Kyoo.Models;
|
||||||
|
|
||||||
|
namespace Kyoo.Controllers
|
||||||
|
{
|
||||||
|
public interface IProviderManager
|
||||||
|
{
|
||||||
|
Task<Collection> GetCollectionFromName(string name, Library library);
|
||||||
|
Task<Show> GetShowFromName(string showName, Library library);
|
||||||
|
Task<Season> GetSeason(Show show, long seasonNumber, Library library);
|
||||||
|
Task<Episode> GetEpisode(Show show, long seasonNumber, long episodeNumber, long absoluteNumber, Library library);
|
||||||
|
Task<IEnumerable<People>> GetPeople(Show show, Library library);
|
||||||
|
}
|
||||||
|
}
|
@ -2,12 +2,12 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Kyoo.InternalAPI.ThumbnailsManager
|
namespace Kyoo.Controllers.ThumbnailsManager
|
||||||
{
|
{
|
||||||
public interface IThumbnailsManager
|
public interface IThumbnailsManager
|
||||||
{
|
{
|
||||||
Task<Show> Validate(Show show);
|
Task<Show> Validate(Show show);
|
||||||
Task<List<People>> Validate(List<People> actors);
|
Task<IEnumerable<People>> Validate(IEnumerable<People> actors);
|
||||||
Task<Episode> Validate(Episode episode);
|
Task<Episode> Validate(Episode episode);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,7 +2,7 @@ using Kyoo.Models;
|
|||||||
using Kyoo.Models.Watch;
|
using Kyoo.Models.Watch;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Kyoo.InternalAPI
|
namespace Kyoo.Controllers
|
||||||
{
|
{
|
||||||
public interface ITranscoder
|
public interface ITranscoder
|
||||||
{
|
{
|
22
Kyoo.Common/Kyoo.Common.csproj
Normal file
22
Kyoo.Common/Kyoo.Common.csproj
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||||
|
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||||
|
<Title>Kyoo.Common</Title>
|
||||||
|
<Authors>Anonymus Raccoon</Authors>
|
||||||
|
<Description>Base package to create plugins for Kyoo.</Description>
|
||||||
|
<PackageProjectUrl>https://github.com/AnonymusRaccoon/Kyoo</PackageProjectUrl>
|
||||||
|
<RepositoryUrl>https://github.com/AnonymusRaccoon/Kyoo</RepositoryUrl>
|
||||||
|
<Company>SDG</Company>
|
||||||
|
<PackageLicenseExpression>GPL-3.0-or-later</PackageLicenseExpression>
|
||||||
|
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
||||||
|
<PackageVersion>1.0.6</PackageVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||||
|
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.112" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
75
Kyoo.Common/Models/Collection.cs
Normal file
75
Kyoo.Common/Models/Collection.cs
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
using Kyoo.Controllers;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Kyoo.Models
|
||||||
|
{
|
||||||
|
public class Collection : IMergable<Collection>
|
||||||
|
|
||||||
|
{
|
||||||
|
[JsonIgnore] public long ID = -1;
|
||||||
|
public string Slug;
|
||||||
|
public string Name;
|
||||||
|
public string Poster;
|
||||||
|
public string Overview;
|
||||||
|
[JsonIgnore] public string ImgPrimary;
|
||||||
|
public IEnumerable<Show> Shows;
|
||||||
|
|
||||||
|
public Collection() { }
|
||||||
|
|
||||||
|
public Collection(long id, string slug, string name, string overview, string imgPrimary)
|
||||||
|
{
|
||||||
|
ID = id;
|
||||||
|
Slug = slug;
|
||||||
|
Name = name;
|
||||||
|
Overview = overview;
|
||||||
|
ImgPrimary = imgPrimary;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Collection FromReader(System.Data.SQLite.SQLiteDataReader reader)
|
||||||
|
{
|
||||||
|
Collection col = new Collection((long) reader["id"],
|
||||||
|
reader["slug"] as string,
|
||||||
|
reader["name"] as string,
|
||||||
|
reader["overview"] as string,
|
||||||
|
reader["imgPrimary"] as string);
|
||||||
|
col.Poster = "poster/" + col.Slug;
|
||||||
|
return col;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Show AsShow()
|
||||||
|
{
|
||||||
|
return new Show(-1, Slug, Name, null, null, Overview, null, null, null, null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection SetShows(ILibraryManager libraryManager)
|
||||||
|
{
|
||||||
|
Shows = libraryManager.GetShowsInCollection(ID);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection Merge(Collection collection)
|
||||||
|
{
|
||||||
|
if (collection == null)
|
||||||
|
return this;
|
||||||
|
if (ID == -1)
|
||||||
|
ID = collection.ID;
|
||||||
|
if (Slug == null)
|
||||||
|
Slug = collection.Slug;
|
||||||
|
if (Name == null)
|
||||||
|
Name = collection.Name;
|
||||||
|
if (Poster == null)
|
||||||
|
Poster = collection.Poster;
|
||||||
|
if (Overview == null)
|
||||||
|
Overview = collection.Overview;
|
||||||
|
if (ImgPrimary == null)
|
||||||
|
ImgPrimary = collection.ImgPrimary;
|
||||||
|
if (Shows == null)
|
||||||
|
Shows = collection.Shows;
|
||||||
|
else
|
||||||
|
Shows = Shows.Concat(collection.Shows);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,15 +3,15 @@ using System;
|
|||||||
|
|
||||||
namespace Kyoo.Models
|
namespace Kyoo.Models
|
||||||
{
|
{
|
||||||
public class Episode
|
public class Episode : IMergable<Episode>
|
||||||
{
|
{
|
||||||
[JsonIgnore] public long id;
|
[JsonIgnore] public long ID;
|
||||||
[JsonIgnore] public long ShowID;
|
[JsonIgnore] public long ShowID;
|
||||||
[JsonIgnore] public long SeasonID;
|
[JsonIgnore] public long SeasonID;
|
||||||
|
|
||||||
public long seasonNumber;
|
public long SeasonNumber;
|
||||||
public long episodeNumber;
|
public long EpisodeNumber;
|
||||||
public long absoluteNumber;
|
public long AbsoluteNumber;
|
||||||
[JsonIgnore] public string Path;
|
[JsonIgnore] public string Path;
|
||||||
public string Title;
|
public string Title;
|
||||||
public string Overview;
|
public string Overview;
|
||||||
@ -27,16 +27,24 @@ namespace Kyoo.Models
|
|||||||
public string Thumb; //Used in the API response only
|
public string Thumb; //Used in the API response only
|
||||||
|
|
||||||
|
|
||||||
public Episode() { }
|
public Episode()
|
||||||
|
{
|
||||||
|
ID = -1;
|
||||||
|
ShowID = -1;
|
||||||
|
SeasonID = -1;
|
||||||
|
SeasonNumber = -1;
|
||||||
|
EpisodeNumber = -1;
|
||||||
|
AbsoluteNumber = -1;
|
||||||
|
}
|
||||||
|
|
||||||
public Episode(long seasonNumber, long episodeNumber, long absoluteNumber, string title, string overview, DateTime? releaseDate, long runtime, string imgPrimary, string externalIDs)
|
public Episode(long seasonNumber, long episodeNumber, long absoluteNumber, string title, string overview, DateTime? releaseDate, long runtime, string imgPrimary, string externalIDs)
|
||||||
{
|
{
|
||||||
id = -1;
|
ID = -1;
|
||||||
ShowID = -1;
|
ShowID = -1;
|
||||||
SeasonID = -1;
|
SeasonID = -1;
|
||||||
this.seasonNumber = seasonNumber;
|
SeasonNumber = seasonNumber;
|
||||||
this.episodeNumber = episodeNumber;
|
EpisodeNumber = episodeNumber;
|
||||||
this.absoluteNumber = absoluteNumber;
|
AbsoluteNumber = absoluteNumber;
|
||||||
Title = title;
|
Title = title;
|
||||||
Overview = overview;
|
Overview = overview;
|
||||||
ReleaseDate = releaseDate;
|
ReleaseDate = releaseDate;
|
||||||
@ -47,12 +55,12 @@ namespace Kyoo.Models
|
|||||||
|
|
||||||
public Episode(long id, long showID, long seasonID, long seasonNumber, long episodeNumber, long absoluteNumber, string path, string title, string overview, DateTime? releaseDate, long runtime, string imgPrimary, string externalIDs)
|
public Episode(long id, long showID, long seasonID, long seasonNumber, long episodeNumber, long absoluteNumber, string path, string title, string overview, DateTime? releaseDate, long runtime, string imgPrimary, string externalIDs)
|
||||||
{
|
{
|
||||||
this.id = id;
|
ID = id;
|
||||||
ShowID = showID;
|
ShowID = showID;
|
||||||
SeasonID = seasonID;
|
SeasonID = seasonID;
|
||||||
this.seasonNumber = seasonNumber;
|
SeasonNumber = seasonNumber;
|
||||||
this.episodeNumber = episodeNumber;
|
EpisodeNumber = episodeNumber;
|
||||||
this.absoluteNumber = absoluteNumber;
|
AbsoluteNumber = absoluteNumber;
|
||||||
Path = path;
|
Path = path;
|
||||||
Title = title;
|
Title = title;
|
||||||
Overview = overview;
|
Overview = overview;
|
||||||
@ -82,7 +90,7 @@ namespace Kyoo.Models
|
|||||||
|
|
||||||
public Episode SetThumb(string showSlug)
|
public Episode SetThumb(string showSlug)
|
||||||
{
|
{
|
||||||
Link = GetSlug(showSlug, seasonNumber, episodeNumber);
|
Link = GetSlug(showSlug, SeasonNumber, EpisodeNumber);
|
||||||
Thumb = "thumb/" + Link;
|
Thumb = "thumb/" + Link;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@ -97,5 +105,37 @@ namespace Kyoo.Models
|
|||||||
{
|
{
|
||||||
return showSlug + "-s" + seasonNumber + "e" + episodeNumber;
|
return showSlug + "-s" + seasonNumber + "e" + episodeNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Episode Merge(Episode other)
|
||||||
|
{
|
||||||
|
if (other == null)
|
||||||
|
return this;
|
||||||
|
if (ID == -1)
|
||||||
|
ID = other.ID;
|
||||||
|
if (ShowID == -1)
|
||||||
|
ShowID = other.ShowID;
|
||||||
|
if (SeasonID == -1)
|
||||||
|
SeasonID = other.SeasonID;
|
||||||
|
if (SeasonNumber == -1)
|
||||||
|
SeasonNumber = other.SeasonNumber;
|
||||||
|
if (EpisodeNumber == -1)
|
||||||
|
EpisodeNumber = other.EpisodeNumber;
|
||||||
|
if (AbsoluteNumber == -1)
|
||||||
|
AbsoluteNumber = other.AbsoluteNumber;
|
||||||
|
if (Path == null)
|
||||||
|
Path = other.Path;
|
||||||
|
if (Title == null)
|
||||||
|
Title = other.Title;
|
||||||
|
if (Overview == null)
|
||||||
|
Overview = other.Overview;
|
||||||
|
if (ReleaseDate == null)
|
||||||
|
ReleaseDate = other.ReleaseDate;
|
||||||
|
if (Runtime == -1)
|
||||||
|
Runtime = other.Runtime;
|
||||||
|
if (ImgPrimary == null)
|
||||||
|
ImgPrimary = other.ImgPrimary;
|
||||||
|
ExternalIDs += '|' + other.ExternalIDs;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -4,7 +4,7 @@ namespace Kyoo.Models
|
|||||||
{
|
{
|
||||||
public class Genre
|
public class Genre
|
||||||
{
|
{
|
||||||
[JsonIgnore] public readonly long id;
|
[JsonIgnore] public readonly long ID;
|
||||||
public string Slug;
|
public string Slug;
|
||||||
public string Name;
|
public string Name;
|
||||||
|
|
||||||
@ -16,7 +16,7 @@ namespace Kyoo.Models
|
|||||||
|
|
||||||
public Genre(long id, string slug, string name)
|
public Genre(long id, string slug, string name)
|
||||||
{
|
{
|
||||||
this.id = id;
|
ID = id;
|
||||||
Slug = slug;
|
Slug = slug;
|
||||||
Name = name;
|
Name = name;
|
||||||
}
|
}
|
4
Kyoo.Common/Models/ImageTypes.cs
Normal file
4
Kyoo.Common/Models/ImageTypes.cs
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
namespace Kyoo.Models
|
||||||
|
{
|
||||||
|
public enum ImageType { Poster, Background, Thumbnail, Logo }
|
||||||
|
}
|
@ -4,17 +4,19 @@ namespace Kyoo.Models
|
|||||||
{
|
{
|
||||||
public class Library
|
public class Library
|
||||||
{
|
{
|
||||||
[JsonIgnore] public readonly long id;
|
[JsonIgnore] public readonly long ID;
|
||||||
public string Slug;
|
public string Slug;
|
||||||
public string Name;
|
public string Name;
|
||||||
public string Path;
|
public string[] Paths;
|
||||||
|
public string[] Providers;
|
||||||
|
|
||||||
public Library(long id, string slug, string name, string path)
|
public Library(long id, string slug, string name, string[] paths, string[] providers)
|
||||||
{
|
{
|
||||||
this.id = id;
|
ID = id;
|
||||||
Slug = slug;
|
Slug = slug;
|
||||||
Name = name;
|
Name = name;
|
||||||
Path = path;
|
Paths = paths;
|
||||||
|
Providers = providers;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Library FromReader(System.Data.SQLite.SQLiteDataReader reader)
|
public static Library FromReader(System.Data.SQLite.SQLiteDataReader reader)
|
||||||
@ -22,7 +24,8 @@ namespace Kyoo.Models
|
|||||||
return new Library((long)reader["id"],
|
return new Library((long)reader["id"],
|
||||||
reader["slug"] as string,
|
reader["slug"] as string,
|
||||||
reader["name"] as string,
|
reader["name"] as string,
|
||||||
reader["path"] as string);
|
(reader["path"] as string)?.Split('|'),
|
||||||
|
(reader["providers"] as string)?.Split('|'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,35 +2,37 @@
|
|||||||
|
|
||||||
namespace Kyoo.Models
|
namespace Kyoo.Models
|
||||||
{
|
{
|
||||||
public class People
|
public class People : IMergable<People>
|
||||||
{
|
{
|
||||||
[JsonIgnore] public long id;
|
[JsonIgnore] public long ID = -1;
|
||||||
public string slug;
|
public string Slug;
|
||||||
public string Name;
|
public string Name;
|
||||||
public string Role; //Dynamic data not stored as it in the database
|
public string Role; //Dynamic data not stored as it in the database
|
||||||
public string Type; //Dynamic data not stored as it in the database ---- Null for now
|
public string Type; //Dynamic data not stored as it in the database ---- Null for now
|
||||||
[JsonIgnore] public string imgPrimary;
|
[JsonIgnore] public string ImgPrimary;
|
||||||
|
|
||||||
public string externalIDs;
|
public string ExternalIDs;
|
||||||
|
|
||||||
|
public People() {}
|
||||||
|
|
||||||
public People(long id, string slug, string name, string imgPrimary, string externalIDs)
|
public People(long id, string slug, string name, string imgPrimary, string externalIDs)
|
||||||
{
|
{
|
||||||
this.id = id;
|
ID = id;
|
||||||
this.slug = slug;
|
Slug = slug;
|
||||||
Name = name;
|
Name = name;
|
||||||
this.imgPrimary = imgPrimary;
|
ImgPrimary = imgPrimary;
|
||||||
this.externalIDs = externalIDs;
|
ExternalIDs = externalIDs;
|
||||||
}
|
}
|
||||||
|
|
||||||
public People(long id, string slug, string name, string role, string type, string imgPrimary, string externalIDs)
|
public People(long id, string slug, string name, string role, string type, string imgPrimary, string externalIDs)
|
||||||
{
|
{
|
||||||
this.id = id;
|
ID = id;
|
||||||
this.slug = slug;
|
Slug = slug;
|
||||||
Name = name;
|
Name = name;
|
||||||
Role = role;
|
Role = role;
|
||||||
Type = type;
|
Type = type;
|
||||||
this.imgPrimary = imgPrimary;
|
ImgPrimary = imgPrimary;
|
||||||
this.externalIDs = externalIDs;
|
ExternalIDs = externalIDs;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static People FromReader(System.Data.SQLite.SQLiteDataReader reader)
|
public static People FromReader(System.Data.SQLite.SQLiteDataReader reader)
|
||||||
@ -52,5 +54,25 @@ namespace Kyoo.Models
|
|||||||
reader["imgPrimary"] as string,
|
reader["imgPrimary"] as string,
|
||||||
reader["externalIDs"] as string);
|
reader["externalIDs"] as string);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public People Merge(People other)
|
||||||
|
{
|
||||||
|
if (other == null)
|
||||||
|
return this;
|
||||||
|
if (ID == -1)
|
||||||
|
ID = other.ID;
|
||||||
|
if (Slug == null)
|
||||||
|
Slug = other.Slug;
|
||||||
|
if (Name == null)
|
||||||
|
Name = other.Name;
|
||||||
|
if (Role == null)
|
||||||
|
Role = other.Role;
|
||||||
|
if (Type == null)
|
||||||
|
Type = other.Type;
|
||||||
|
if (ImgPrimary == null)
|
||||||
|
ImgPrimary = other.ImgPrimary;
|
||||||
|
ExternalIDs += '|' + other.ExternalIDs;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
7
Kyoo.Common/Models/Plugin.cs
Normal file
7
Kyoo.Common/Models/Plugin.cs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
namespace Kyoo.Models
|
||||||
|
{
|
||||||
|
public interface IPlugin
|
||||||
|
{
|
||||||
|
public string Name { get; }
|
||||||
|
}
|
||||||
|
}
|
14
Kyoo.Common/Models/SearchResult.cs
Normal file
14
Kyoo.Common/Models/SearchResult.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Kyoo.Models
|
||||||
|
{
|
||||||
|
public class SearchResult
|
||||||
|
{
|
||||||
|
public string Query;
|
||||||
|
public IEnumerable<Show> Shows;
|
||||||
|
public IEnumerable<Episode> Episodes;
|
||||||
|
public IEnumerable<People> People;
|
||||||
|
public IEnumerable<Genre> Genres;
|
||||||
|
public IEnumerable<Studio> Studios;
|
||||||
|
}
|
||||||
|
}
|
@ -2,15 +2,15 @@
|
|||||||
|
|
||||||
namespace Kyoo.Models
|
namespace Kyoo.Models
|
||||||
{
|
{
|
||||||
public class Season
|
public class Season : IMergable<Season>
|
||||||
{
|
{
|
||||||
[JsonIgnore] public readonly long id;
|
[JsonIgnore] public readonly long ID = -1;
|
||||||
[JsonIgnore] public long ShowID;
|
[JsonIgnore] public long ShowID = -1;
|
||||||
|
|
||||||
public long seasonNumber;
|
public long SeasonNumber = -1;
|
||||||
public string Title;
|
public string Title;
|
||||||
public string Overview;
|
public string Overview;
|
||||||
public long? year;
|
public long? Year;
|
||||||
|
|
||||||
[JsonIgnore] public string ImgPrimary;
|
[JsonIgnore] public string ImgPrimary;
|
||||||
public string ExternalIDs;
|
public string ExternalIDs;
|
||||||
@ -19,12 +19,12 @@ namespace Kyoo.Models
|
|||||||
|
|
||||||
public Season(long id, long showID, long seasonNumber, string title, string overview, long? year, string imgPrimary, string externalIDs)
|
public Season(long id, long showID, long seasonNumber, string title, string overview, long? year, string imgPrimary, string externalIDs)
|
||||||
{
|
{
|
||||||
this.id = id;
|
ID = id;
|
||||||
ShowID = showID;
|
ShowID = showID;
|
||||||
this.seasonNumber = seasonNumber;
|
SeasonNumber = seasonNumber;
|
||||||
Title = title;
|
Title = title;
|
||||||
Overview = overview;
|
Overview = overview;
|
||||||
this.year = year;
|
Year = year;
|
||||||
ImgPrimary = imgPrimary;
|
ImgPrimary = imgPrimary;
|
||||||
ExternalIDs = externalIDs;
|
ExternalIDs = externalIDs;
|
||||||
}
|
}
|
||||||
@ -40,5 +40,25 @@ namespace Kyoo.Models
|
|||||||
reader["imgPrimary"] as string,
|
reader["imgPrimary"] as string,
|
||||||
reader["externalIDs"] as string);
|
reader["externalIDs"] as string);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Season Merge(Season other)
|
||||||
|
{
|
||||||
|
if (other == null)
|
||||||
|
return this;
|
||||||
|
if (ShowID == -1)
|
||||||
|
ShowID = other.ShowID;
|
||||||
|
if (SeasonNumber == -1)
|
||||||
|
SeasonNumber = other.SeasonNumber;
|
||||||
|
if (Title == null)
|
||||||
|
Title = other.Title;
|
||||||
|
if (Overview == null)
|
||||||
|
Overview = other.Overview;
|
||||||
|
if (Year == null)
|
||||||
|
Year = other.Year;
|
||||||
|
if (ImgPrimary == null)
|
||||||
|
ImgPrimary = other.ImgPrimary;
|
||||||
|
ExternalIDs += '|' + other.ExternalIDs;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,12 +1,14 @@
|
|||||||
using Kyoo.InternalAPI;
|
using System;
|
||||||
|
using Kyoo.Controllers;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace Kyoo.Models
|
namespace Kyoo.Models
|
||||||
{
|
{
|
||||||
public class Show
|
public class Show : IMergable<Show>
|
||||||
{
|
{
|
||||||
[JsonIgnore] public long id = -1;
|
[JsonIgnore] public long ID = -1;
|
||||||
|
|
||||||
public string Slug;
|
public string Slug;
|
||||||
public string Title;
|
public string Title;
|
||||||
@ -28,27 +30,21 @@ namespace Kyoo.Models
|
|||||||
public string ExternalIDs;
|
public string ExternalIDs;
|
||||||
|
|
||||||
//Used in the rest API excusively.
|
//Used in the rest API excusively.
|
||||||
public Studio studio;
|
public Studio Studio;
|
||||||
public IEnumerable<People> directors;
|
public IEnumerable<People> Directors;
|
||||||
public IEnumerable<People> people;
|
public IEnumerable<People> People;
|
||||||
public IEnumerable<Season> seasons;
|
public IEnumerable<Season> Seasons;
|
||||||
public bool IsCollection;
|
public bool IsCollection;
|
||||||
|
|
||||||
|
|
||||||
public string GetAliases()
|
public string GetAliases()
|
||||||
{
|
{
|
||||||
if (Aliases == null)
|
return Aliases == null ? null : string.Join('|', Aliases);
|
||||||
return null;
|
|
||||||
|
|
||||||
return string.Join('|', Aliases);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetGenres()
|
public string GetGenres()
|
||||||
{
|
{
|
||||||
if (Genres == null)
|
return Genres == null ? null : string.Join('|', Genres);
|
||||||
return null;
|
|
||||||
|
|
||||||
return string.Join('|', Genres);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -56,7 +52,7 @@ namespace Kyoo.Models
|
|||||||
|
|
||||||
public Show(long id, string slug, string title, IEnumerable<string> aliases, string path, string overview, string trailerUrl, IEnumerable<Genre> genres, Status? status, long? startYear, long? endYear, string externalIDs)
|
public Show(long id, string slug, string title, IEnumerable<string> aliases, string path, string overview, string trailerUrl, IEnumerable<Genre> genres, Status? status, long? startYear, long? endYear, string externalIDs)
|
||||||
{
|
{
|
||||||
this.id = id;
|
ID = id;
|
||||||
Slug = slug;
|
Slug = slug;
|
||||||
Title = title;
|
Title = title;
|
||||||
Aliases = aliases;
|
Aliases = aliases;
|
||||||
@ -73,7 +69,7 @@ namespace Kyoo.Models
|
|||||||
|
|
||||||
public Show(long id, string slug, string title, IEnumerable<string> aliases, string path, string overview, string trailerUrl, Status? status, long? startYear, long? endYear, string imgPrimary, string imgThumb, string imgLogo, string imgBackdrop, string externalIDs)
|
public Show(long id, string slug, string title, IEnumerable<string> aliases, string path, string overview, string trailerUrl, Status? status, long? startYear, long? endYear, string imgPrimary, string imgThumb, string imgLogo, string imgBackdrop, string externalIDs)
|
||||||
{
|
{
|
||||||
this.id = id;
|
ID = id;
|
||||||
Slug = slug;
|
Slug = slug;
|
||||||
Title = title;
|
Title = title;
|
||||||
Aliases = aliases;
|
Aliases = aliases;
|
||||||
@ -111,7 +107,7 @@ namespace Kyoo.Models
|
|||||||
return new Show((long)reader["id"],
|
return new Show((long)reader["id"],
|
||||||
reader["slug"] as string,
|
reader["slug"] as string,
|
||||||
reader["title"] as string,
|
reader["title"] as string,
|
||||||
(reader["aliases"] as string)?.Split('|') ?? null,
|
(reader["aliases"] as string)?.Split('|'),
|
||||||
reader["path"] as string,
|
reader["path"] as string,
|
||||||
reader["overview"] as string,
|
reader["overview"] as string,
|
||||||
reader["trailerUrl"] as string,
|
reader["trailerUrl"] as string,
|
||||||
@ -125,6 +121,16 @@ namespace Kyoo.Models
|
|||||||
reader["externalIDs"] as string);
|
reader["externalIDs"] as string);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string GetID(string provider)
|
||||||
|
{
|
||||||
|
if (ExternalIDs?.Contains(provider) != true)
|
||||||
|
return null;
|
||||||
|
int startIndex = ExternalIDs.IndexOf(provider, StringComparison.Ordinal) + provider.Length + 1; //The + 1 is for the '='
|
||||||
|
if (ExternalIDs.IndexOf('|', startIndex) == -1)
|
||||||
|
return ExternalIDs.Substring(startIndex);
|
||||||
|
return ExternalIDs.Substring(startIndex, ExternalIDs.IndexOf('|', startIndex) - startIndex);
|
||||||
|
}
|
||||||
|
|
||||||
public Show Set(string slug, string path)
|
public Show Set(string slug, string path)
|
||||||
{
|
{
|
||||||
Slug = slug;
|
Slug = slug;
|
||||||
@ -134,31 +140,73 @@ namespace Kyoo.Models
|
|||||||
|
|
||||||
public Show SetGenres(ILibraryManager manager)
|
public Show SetGenres(ILibraryManager manager)
|
||||||
{
|
{
|
||||||
Genres = manager.GetGenreForShow(id);
|
Genres = manager.GetGenreForShow(ID);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Show SetStudio(ILibraryManager manager)
|
public Show SetStudio(ILibraryManager manager)
|
||||||
{
|
{
|
||||||
studio = manager.GetStudio(id);
|
Studio = manager.GetStudio(ID);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Show SetDirectors(ILibraryManager manager)
|
public Show SetDirectors(ILibraryManager manager)
|
||||||
{
|
{
|
||||||
directors = manager.GetDirectors(id);
|
Directors = manager.GetDirectors(ID);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Show SetPeople(ILibraryManager manager)
|
public Show SetPeople(ILibraryManager manager)
|
||||||
{
|
{
|
||||||
people = manager.GetPeople(id);
|
People = manager.GetPeople(ID);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Show SetSeasons(ILibraryManager manager)
|
public Show SetSeasons(ILibraryManager manager)
|
||||||
{
|
{
|
||||||
seasons = manager.GetSeasons(id);
|
Seasons = manager.GetSeasons(ID);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Show Merge(Show other)
|
||||||
|
{
|
||||||
|
if (other == null)
|
||||||
|
return this;
|
||||||
|
if (ID == -1)
|
||||||
|
ID = other.ID;
|
||||||
|
if (Slug == null)
|
||||||
|
Slug = other.Slug;
|
||||||
|
if (Title == null)
|
||||||
|
Title = other.Title;
|
||||||
|
if (Aliases == null)
|
||||||
|
Aliases = other.Aliases;
|
||||||
|
else
|
||||||
|
Aliases = Aliases.Concat(other.Aliases);
|
||||||
|
if (Genres == null)
|
||||||
|
Genres = other.Genres;
|
||||||
|
else
|
||||||
|
Genres = Genres.Concat(other.Genres);
|
||||||
|
if (Path == null)
|
||||||
|
Path = other.Path;
|
||||||
|
if (Overview == null)
|
||||||
|
Overview = other.Overview;
|
||||||
|
if (TrailerUrl == null)
|
||||||
|
TrailerUrl = other.TrailerUrl;
|
||||||
|
if (Status == null)
|
||||||
|
Status = other.Status;
|
||||||
|
if (StartYear == null)
|
||||||
|
StartYear = other.StartYear;
|
||||||
|
if (EndYear == null)
|
||||||
|
EndYear = other.EndYear;
|
||||||
|
if (ImgPrimary == null)
|
||||||
|
ImgPrimary = other.ImgPrimary;
|
||||||
|
if (ImgThumb == null)
|
||||||
|
ImgThumb = other.ImgThumb;
|
||||||
|
if (ImgLogo == null)
|
||||||
|
ImgLogo = other.ImgLogo;
|
||||||
|
if (ImgBackdrop == null)
|
||||||
|
ImgBackdrop = other.ImgBackdrop;
|
||||||
|
ExternalIDs += '|' + other.ExternalIDs;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -4,7 +4,7 @@ namespace Kyoo.Models
|
|||||||
{
|
{
|
||||||
public class Studio
|
public class Studio
|
||||||
{
|
{
|
||||||
[JsonIgnore] public readonly long id;
|
[JsonIgnore] public readonly long ID = -1;
|
||||||
public string Slug;
|
public string Slug;
|
||||||
public string Name;
|
public string Name;
|
||||||
|
|
||||||
@ -16,7 +16,7 @@ namespace Kyoo.Models
|
|||||||
|
|
||||||
public Studio(long id, string slug, string name)
|
public Studio(long id, string slug, string name)
|
||||||
{
|
{
|
||||||
this.id = id;
|
ID = id;
|
||||||
Slug = slug;
|
Slug = slug;
|
||||||
Name = name;
|
Name = name;
|
||||||
}
|
}
|
@ -58,7 +58,7 @@ namespace Kyoo.Models
|
|||||||
public string DisplayName;
|
public string DisplayName;
|
||||||
public string Link;
|
public string Link;
|
||||||
|
|
||||||
[JsonIgnore] public long episodeID;
|
[JsonIgnore] public long EpisodeID;
|
||||||
[JsonIgnore] public bool IsExternal;
|
[JsonIgnore] public bool IsExternal;
|
||||||
|
|
||||||
public Track(StreamType type, string title, string language, bool isDefault, bool isForced, string codec, bool isExternal, string path)
|
public Track(StreamType type, string title, string language, bool isDefault, bool isForced, string codec, bool isExternal, string path)
|
||||||
@ -94,7 +94,7 @@ namespace Kyoo.Models
|
|||||||
if (language == "fre")
|
if (language == "fre")
|
||||||
language = "fra";
|
language = "fra";
|
||||||
|
|
||||||
DisplayName = CultureInfo.GetCultures(CultureTypes.NeutralCultures).FirstOrDefault(x => x.ThreeLetterISOLanguageName == language)?.DisplayName ?? language;
|
DisplayName = CultureInfo.GetCultures(CultureTypes.NeutralCultures).FirstOrDefault(x => x.ThreeLetterISOLanguageName == language)?.EnglishName ?? language;
|
||||||
Link = "/subtitle/" + episodeSlug + "." + Language;
|
Link = "/subtitle/" + episodeSlug + "." + Language;
|
||||||
|
|
||||||
if (IsForced)
|
if (IsForced)
|
@ -1,4 +1,4 @@
|
|||||||
using Kyoo.InternalAPI;
|
using Kyoo.Controllers;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@ -7,33 +7,33 @@ namespace Kyoo.Models
|
|||||||
{
|
{
|
||||||
public class WatchItem
|
public class WatchItem
|
||||||
{
|
{
|
||||||
[JsonIgnore] public readonly long episodeID;
|
[JsonIgnore] public readonly long EpisodeID = -1;
|
||||||
|
|
||||||
public string ShowTitle;
|
public string ShowTitle;
|
||||||
public string ShowSlug;
|
public string ShowSlug;
|
||||||
public long seasonNumber;
|
public long SeasonNumber;
|
||||||
public long episodeNumber;
|
public long EpisodeNumber;
|
||||||
public string Title;
|
public string Title;
|
||||||
public string Link;
|
public string Link;
|
||||||
public DateTime? ReleaseDate;
|
public DateTime? ReleaseDate;
|
||||||
[JsonIgnore] public string Path;
|
[JsonIgnore] public string Path;
|
||||||
public string previousEpisode;
|
public string PreviousEpisode;
|
||||||
public Episode nextEpisode;
|
public Episode NextEpisode;
|
||||||
|
|
||||||
public string container;
|
public string Container;
|
||||||
public Track video;
|
public Track Video;
|
||||||
public IEnumerable<Track> audios;
|
public IEnumerable<Track> Audios;
|
||||||
public IEnumerable<Track> subtitles;
|
public IEnumerable<Track> Subtitles;
|
||||||
|
|
||||||
public WatchItem() { }
|
public WatchItem() { }
|
||||||
|
|
||||||
public WatchItem(long episodeID, string showTitle, string showSlug, long seasonNumber, long episodeNumber, string title, DateTime? releaseDate, string path)
|
public WatchItem(long episodeID, string showTitle, string showSlug, long seasonNumber, long episodeNumber, string title, DateTime? releaseDate, string path)
|
||||||
{
|
{
|
||||||
this.episodeID = episodeID;
|
EpisodeID = episodeID;
|
||||||
ShowTitle = showTitle;
|
ShowTitle = showTitle;
|
||||||
ShowSlug = showSlug;
|
ShowSlug = showSlug;
|
||||||
this.seasonNumber = seasonNumber;
|
SeasonNumber = seasonNumber;
|
||||||
this.episodeNumber = episodeNumber;
|
EpisodeNumber = episodeNumber;
|
||||||
Title = title;
|
Title = title;
|
||||||
ReleaseDate = releaseDate;
|
ReleaseDate = releaseDate;
|
||||||
Path = path;
|
Path = path;
|
||||||
@ -43,8 +43,8 @@ namespace Kyoo.Models
|
|||||||
|
|
||||||
public WatchItem(long episodeID, string showTitle, string showSlug, long seasonNumber, long episodeNumber, string title, DateTime? releaseDate, string path, Track[] audios, Track[] subtitles) : this(episodeID, showTitle, showSlug, seasonNumber, episodeNumber, title, releaseDate, path)
|
public WatchItem(long episodeID, string showTitle, string showSlug, long seasonNumber, long episodeNumber, string title, DateTime? releaseDate, string path, Track[] audios, Track[] subtitles) : this(episodeID, showTitle, showSlug, seasonNumber, episodeNumber, title, releaseDate, path)
|
||||||
{
|
{
|
||||||
this.audios = audios;
|
Audios = audios;
|
||||||
this.subtitles = subtitles;
|
Subtitles = subtitles;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static WatchItem FromReader(System.Data.SQLite.SQLiteDataReader reader)
|
public static WatchItem FromReader(System.Data.SQLite.SQLiteDataReader reader)
|
||||||
@ -61,35 +61,35 @@ namespace Kyoo.Models
|
|||||||
|
|
||||||
public WatchItem SetStreams(ILibraryManager libraryManager)
|
public WatchItem SetStreams(ILibraryManager libraryManager)
|
||||||
{
|
{
|
||||||
(Track video, IEnumerable<Track> audios, IEnumerable<Track> subtitles) streams = libraryManager.GetStreams(episodeID, Link);
|
(Track video, IEnumerable<Track> audios, IEnumerable<Track> subtitles) streams = libraryManager.GetStreams(EpisodeID, Link);
|
||||||
|
|
||||||
container = Path.Substring(Path.LastIndexOf('.') + 1);
|
Container = Path.Substring(Path.LastIndexOf('.') + 1);
|
||||||
video = streams.video;
|
Video = streams.video;
|
||||||
audios = streams.audios;
|
Audios = streams.audios;
|
||||||
subtitles = streams.subtitles;
|
Subtitles = streams.subtitles;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public WatchItem SetPrevious(ILibraryManager libraryManager)
|
public WatchItem SetPrevious(ILibraryManager libraryManager)
|
||||||
{
|
{
|
||||||
long lastEp = episodeNumber - 1;
|
long lastEp = EpisodeNumber - 1;
|
||||||
if(lastEp > 0)
|
if(lastEp > 0)
|
||||||
previousEpisode = ShowSlug + "-s" + seasonNumber + "e" + lastEp;
|
PreviousEpisode = ShowSlug + "-s" + SeasonNumber + "e" + lastEp;
|
||||||
else if(seasonNumber > 1)
|
else if(SeasonNumber > 1)
|
||||||
{
|
{
|
||||||
int seasonCount = libraryManager.GetSeasonCount(ShowSlug, seasonNumber - 1);
|
int seasonCount = libraryManager.GetSeasonCount(ShowSlug, SeasonNumber - 1);
|
||||||
previousEpisode = ShowSlug + "-s" + (seasonNumber - 1) + "e" + seasonCount;
|
PreviousEpisode = ShowSlug + "-s" + (SeasonNumber - 1) + "e" + seasonCount;
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public WatchItem SetNext(ILibraryManager libraryManager)
|
public WatchItem SetNext(ILibraryManager libraryManager)
|
||||||
{
|
{
|
||||||
long seasonCount = libraryManager.GetSeasonCount(ShowSlug, seasonNumber);
|
long seasonCount = libraryManager.GetSeasonCount(ShowSlug, SeasonNumber);
|
||||||
if (episodeNumber >= seasonCount)
|
if (EpisodeNumber >= seasonCount)
|
||||||
nextEpisode = libraryManager.GetEpisode(ShowSlug, seasonNumber + 1, 1);
|
NextEpisode = libraryManager.GetEpisode(ShowSlug, SeasonNumber + 1, 1);
|
||||||
else
|
else
|
||||||
nextEpisode = libraryManager.GetEpisode(ShowSlug, seasonNumber, episodeNumber + 1);
|
NextEpisode = libraryManager.GetEpisode(ShowSlug, SeasonNumber, EpisodeNumber + 1);
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
62
Kyoo.Common/Utility.cs
Normal file
62
Kyoo.Common/Utility.cs
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using Kyoo.Models;
|
||||||
|
|
||||||
|
namespace Kyoo
|
||||||
|
{
|
||||||
|
public interface IMergable<T>
|
||||||
|
{
|
||||||
|
public T Merge(T other);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Utility
|
||||||
|
{
|
||||||
|
public static string ToSlug(string name)
|
||||||
|
{
|
||||||
|
if (name == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
//First to lower case
|
||||||
|
name = name.ToLowerInvariant();
|
||||||
|
|
||||||
|
//Remove all accents
|
||||||
|
//var bytes = Encoding.GetEncoding("Cyrillic").GetBytes(showTitle);
|
||||||
|
//showTitle = Encoding.ASCII.GetString(bytes);
|
||||||
|
|
||||||
|
//Replace spaces
|
||||||
|
name = Regex.Replace(name, @"\s", "-", RegexOptions.Compiled);
|
||||||
|
|
||||||
|
//Remove invalid chars
|
||||||
|
name = Regex.Replace(name, @"[^\w\s\p{Pd}]", "", RegexOptions.Compiled);
|
||||||
|
|
||||||
|
//Trim dashes from end
|
||||||
|
name = name.Trim('-', '_');
|
||||||
|
|
||||||
|
//Replace double occurences of - or \_
|
||||||
|
name = Regex.Replace(name, @"([-_]){2,}", "$1", RegexOptions.Compiled);
|
||||||
|
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void SetImage(Show show, string imgUrl, ImageType type)
|
||||||
|
{
|
||||||
|
switch(type)
|
||||||
|
{
|
||||||
|
case ImageType.Poster:
|
||||||
|
show.ImgPrimary = imgUrl;
|
||||||
|
break;
|
||||||
|
case ImageType.Thumbnail:
|
||||||
|
show.ImgThumb = imgUrl;
|
||||||
|
break;
|
||||||
|
case ImageType.Logo:
|
||||||
|
show.ImgLogo = imgUrl;
|
||||||
|
break;
|
||||||
|
case ImageType.Background:
|
||||||
|
show.ImgBackdrop = imgUrl;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
6
Kyoo.sln
6
Kyoo.sln
@ -1,6 +1,8 @@
|
|||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kyoo", "Kyoo\Kyoo.csproj", "{0F8275B6-C7DD-42DF-A168-755C81B1C329}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kyoo", "Kyoo\Kyoo.csproj", "{0F8275B6-C7DD-42DF-A168-755C81B1C329}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.Common", "Kyoo.Common\Kyoo.Common.csproj", "{BAB2CAE1-AC28-4509-AA3E-8DC75BD59220}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@ -11,5 +13,9 @@ Global
|
|||||||
{0F8275B6-C7DD-42DF-A168-755C81B1C329}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{0F8275B6-C7DD-42DF-A168-755C81B1C329}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{0F8275B6-C7DD-42DF-A168-755C81B1C329}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{0F8275B6-C7DD-42DF-A168-755C81B1C329}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{0F8275B6-C7DD-42DF-A168-755C81B1C329}.Release|Any CPU.Build.0 = Release|Any CPU
|
{0F8275B6-C7DD-42DF-A168-755C81B1C329}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{BAB2CAE1-AC28-4509-AA3E-8DC75BD59220}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{BAB2CAE1-AC28-4509-AA3E-8DC75BD59220}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{BAB2CAE1-AC28-4509-AA3E-8DC75BD59220}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{BAB2CAE1-AC28-4509-AA3E-8DC75BD59220}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
@ -4,4 +4,8 @@
|
|||||||
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForOtherTypes/@EntryValue">UseExplicitType</s:String>
|
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForOtherTypes/@EntryValue">UseExplicitType</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForSimpleTypes/@EntryValue">UseExplicitType</s:String>
|
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForSimpleTypes/@EntryValue">UseExplicitType</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=API/@EntryIndexedValue">API</s:String>
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=API/@EntryIndexedValue">API</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=DB/@EntryIndexedValue">DB</s:String></wpf:ResourceDictionary>
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=DB/@EntryIndexedValue">DB</s:String>
|
||||||
|
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
269
Kyoo/Controllers/Crawler.cs
Normal file
269
Kyoo/Controllers/Crawler.cs
Normal file
@ -0,0 +1,269 @@
|
|||||||
|
using System;
|
||||||
|
using Kyoo.Models;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Kyoo.Models.Watch;
|
||||||
|
|
||||||
|
namespace Kyoo.Controllers
|
||||||
|
{
|
||||||
|
public class Crawler : ICrawler
|
||||||
|
{
|
||||||
|
private bool isRunning;
|
||||||
|
private readonly CancellationTokenSource cancellation;
|
||||||
|
|
||||||
|
private readonly ILibraryManager libraryManager;
|
||||||
|
private readonly IProviderManager metadataProvider;
|
||||||
|
private readonly ITranscoder transcoder;
|
||||||
|
private readonly IConfiguration config;
|
||||||
|
|
||||||
|
public Crawler(ILibraryManager libraryManager, IProviderManager metadataProvider, ITranscoder transcoder, IConfiguration configuration)
|
||||||
|
{
|
||||||
|
this.libraryManager = libraryManager;
|
||||||
|
this.metadataProvider = metadataProvider;
|
||||||
|
this.transcoder = transcoder;
|
||||||
|
config = configuration;
|
||||||
|
cancellation = new CancellationTokenSource();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Start()
|
||||||
|
{
|
||||||
|
if (isRunning)
|
||||||
|
return;
|
||||||
|
isRunning = true;
|
||||||
|
StartAsync(cancellation.Token);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Cancel()
|
||||||
|
{
|
||||||
|
if (!isRunning)
|
||||||
|
return;
|
||||||
|
isRunning = false;
|
||||||
|
cancellation.Cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void StartAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
IEnumerable<Episode> episodes = libraryManager.GetAllEpisodes();
|
||||||
|
IEnumerable<Library> libraries = libraryManager.GetLibraries();
|
||||||
|
|
||||||
|
Debug.WriteLine("&Crawler started");
|
||||||
|
foreach (Episode episode in episodes)
|
||||||
|
{
|
||||||
|
if (!File.Exists(episode.Path))
|
||||||
|
libraryManager.RemoveEpisode(episode);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (Library library in libraries)
|
||||||
|
await Scan(library, cancellationToken);
|
||||||
|
|
||||||
|
isRunning = false;
|
||||||
|
Debug.WriteLine("&Crawler stopped");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Scan(Library library, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Scanning library {library.Name} at {string.Concat(library.Paths)}");
|
||||||
|
foreach (string path in library.Paths)
|
||||||
|
{
|
||||||
|
foreach (string file in Directory.GetFiles(path, "*", SearchOption.AllDirectories))
|
||||||
|
{
|
||||||
|
if (cancellationToken.IsCancellationRequested)
|
||||||
|
return;
|
||||||
|
if (!IsVideo(file))
|
||||||
|
continue;
|
||||||
|
string relativePath = file.Substring(path.Length);
|
||||||
|
await RegisterFile(file, relativePath, library);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RegisterFile(string path, string relativePath, Library library)
|
||||||
|
{
|
||||||
|
if (!libraryManager.IsEpisodeRegistered(path))
|
||||||
|
{
|
||||||
|
string patern = config.GetValue<string>("regex");
|
||||||
|
Regex regex = new Regex(patern, RegexOptions.IgnoreCase);
|
||||||
|
Match match = regex.Match(relativePath);
|
||||||
|
|
||||||
|
string showPath = Path.GetDirectoryName(path);
|
||||||
|
string collectionName = match.Groups["Collection"]?.Value;
|
||||||
|
string showName = match.Groups["ShowTitle"].Value;
|
||||||
|
bool seasonSuccess = long.TryParse(match.Groups["Season"].Value, out long seasonNumber);
|
||||||
|
bool episodeSucess = long.TryParse(match.Groups["Episode"].Value, out long episodeNumber);
|
||||||
|
long absoluteNumber = -1;
|
||||||
|
|
||||||
|
Console.WriteLine("&Registering episode at: " + path);
|
||||||
|
if (!seasonSuccess || !episodeSucess)
|
||||||
|
{
|
||||||
|
//Considering that the episode is using absolute path.
|
||||||
|
seasonNumber = -1;
|
||||||
|
episodeNumber = -1;
|
||||||
|
|
||||||
|
regex = new Regex(config.GetValue<string>("absoluteRegex"));
|
||||||
|
match = regex.Match(relativePath);
|
||||||
|
|
||||||
|
showName = match.Groups["ShowTitle"].Value;
|
||||||
|
bool absoluteSucess = long.TryParse(match.Groups["AbsoluteNumber"].Value, out absoluteNumber);
|
||||||
|
|
||||||
|
if (!absoluteSucess)
|
||||||
|
{
|
||||||
|
Console.WriteLine("&Couldn't find basic data for the episode (regexs didn't match) " + relativePath);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Show show = await RegisterOrGetShow(collectionName, showName, showPath, library);
|
||||||
|
if (show != null)
|
||||||
|
await RegisterEpisode(show, seasonNumber, episodeNumber, absoluteNumber, path, library);
|
||||||
|
else
|
||||||
|
Console.Error.WriteLine($"Coudld not get informations about the show ${showName}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<Show> RegisterOrGetShow(string collectionName, string showTitle, string showPath, Library library)
|
||||||
|
{
|
||||||
|
string showProviderIDs;
|
||||||
|
|
||||||
|
if (!libraryManager.IsShowRegistered(showPath, out long showID))
|
||||||
|
{
|
||||||
|
Show show = await metadataProvider.GetShowFromName(showTitle, library);
|
||||||
|
show.Path = showPath;
|
||||||
|
show.Title = show.Title ?? showTitle;
|
||||||
|
show.Slug = show.Slug ?? Utility.ToSlug(showTitle);
|
||||||
|
showProviderIDs = show.ExternalIDs;
|
||||||
|
showID = libraryManager.RegisterShow(show);
|
||||||
|
|
||||||
|
if (showID == -1)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
libraryManager.RegisterInLibrary(showID, library);
|
||||||
|
if (!string.IsNullOrEmpty(collectionName))
|
||||||
|
{
|
||||||
|
if (!libraryManager.IsCollectionRegistered(Utility.ToSlug(collectionName), out long collectionID))
|
||||||
|
{
|
||||||
|
Collection collection = await metadataProvider.GetCollectionFromName(collectionName, library);
|
||||||
|
collection.Name = collection.Name ?? collectionName;
|
||||||
|
collectionID = libraryManager.RegisterCollection(collection);
|
||||||
|
}
|
||||||
|
libraryManager.AddShowToCollection(showID, collectionID);
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerable<People> actors = await metadataProvider.GetPeople(show, library);
|
||||||
|
libraryManager.RegisterShowPeople(showID, actors);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
showProviderIDs = libraryManager.GetShowExternalIDs(showID);
|
||||||
|
|
||||||
|
return new Show { ID = showID, ExternalIDs = showProviderIDs, Title = showTitle };
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<long> RegisterSeason(Show show, long seasonNumber, Library library)
|
||||||
|
{
|
||||||
|
if (!libraryManager.IsSeasonRegistered(show.ID, seasonNumber, out long seasonID))
|
||||||
|
{
|
||||||
|
Season season = await metadataProvider.GetSeason(show, seasonNumber, library);
|
||||||
|
season.ShowID = show.ID;
|
||||||
|
season.SeasonNumber = season.SeasonNumber == -1 ? seasonNumber : season.SeasonNumber;
|
||||||
|
season.Title ??= $"Season {season.SeasonNumber}";
|
||||||
|
seasonID = libraryManager.RegisterSeason(season);
|
||||||
|
}
|
||||||
|
|
||||||
|
return seasonID;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RegisterEpisode(Show show, long seasonNumber, long episodeNumber, long absoluteNumber, string episodePath, Library library)
|
||||||
|
{
|
||||||
|
long seasonID = -1;
|
||||||
|
if (seasonNumber != -1)
|
||||||
|
seasonID = await RegisterSeason(show, seasonNumber, library);
|
||||||
|
|
||||||
|
Episode episode = await metadataProvider.GetEpisode(show, seasonNumber, episodeNumber, absoluteNumber, library);
|
||||||
|
episode.ShowID = show.ID;
|
||||||
|
episode.Path = episodePath;
|
||||||
|
episode.SeasonNumber = episode.SeasonNumber != -1 ? episode.SeasonNumber : seasonNumber;
|
||||||
|
episode.EpisodeNumber = episode.EpisodeNumber != -1 ? episode.EpisodeNumber : episodeNumber;
|
||||||
|
episode.AbsoluteNumber = episode.AbsoluteNumber != -1 ? episode.AbsoluteNumber : absoluteNumber;
|
||||||
|
|
||||||
|
if (seasonID == -1)
|
||||||
|
seasonID = await RegisterSeason(show, seasonNumber, library);
|
||||||
|
episode.SeasonID = seasonID;
|
||||||
|
episode.ID = libraryManager.RegisterEpisode(episode);
|
||||||
|
|
||||||
|
Track[] tracks = await transcoder.GetTrackInfo(episode.Path);
|
||||||
|
int subcount = 0;
|
||||||
|
foreach (Track track in tracks)
|
||||||
|
{
|
||||||
|
if (track.Type == StreamType.Subtitle)
|
||||||
|
{
|
||||||
|
subcount++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
track.EpisodeID = episode.ID;
|
||||||
|
libraryManager.RegisterTrack(track);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (episode.Path.EndsWith(".mkv") && CountExtractedSubtitles(episode) != subcount)
|
||||||
|
{
|
||||||
|
Track[] subtitles = await transcoder.ExtractSubtitles(episode.Path);
|
||||||
|
if (subtitles != null)
|
||||||
|
{
|
||||||
|
foreach (Track track in subtitles)
|
||||||
|
{
|
||||||
|
track.EpisodeID = episode.ID;
|
||||||
|
libraryManager.RegisterTrack(track);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int CountExtractedSubtitles(Episode episode)
|
||||||
|
{
|
||||||
|
string path = Path.Combine(Path.GetDirectoryName(episode.Path), "Subtitles");
|
||||||
|
int subcount = 0;
|
||||||
|
|
||||||
|
if (!Directory.Exists(path))
|
||||||
|
return 0;
|
||||||
|
foreach (string sub in Directory.EnumerateFiles(path, "", SearchOption.AllDirectories))
|
||||||
|
{
|
||||||
|
string episodeLink = Path.GetFileNameWithoutExtension(episode.Path);
|
||||||
|
|
||||||
|
if (!sub.Contains(episodeLink))
|
||||||
|
continue;
|
||||||
|
string language = sub.Substring(Path.GetDirectoryName(sub).Length + episodeLink.Length + 2, 3);
|
||||||
|
bool isDefault = sub.Contains("default");
|
||||||
|
bool isForced = sub.Contains("forced");
|
||||||
|
Track track = new Track(StreamType.Subtitle, null, language, isDefault, isForced, null, false, sub) { EpisodeID = episode.ID };
|
||||||
|
|
||||||
|
if (Path.GetExtension(sub) == ".ass")
|
||||||
|
track.Codec = "ass";
|
||||||
|
else if (Path.GetExtension(sub) == ".srt")
|
||||||
|
track.Codec = "subrip";
|
||||||
|
else
|
||||||
|
track.Codec = null;
|
||||||
|
libraryManager.RegisterTrack(track);
|
||||||
|
subcount++;
|
||||||
|
}
|
||||||
|
return subcount;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly string[] VideoExtensions = { ".webm", ".mkv", ".flv", ".vob", ".ogg", ".ogv", ".avi", ".mts", ".m2ts", ".ts", ".mov", ".qt", ".asf", ".mp4", ".m4p", ".m4v", ".mpg", ".mp2", ".mpeg", ".mpe", ".mpv", ".m2v", ".3gp", ".3g2" };
|
||||||
|
|
||||||
|
private static bool IsVideo(string filePath)
|
||||||
|
{
|
||||||
|
return VideoExtensions.Contains(Path.GetExtension(filePath));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Task StopAsync()
|
||||||
|
{
|
||||||
|
cancellation.Cancel();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -7,7 +7,7 @@ using System.Data.SQLite;
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
namespace Kyoo.InternalAPI
|
namespace Kyoo.Controllers
|
||||||
{
|
{
|
||||||
public class LibraryManager : ILibraryManager
|
public class LibraryManager : ILibraryManager
|
||||||
{
|
{
|
||||||
@ -18,10 +18,9 @@ namespace Kyoo.InternalAPI
|
|||||||
{
|
{
|
||||||
string databasePath = configuration.GetValue<string>("databasePath");
|
string databasePath = configuration.GetValue<string>("databasePath");
|
||||||
|
|
||||||
Debug.WriteLine("&Library Manager init, databasePath: " + databasePath);
|
|
||||||
if (!File.Exists(databasePath))
|
if (!File.Exists(databasePath))
|
||||||
{
|
{
|
||||||
Debug.WriteLine("&Database doesn't exist, creating one.");
|
Console.WriteLine($"Creating the database at {databasePath}.");
|
||||||
|
|
||||||
if (!Directory.Exists(Path.GetDirectoryName(databasePath)))
|
if (!Directory.Exists(Path.GetDirectoryName(databasePath)))
|
||||||
Directory.CreateDirectory(databasePath);
|
Directory.CreateDirectory(databasePath);
|
||||||
@ -29,7 +28,7 @@ namespace Kyoo.InternalAPI
|
|||||||
sqlConnection = new SQLiteConnection($"Data Source={databasePath};Version=3");
|
sqlConnection = new SQLiteConnection($"Data Source={databasePath};Version=3");
|
||||||
sqlConnection.Open();
|
sqlConnection.Open();
|
||||||
|
|
||||||
string createStatement = @"CREATE TABLE shows(
|
const string createStatement = @"CREATE TABLE shows(
|
||||||
id INTEGER PRIMARY KEY UNIQUE,
|
id INTEGER PRIMARY KEY UNIQUE,
|
||||||
slug TEXT UNIQUE,
|
slug TEXT UNIQUE,
|
||||||
title TEXT,
|
title TEXT,
|
||||||
@ -92,7 +91,8 @@ namespace Kyoo.InternalAPI
|
|||||||
id INTEGER PRIMARY KEY UNIQUE,
|
id INTEGER PRIMARY KEY UNIQUE,
|
||||||
slug TEXT UNIQUE,
|
slug TEXT UNIQUE,
|
||||||
name TEXT,
|
name TEXT,
|
||||||
path TEXT
|
path TEXT,
|
||||||
|
providers TEXT
|
||||||
);
|
);
|
||||||
CREATE TABLE librariesLinks(
|
CREATE TABLE librariesLinks(
|
||||||
libraryID INTEGER,
|
libraryID INTEGER,
|
||||||
@ -209,10 +209,11 @@ namespace Kyoo.InternalAPI
|
|||||||
|
|
||||||
public string GetShowExternalIDs(long showID)
|
public string GetShowExternalIDs(long showID)
|
||||||
{
|
{
|
||||||
string query = string.Format("SELECT * FROM shows WHERE id = {0};", showID);
|
string query = "SELECT * FROM shows WHERE id = $showID;";
|
||||||
|
|
||||||
using (SQLiteCommand cmd = new SQLiteCommand(query, sqlConnection))
|
using (SQLiteCommand cmd = new SQLiteCommand(query, sqlConnection))
|
||||||
{
|
{
|
||||||
|
cmd.Parameters.AddWithValue("$showID", showID);
|
||||||
SQLiteDataReader reader = cmd.ExecuteReader();
|
SQLiteDataReader reader = cmd.ExecuteReader();
|
||||||
|
|
||||||
if (reader.Read())
|
if (reader.Read())
|
||||||
@ -835,7 +836,7 @@ namespace Kyoo.InternalAPI
|
|||||||
Genre existingGenre = GetGenreBySlug(genre.Slug);
|
Genre existingGenre = GetGenreBySlug(genre.Slug);
|
||||||
|
|
||||||
if (existingGenre != null)
|
if (existingGenre != null)
|
||||||
return existingGenre.id;
|
return existingGenre.ID;
|
||||||
|
|
||||||
string query = "INSERT INTO genres (slug, name) VALUES($slug, $name);";
|
string query = "INSERT INTO genres (slug, name) VALUES($slug, $name);";
|
||||||
using (SQLiteCommand cmd = new SQLiteCommand(query, sqlConnection))
|
using (SQLiteCommand cmd = new SQLiteCommand(query, sqlConnection))
|
||||||
@ -864,7 +865,7 @@ namespace Kyoo.InternalAPI
|
|||||||
Studio existingStudio = GetStudioBySlug(studio.Slug);
|
Studio existingStudio = GetStudioBySlug(studio.Slug);
|
||||||
|
|
||||||
if (existingStudio != null)
|
if (existingStudio != null)
|
||||||
return existingStudio.id;
|
return existingStudio.ID;
|
||||||
|
|
||||||
string query = "INSERT INTO studios (slug, name) VALUES($slug, $name);";
|
string query = "INSERT INTO studios (slug, name) VALUES($slug, $name);";
|
||||||
using (SQLiteCommand cmd = new SQLiteCommand(query, sqlConnection))
|
using (SQLiteCommand cmd = new SQLiteCommand(query, sqlConnection))
|
||||||
@ -890,20 +891,20 @@ namespace Kyoo.InternalAPI
|
|||||||
|
|
||||||
public long GetOrCreatePeople(People people)
|
public long GetOrCreatePeople(People people)
|
||||||
{
|
{
|
||||||
People existingPeople = GetPeopleBySlug(people.slug);
|
People existingPeople = GetPeopleBySlug(people.Slug);
|
||||||
|
|
||||||
if (existingPeople != null)
|
if (existingPeople != null)
|
||||||
return existingPeople.id;
|
return existingPeople.ID;
|
||||||
|
|
||||||
string query = "INSERT INTO people (slug, name, imgPrimary, externalIDs) VALUES($slug, $name, $imgPrimary, $externalIDs);";
|
string query = "INSERT INTO people (slug, name, imgPrimary, externalIDs) VALUES($slug, $name, $imgPrimary, $externalIDs);";
|
||||||
using (SQLiteCommand cmd = new SQLiteCommand(query, sqlConnection))
|
using (SQLiteCommand cmd = new SQLiteCommand(query, sqlConnection))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
cmd.Parameters.AddWithValue("$slug", people.slug);
|
cmd.Parameters.AddWithValue("$slug", people.Slug);
|
||||||
cmd.Parameters.AddWithValue("$name", people.Name);
|
cmd.Parameters.AddWithValue("$name", people.Name);
|
||||||
cmd.Parameters.AddWithValue("$imgPrimary", people.imgPrimary);
|
cmd.Parameters.AddWithValue("$imgPrimary", people.ImgPrimary);
|
||||||
cmd.Parameters.AddWithValue("$externalIDs", people.externalIDs);
|
cmd.Parameters.AddWithValue("$externalIDs", people.ExternalIDs);
|
||||||
cmd.ExecuteNonQuery();
|
cmd.ExecuteNonQuery();
|
||||||
|
|
||||||
cmd.CommandText = "SELECT LAST_INSERT_ROWID()";
|
cmd.CommandText = "SELECT LAST_INSERT_ROWID()";
|
||||||
@ -913,7 +914,7 @@ namespace Kyoo.InternalAPI
|
|||||||
{
|
{
|
||||||
Console.Error.WriteLine("SQL error while trying to insert a people ({0}).", people.Name);
|
Console.Error.WriteLine("SQL error while trying to insert a people ({0}).", people.Name);
|
||||||
cmd.CommandText = "SELECT * FROM people WHERE slug = $slug";
|
cmd.CommandText = "SELECT * FROM people WHERE slug = $slug";
|
||||||
cmd.Parameters.AddWithValue("$slug", people.slug);
|
cmd.Parameters.AddWithValue("$slug", people.Slug);
|
||||||
return (long)cmd.ExecuteScalar();
|
return (long)cmd.ExecuteScalar();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -949,13 +950,14 @@ namespace Kyoo.InternalAPI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RegisterInLibrary(long showID, string libraryPath)
|
public void RegisterInLibrary(long showID, Library library)
|
||||||
{
|
{
|
||||||
string query = "INSERT INTO librariesLinks (libraryID, showID) SELECT id, $showID FROM libraries WHERE libraries.path = $libraryPath;";
|
string query =
|
||||||
|
"INSERT INTO librariesLinks (libraryID, showID) SELECT id, $showID FROM libraries WHERE libraries.id = $libraryID;";
|
||||||
|
|
||||||
using (SQLiteCommand cmd = new SQLiteCommand(query, sqlConnection))
|
using (SQLiteCommand cmd = new SQLiteCommand(query, sqlConnection))
|
||||||
{
|
{
|
||||||
cmd.Parameters.AddWithValue("$libraryPath", libraryPath);
|
cmd.Parameters.AddWithValue("$libraryID", library.ID);
|
||||||
cmd.Parameters.AddWithValue("$showID", showID);
|
cmd.Parameters.AddWithValue("$showID", showID);
|
||||||
cmd.ExecuteNonQuery();
|
cmd.ExecuteNonQuery();
|
||||||
}
|
}
|
||||||
@ -999,10 +1001,10 @@ namespace Kyoo.InternalAPI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (show.studio != null)
|
if (show.Studio != null)
|
||||||
{
|
{
|
||||||
cmd.CommandText = "INSERT INTO studiosLinks (studioID, showID) VALUES($studioID, $showID);";
|
cmd.CommandText = "INSERT INTO studiosLinks (studioID, showID) VALUES($studioID, $showID);";
|
||||||
long studioID = GetOrCreateStudio(show.studio);
|
long studioID = GetOrCreateStudio(show.Studio);
|
||||||
cmd.Parameters.AddWithValue("$studioID", studioID);
|
cmd.Parameters.AddWithValue("$studioID", studioID);
|
||||||
cmd.Parameters.AddWithValue("$showID", showID);
|
cmd.Parameters.AddWithValue("$showID", showID);
|
||||||
cmd.ExecuteNonQuery();
|
cmd.ExecuteNonQuery();
|
||||||
@ -1026,10 +1028,10 @@ namespace Kyoo.InternalAPI
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
cmd.Parameters.AddWithValue("$showID", season.ShowID);
|
cmd.Parameters.AddWithValue("$showID", season.ShowID);
|
||||||
cmd.Parameters.AddWithValue("$seasonNumber", season.seasonNumber);
|
cmd.Parameters.AddWithValue("$seasonNumber", season.SeasonNumber);
|
||||||
cmd.Parameters.AddWithValue("$title", season.Title);
|
cmd.Parameters.AddWithValue("$title", season.Title);
|
||||||
cmd.Parameters.AddWithValue("$overview", season.Overview);
|
cmd.Parameters.AddWithValue("$overview", season.Overview);
|
||||||
cmd.Parameters.AddWithValue("$year", season.year);
|
cmd.Parameters.AddWithValue("$year", season.Year);
|
||||||
cmd.Parameters.AddWithValue("$imgPrimary", season.ImgPrimary);
|
cmd.Parameters.AddWithValue("$imgPrimary", season.ImgPrimary);
|
||||||
cmd.Parameters.AddWithValue("$externalIDs", season.ExternalIDs);
|
cmd.Parameters.AddWithValue("$externalIDs", season.ExternalIDs);
|
||||||
cmd.ExecuteNonQuery();
|
cmd.ExecuteNonQuery();
|
||||||
@ -1042,7 +1044,7 @@ namespace Kyoo.InternalAPI
|
|||||||
Console.Error.WriteLine("SQL error while trying to insert a season ({0}), season probably already registered.", season.Title);
|
Console.Error.WriteLine("SQL error while trying to insert a season ({0}), season probably already registered.", season.Title);
|
||||||
cmd.CommandText = "SELECT * FROM seasons WHERE showID = $showID AND seasonNumber = $seasonNumber";
|
cmd.CommandText = "SELECT * FROM seasons WHERE showID = $showID AND seasonNumber = $seasonNumber";
|
||||||
cmd.Parameters.AddWithValue("$showID", season.ShowID);
|
cmd.Parameters.AddWithValue("$showID", season.ShowID);
|
||||||
cmd.Parameters.AddWithValue("$seasonNumber", season.seasonNumber);
|
cmd.Parameters.AddWithValue("$seasonNumber", season.SeasonNumber);
|
||||||
return (long)cmd.ExecuteScalar();
|
return (long)cmd.ExecuteScalar();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1057,9 +1059,9 @@ namespace Kyoo.InternalAPI
|
|||||||
{
|
{
|
||||||
cmd.Parameters.AddWithValue("$showID", episode.ShowID);
|
cmd.Parameters.AddWithValue("$showID", episode.ShowID);
|
||||||
cmd.Parameters.AddWithValue("$seasonID", episode.SeasonID);
|
cmd.Parameters.AddWithValue("$seasonID", episode.SeasonID);
|
||||||
cmd.Parameters.AddWithValue("$seasonNUmber", episode.seasonNumber);
|
cmd.Parameters.AddWithValue("$seasonNUmber", episode.SeasonNumber);
|
||||||
cmd.Parameters.AddWithValue("$episodeNumber", episode.episodeNumber);
|
cmd.Parameters.AddWithValue("$episodeNumber", episode.EpisodeNumber);
|
||||||
cmd.Parameters.AddWithValue("$absoluteNumber", episode.absoluteNumber);
|
cmd.Parameters.AddWithValue("$absoluteNumber", episode.AbsoluteNumber);
|
||||||
cmd.Parameters.AddWithValue("$path", episode.Path);
|
cmd.Parameters.AddWithValue("$path", episode.Path);
|
||||||
cmd.Parameters.AddWithValue("$title", episode.Title);
|
cmd.Parameters.AddWithValue("$title", episode.Title);
|
||||||
cmd.Parameters.AddWithValue("$overview", episode.Overview);
|
cmd.Parameters.AddWithValue("$overview", episode.Overview);
|
||||||
@ -1077,8 +1079,8 @@ namespace Kyoo.InternalAPI
|
|||||||
Console.Error.WriteLine("SQL error while trying to insert an episode ({0}), episode probably already registered.", episode.Link);
|
Console.Error.WriteLine("SQL error while trying to insert an episode ({0}), episode probably already registered.", episode.Link);
|
||||||
cmd.CommandText = "SELECT * FROM episodes WHERE showID = $showID AND seasonNumber = $seasonNumber AND episodeNumber = $episodeNumber";
|
cmd.CommandText = "SELECT * FROM episodes WHERE showID = $showID AND seasonNumber = $seasonNumber AND episodeNumber = $episodeNumber";
|
||||||
cmd.Parameters.AddWithValue("$showID", episode.ShowID);
|
cmd.Parameters.AddWithValue("$showID", episode.ShowID);
|
||||||
cmd.Parameters.AddWithValue("$seasonNumber", episode.seasonNumber);
|
cmd.Parameters.AddWithValue("$seasonNumber", episode.SeasonNumber);
|
||||||
cmd.Parameters.AddWithValue("$episodeNumber", episode.episodeNumber);
|
cmd.Parameters.AddWithValue("$episodeNumber", episode.EpisodeNumber);
|
||||||
return (long)cmd.ExecuteScalar();
|
return (long)cmd.ExecuteScalar();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1089,7 +1091,7 @@ namespace Kyoo.InternalAPI
|
|||||||
string query = "INSERT INTO tracks (episodeID, streamType, title, language, codec, isDefault, isForced, isExternal, path) VALUES($episodeID, $streamType, $title, $language, $codec, $isDefault, $isForced, $isExternal, $path);";
|
string query = "INSERT INTO tracks (episodeID, streamType, title, language, codec, isDefault, isForced, isExternal, path) VALUES($episodeID, $streamType, $title, $language, $codec, $isDefault, $isForced, $isExternal, $path);";
|
||||||
using (SQLiteCommand cmd = new SQLiteCommand(query, sqlConnection))
|
using (SQLiteCommand cmd = new SQLiteCommand(query, sqlConnection))
|
||||||
{
|
{
|
||||||
cmd.Parameters.AddWithValue("$episodeID", track.episodeID);
|
cmd.Parameters.AddWithValue("$episodeID", track.EpisodeID);
|
||||||
cmd.Parameters.AddWithValue("$streamType", track.Type);
|
cmd.Parameters.AddWithValue("$streamType", track.Type);
|
||||||
cmd.Parameters.AddWithValue("$title", track.Title);
|
cmd.Parameters.AddWithValue("$title", track.Title);
|
||||||
cmd.Parameters.AddWithValue("$language", track.Language);
|
cmd.Parameters.AddWithValue("$language", track.Language);
|
||||||
@ -1102,21 +1104,21 @@ namespace Kyoo.InternalAPI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RegisterShowPeople(long showID, List<People> people)
|
public void RegisterShowPeople(long showID, IEnumerable<People> people)
|
||||||
{
|
{
|
||||||
if (people == null)
|
if (people == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
string linkQuery = "INSERT INTO peopleLinks (peopleID, showID, role, type) VALUES($peopleID, $showID, $role, $type);";
|
string linkQuery = "INSERT INTO peopleLinks (peopleID, showID, role, type) VALUES($peopleID, $showID, $role, $type);";
|
||||||
|
|
||||||
for (int i = 0; i < people.Count; i++)
|
foreach (People peop in people)
|
||||||
{
|
{
|
||||||
using (SQLiteCommand cmd = new SQLiteCommand(linkQuery, sqlConnection))
|
using (SQLiteCommand cmd = new SQLiteCommand(linkQuery, sqlConnection))
|
||||||
{
|
{
|
||||||
cmd.Parameters.AddWithValue("$peopleID", GetOrCreatePeople(people[i]));
|
cmd.Parameters.AddWithValue("$peopleID", GetOrCreatePeople(peop));
|
||||||
cmd.Parameters.AddWithValue("$showID", showID);
|
cmd.Parameters.AddWithValue("$showID", showID);
|
||||||
cmd.Parameters.AddWithValue("$role", people[i].Role);
|
cmd.Parameters.AddWithValue("$role", peop.Role);
|
||||||
cmd.Parameters.AddWithValue("$type", people[i].Type);
|
cmd.Parameters.AddWithValue("$type", peop.Type);
|
||||||
cmd.ExecuteNonQuery();
|
cmd.ExecuteNonQuery();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1164,11 +1166,11 @@ namespace Kyoo.InternalAPI
|
|||||||
|
|
||||||
using (SQLiteCommand cmd = new SQLiteCommand(query, sqlConnection))
|
using (SQLiteCommand cmd = new SQLiteCommand(query, sqlConnection))
|
||||||
{
|
{
|
||||||
cmd.Parameters.AddWithValue("$episodeID", episode.id);
|
cmd.Parameters.AddWithValue("$episodeID", episode.ID);
|
||||||
cmd.ExecuteNonQuery();
|
cmd.ExecuteNonQuery();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GetEpisodes(episode.ShowID, episode.seasonNumber).Count == 0)
|
if (GetEpisodes(episode.ShowID, episode.SeasonNumber).Count == 0)
|
||||||
RemoveSeason(episode.ShowID, episode.SeasonID);
|
RemoveSeason(episode.ShowID, episode.SeasonID);
|
||||||
}
|
}
|
||||||
|
|
65
Kyoo/Controllers/PluginManager.cs
Normal file
65
Kyoo/Controllers/PluginManager.cs
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using Kyoo.Models;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace Kyoo.Controllers
|
||||||
|
{
|
||||||
|
public class PluginManager : IPluginManager
|
||||||
|
{
|
||||||
|
private readonly IServiceProvider provider;
|
||||||
|
private readonly IConfiguration config;
|
||||||
|
private List<IPlugin> plugins;
|
||||||
|
|
||||||
|
public PluginManager(IServiceProvider provider, IConfiguration config)
|
||||||
|
{
|
||||||
|
this.provider = provider;
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T GetPlugin<T>(string name)
|
||||||
|
{
|
||||||
|
if (plugins == null)
|
||||||
|
return default;
|
||||||
|
return (T)(from plugin in plugins where plugin.Name == name && plugin is T
|
||||||
|
select plugin).FirstOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<T> GetPlugins<T>()
|
||||||
|
{
|
||||||
|
if (plugins == null)
|
||||||
|
return null;
|
||||||
|
return from plugin in plugins where plugin is T
|
||||||
|
select (T)plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReloadPlugins()
|
||||||
|
{
|
||||||
|
string pluginFolder = config.GetValue<string>("plugins");
|
||||||
|
|
||||||
|
if (!Directory.Exists(pluginFolder))
|
||||||
|
return;
|
||||||
|
string[] pluginsPaths = Directory.GetFiles(pluginFolder);
|
||||||
|
|
||||||
|
plugins = pluginsPaths.Select(path =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Assembly ass = Assembly.LoadFile(Path.GetFullPath(path));
|
||||||
|
return (from type in ass.GetTypes()
|
||||||
|
where typeof(IPlugin).IsAssignableFrom(type)
|
||||||
|
select (IPlugin) ActivatorUtilities.CreateInstance(provider, type)).FirstOrDefault();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine($"Error loading the plugin at {path}.\nException: {ex.Message}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}).Where(x => x != null).ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
86
Kyoo/Controllers/ProviderManager.cs
Normal file
86
Kyoo/Controllers/ProviderManager.cs
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
using System;
|
||||||
|
using Kyoo.Models;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Kyoo.Controllers.ThumbnailsManager;
|
||||||
|
|
||||||
|
namespace Kyoo.Controllers
|
||||||
|
{
|
||||||
|
public class ProviderManager : IProviderManager
|
||||||
|
{
|
||||||
|
private readonly IEnumerable<IMetadataProvider> providers;
|
||||||
|
private readonly IThumbnailsManager thumbnailsManager;
|
||||||
|
|
||||||
|
public ProviderManager(IThumbnailsManager thumbnailsManager, IPluginManager pluginManager)
|
||||||
|
{
|
||||||
|
this.thumbnailsManager = thumbnailsManager;
|
||||||
|
providers = pluginManager.GetPlugins<IMetadataProvider>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<T> GetMetadata<T>(Func<IMetadataProvider, Task<T>> providerCall, Library library, string what) where T : IMergable<T>, new()
|
||||||
|
{
|
||||||
|
T ret = new T();
|
||||||
|
|
||||||
|
foreach (IMetadataProvider provider in providers.OrderBy(provider => Array.IndexOf(library.Providers, provider.Name)))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (library.Providers.Contains(provider.Name))
|
||||||
|
ret = ret.Merge(await providerCall(provider));
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Console.Error.WriteLine($"The provider {provider.Name} coudln't work for {what}. (Exception: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<T>> GetMetadata<T>(Func<IMetadataProvider, Task<IEnumerable<T>>> providerCall, Library library, string what)
|
||||||
|
{
|
||||||
|
List<T> ret = new List<T>();
|
||||||
|
|
||||||
|
foreach (IMetadataProvider provider in providers.OrderBy(provider => Array.IndexOf(library.Providers, provider.Name)))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (library.Providers.Contains(provider.Name))
|
||||||
|
ret.AddRange(await providerCall(provider));
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Console.Error.WriteLine($"The provider {provider.Name} coudln't work for {what}. (Excepetion: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Collection> GetCollectionFromName(string name, Library library)
|
||||||
|
{
|
||||||
|
return await GetMetadata(provider => provider.GetCollectionFromName(name), library, $"the collection {name}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Show> GetShowFromName(string showName, Library library)
|
||||||
|
{
|
||||||
|
Show show = await GetMetadata(provider => provider.GetShowFromName(showName), library, $"the show {showName}");
|
||||||
|
await thumbnailsManager.Validate(show);
|
||||||
|
return show;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Season> GetSeason(Show show, long seasonNumber, Library library)
|
||||||
|
{
|
||||||
|
return await GetMetadata(provider => provider.GetSeason(show, seasonNumber), library, $"the season {seasonNumber} of {show.Title}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Episode> GetEpisode(Show show, long seasonNumber, long episodeNumber, long absoluteNumber, Library library)
|
||||||
|
{
|
||||||
|
Episode episode = await GetMetadata(provider => provider.GetEpisode(show, seasonNumber, episodeNumber, absoluteNumber), library, "an episode");
|
||||||
|
await thumbnailsManager.Validate(episode);
|
||||||
|
return episode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<People>> GetPeople(Show show, Library library)
|
||||||
|
{
|
||||||
|
IEnumerable<People> people = await GetMetadata(provider => provider.GetPeople(show), library, "unknown data");
|
||||||
|
people = await thumbnailsManager.Validate(people);
|
||||||
|
return people;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -7,7 +7,7 @@ using System.IO;
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Kyoo.InternalAPI.ThumbnailsManager
|
namespace Kyoo.Controllers.ThumbnailsManager
|
||||||
{
|
{
|
||||||
public class ThumbnailsManager : IThumbnailsManager
|
public class ThumbnailsManager : IThumbnailsManager
|
||||||
{
|
{
|
||||||
@ -20,6 +20,8 @@ namespace Kyoo.InternalAPI.ThumbnailsManager
|
|||||||
|
|
||||||
public async Task<Show> Validate(Show show)
|
public async Task<Show> Validate(Show show)
|
||||||
{
|
{
|
||||||
|
if (show == null || show.Path == null)
|
||||||
|
return null;
|
||||||
string localThumb = Path.Combine(show.Path, "poster.jpg");
|
string localThumb = Path.Combine(show.Path, "poster.jpg");
|
||||||
string localLogo = Path.Combine(show.Path, "logo.png");
|
string localLogo = Path.Combine(show.Path, "logo.png");
|
||||||
string localBackdrop = Path.Combine(show.Path, "backdrop.jpg");
|
string localBackdrop = Path.Combine(show.Path, "backdrop.jpg");
|
||||||
@ -67,25 +69,26 @@ namespace Kyoo.InternalAPI.ThumbnailsManager
|
|||||||
return show;
|
return show;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<People>> Validate(List<People> people)
|
public async Task<IEnumerable<People>> Validate(IEnumerable<People> people)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < people?.Count; i++)
|
if (people == null)
|
||||||
|
return null;
|
||||||
|
foreach (People peop in people)
|
||||||
{
|
{
|
||||||
string root = config.GetValue<string>("peoplePath");
|
string root = config.GetValue<string>("peoplePath");
|
||||||
Directory.CreateDirectory(root);
|
Directory.CreateDirectory(root);
|
||||||
|
|
||||||
string localThumb = root + "/" + people[i].slug + ".jpg";
|
string localThumb = root + "/" + peop.Slug + ".jpg";
|
||||||
if (people[i].imgPrimary != null && !File.Exists(localThumb))
|
if (peop.ImgPrimary == null || File.Exists(localThumb))
|
||||||
|
continue;
|
||||||
|
try
|
||||||
{
|
{
|
||||||
try
|
using WebClient client = new WebClient();
|
||||||
{
|
await client.DownloadFileTaskAsync(new Uri(peop.ImgPrimary), localThumb);
|
||||||
using WebClient client = new WebClient();
|
}
|
||||||
await client.DownloadFileTaskAsync(new Uri(people[i].imgPrimary), localThumb);
|
catch (WebException)
|
||||||
}
|
{
|
||||||
catch (WebException)
|
Console.Error.WriteLine("Couldn't download an image.");
|
||||||
{
|
|
||||||
Console.Error.WriteLine("Couldn't download an image.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,18 +97,19 @@ namespace Kyoo.InternalAPI.ThumbnailsManager
|
|||||||
|
|
||||||
public async Task<Episode> Validate(Episode episode)
|
public async Task<Episode> Validate(Episode episode)
|
||||||
{
|
{
|
||||||
string localThumb = Path.ChangeExtension(episode.Path, "jpg");
|
if (episode == null || episode.Path == null)
|
||||||
if (episode.ImgPrimary != null && !File.Exists(localThumb))
|
return null;
|
||||||
|
string localThumb = Path.ChangeExtension(episode.Path, "jpg");
|
||||||
|
if (episode.ImgPrimary == null || File.Exists(localThumb))
|
||||||
|
return episode;
|
||||||
|
try
|
||||||
{
|
{
|
||||||
try
|
using WebClient client = new WebClient();
|
||||||
{
|
await client.DownloadFileTaskAsync(new Uri(episode.ImgPrimary), localThumb);
|
||||||
using WebClient client = new WebClient();
|
}
|
||||||
await client.DownloadFileTaskAsync(new Uri(episode.ImgPrimary), localThumb);
|
catch (WebException)
|
||||||
}
|
{
|
||||||
catch (WebException)
|
Console.Error.WriteLine("Couldn't download an image.");
|
||||||
{
|
|
||||||
Console.Error.WriteLine("Couldn't download an image.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return episode;
|
return episode;
|
@ -1,14 +1,15 @@
|
|||||||
using System;
|
using System;
|
||||||
using Kyoo.Models;
|
using Kyoo.Models;
|
||||||
using Kyoo.InternalAPI.TranscoderLink;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Kyoo.Controllers.TranscoderLink;
|
||||||
|
|
||||||
#pragma warning disable 4014
|
#pragma warning disable 4014
|
||||||
|
|
||||||
namespace Kyoo.InternalAPI
|
namespace Kyoo.Controllers
|
||||||
{
|
{
|
||||||
public class Transcoder : ITranscoder
|
public class Transcoder : ITranscoder
|
||||||
{
|
{
|
@ -5,7 +5,7 @@ using Kyoo.Models;
|
|||||||
using Kyoo.Models.Watch;
|
using Kyoo.Models.Watch;
|
||||||
// ReSharper disable InconsistentNaming
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
namespace Kyoo.InternalAPI.TranscoderLink
|
namespace Kyoo.Controllers.TranscoderLink
|
||||||
{
|
{
|
||||||
public static class TranscoderAPI
|
public static class TranscoderAPI
|
||||||
{
|
{
|
@ -1,4 +1,4 @@
|
|||||||
using Kyoo.InternalAPI;
|
using Kyoo.Controllers;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@ -18,10 +18,10 @@ namespace Kyoo.Controllers
|
|||||||
this.crawler = crawler;
|
this.crawler = crawler;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("scan/{watch}")]
|
[HttpGet("scan")]
|
||||||
public IActionResult ScanLibrary(bool watch)
|
public IActionResult ScanLibrary()
|
||||||
{
|
{
|
||||||
crawler.Start(watch);
|
crawler.Start();
|
||||||
return Ok("Scanning");
|
return Ok("Scanning");
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
using Kyoo.InternalAPI;
|
using Kyoo.Controllers;
|
||||||
using Kyoo.Models;
|
using Kyoo.Models;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
@ -1,4 +1,4 @@
|
|||||||
using Kyoo.InternalAPI;
|
using Kyoo.Controllers;
|
||||||
using Kyoo.Models;
|
using Kyoo.Models;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
@ -1,4 +1,4 @@
|
|||||||
using Kyoo.InternalAPI;
|
using Kyoo.Controllers;
|
||||||
using Kyoo.Models;
|
using Kyoo.Models;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@ -30,7 +30,7 @@ namespace Kyoo.Controllers
|
|||||||
if (library == null)
|
if (library == null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
|
||||||
return libraryManager.GetShowsInLibrary(library.id);
|
return libraryManager.GetShowsInLibrary(library.ID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
using Kyoo.InternalAPI;
|
using Kyoo.Controllers;
|
||||||
using Kyoo.Models;
|
using Kyoo.Models;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
@ -23,10 +23,10 @@ namespace Kyoo.Controllers
|
|||||||
|
|
||||||
if (people == null)
|
if (people == null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
Collection collection = new Collection(0, people.slug, people.Name, null, null)
|
Collection collection = new Collection(0, people.Slug, people.Name, null, null)
|
||||||
{
|
{
|
||||||
Shows = libraryManager.GetShowsByPeople(people.id),
|
Shows = libraryManager.GetShowsByPeople(people.ID),
|
||||||
Poster = "peopleimg/" + people.slug
|
Poster = "peopleimg/" + people.Slug
|
||||||
};
|
};
|
||||||
return collection;
|
return collection;
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
using Kyoo.InternalAPI;
|
using Kyoo.Controllers;
|
||||||
using Kyoo.Models;
|
using Kyoo.Models;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
@ -20,12 +20,12 @@ namespace Kyoo.Controllers
|
|||||||
{
|
{
|
||||||
SearchResult result = new SearchResult
|
SearchResult result = new SearchResult
|
||||||
{
|
{
|
||||||
query = query,
|
Query = query,
|
||||||
shows = libraryManager.GetShows(query),
|
Shows = libraryManager.GetShows(query),
|
||||||
episodes = libraryManager.SearchEpisodes(query),
|
Episodes = libraryManager.SearchEpisodes(query),
|
||||||
people = libraryManager.SearchPeople(query),
|
People = libraryManager.SearchPeople(query),
|
||||||
genres = libraryManager.SearchGenres(query),
|
Genres = libraryManager.SearchGenres(query),
|
||||||
studios = libraryManager.SearchStudios(query)
|
Studios = libraryManager.SearchStudios(query)
|
||||||
};
|
};
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
using Kyoo.InternalAPI;
|
using Kyoo.Controllers;
|
||||||
using Kyoo.Models;
|
using Kyoo.Models;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
@ -1,8 +1,6 @@
|
|||||||
using Kyoo.InternalAPI;
|
using Kyoo.Models;
|
||||||
using Kyoo.Models;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
@ -52,12 +50,12 @@ namespace Kyoo.Controllers
|
|||||||
public async Task<string> ExtractSubtitle(string showSlug, long seasonNumber, long episodeNumber)
|
public async Task<string> ExtractSubtitle(string showSlug, long seasonNumber, long episodeNumber)
|
||||||
{
|
{
|
||||||
Episode episode = libraryManager.GetEpisode(showSlug, seasonNumber, episodeNumber);
|
Episode episode = libraryManager.GetEpisode(showSlug, seasonNumber, episodeNumber);
|
||||||
libraryManager.ClearSubtitles(episode.id);
|
libraryManager.ClearSubtitles(episode.ID);
|
||||||
|
|
||||||
Track[] tracks = await transcoder.ExtractSubtitles(episode.Path);
|
Track[] tracks = await transcoder.ExtractSubtitles(episode.Path);
|
||||||
foreach (Track track in tracks)
|
foreach (Track track in tracks)
|
||||||
{
|
{
|
||||||
track.episodeID = episode.id;
|
track.EpisodeID = episode.ID;
|
||||||
libraryManager.RegisterTrack(track);
|
libraryManager.RegisterTrack(track);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,12 +68,12 @@ namespace Kyoo.Controllers
|
|||||||
List<Episode> episodes = libraryManager.GetEpisodes(showSlug);
|
List<Episode> episodes = libraryManager.GetEpisodes(showSlug);
|
||||||
foreach (Episode episode in episodes)
|
foreach (Episode episode in episodes)
|
||||||
{
|
{
|
||||||
libraryManager.ClearSubtitles(episode.id);
|
libraryManager.ClearSubtitles(episode.ID);
|
||||||
|
|
||||||
Track[] tracks = await transcoder.ExtractSubtitles(episode.Path);
|
Track[] tracks = await transcoder.ExtractSubtitles(episode.Path);
|
||||||
foreach (Track track in tracks)
|
foreach (Track track in tracks)
|
||||||
{
|
{
|
||||||
track.episodeID = episode.id;
|
track.EpisodeID = episode.ID;
|
||||||
libraryManager.RegisterTrack(track);
|
libraryManager.RegisterTrack(track);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,11 +1,10 @@
|
|||||||
using Kyoo.InternalAPI;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
namespace Kyoo.Controllers
|
namespace Kyoo.Controllers
|
||||||
{
|
{
|
||||||
public class ThumbnailController : Controller
|
public class ThumbnailController : ControllerBase
|
||||||
{
|
{
|
||||||
private readonly ILibraryManager libraryManager;
|
private readonly ILibraryManager libraryManager;
|
||||||
private readonly string peoplePath;
|
private readonly string peoplePath;
|
@ -1,4 +1,4 @@
|
|||||||
using Kyoo.InternalAPI;
|
using Kyoo.Controllers;
|
||||||
using Kyoo.Models;
|
using Kyoo.Models;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
@ -1,4 +1,4 @@
|
|||||||
using Kyoo.InternalAPI;
|
using Kyoo.Controllers;
|
||||||
using Kyoo.Models;
|
using Kyoo.Models;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
@ -1,328 +0,0 @@
|
|||||||
using Kyoo.InternalAPI.Utility;
|
|
||||||
using Kyoo.Models;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Kyoo.Models.Watch;
|
|
||||||
|
|
||||||
namespace Kyoo.InternalAPI
|
|
||||||
{
|
|
||||||
public class Crawler : ICrawler
|
|
||||||
{
|
|
||||||
private static ICrawler runningCrawler;
|
|
||||||
private bool isScanning;
|
|
||||||
private readonly CancellationTokenSource cancellation;
|
|
||||||
|
|
||||||
private readonly ILibraryManager libraryManager;
|
|
||||||
private readonly IMetadataProvider metadataProvider;
|
|
||||||
private readonly ITranscoder transcoder;
|
|
||||||
private readonly IConfiguration config;
|
|
||||||
|
|
||||||
public Crawler(ILibraryManager libraryManager, IMetadataProvider metadataProvider, ITranscoder transcoder, IConfiguration configuration)
|
|
||||||
{
|
|
||||||
this.libraryManager = libraryManager;
|
|
||||||
this.metadataProvider = metadataProvider;
|
|
||||||
this.transcoder = transcoder;
|
|
||||||
config = configuration;
|
|
||||||
cancellation = new CancellationTokenSource();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Start(bool watch)
|
|
||||||
{
|
|
||||||
if (runningCrawler == null)
|
|
||||||
{
|
|
||||||
runningCrawler = this;
|
|
||||||
await StartAsync(watch, cancellation.Token);
|
|
||||||
}
|
|
||||||
else if (runningCrawler is Crawler crawler)
|
|
||||||
{
|
|
||||||
if (!crawler.isScanning)
|
|
||||||
{
|
|
||||||
await crawler.StopAsync();
|
|
||||||
runningCrawler = this;
|
|
||||||
await StartAsync(watch, cancellation.Token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task StartAsync(bool watch, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
IEnumerable<Episode> episodes = libraryManager.GetAllEpisodes();
|
|
||||||
IEnumerable<string> libraryPaths = libraryManager.GetLibrariesPath();
|
|
||||||
|
|
||||||
isScanning = true;
|
|
||||||
Debug.WriteLine("&Crawler started");
|
|
||||||
foreach (Episode episode in episodes)
|
|
||||||
{
|
|
||||||
if (!File.Exists(episode.Path))
|
|
||||||
libraryManager.RemoveEpisode(episode);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (string path in libraryPaths)
|
|
||||||
{
|
|
||||||
Scan(path, cancellationToken);
|
|
||||||
|
|
||||||
if(watch)
|
|
||||||
Watch(path, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
isScanning = false;
|
|
||||||
if (watch)
|
|
||||||
while (!cancellationToken.IsCancellationRequested);
|
|
||||||
Debug.WriteLine("&Crawler stopped");
|
|
||||||
runningCrawler = null;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void Scan(string folderPath, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
string[] files = Directory.GetFiles(folderPath, "*", SearchOption.AllDirectories);
|
|
||||||
|
|
||||||
foreach (string file in files)
|
|
||||||
{
|
|
||||||
if (cancellationToken.IsCancellationRequested)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (IsVideo(file))
|
|
||||||
{
|
|
||||||
Debug.WriteLine("&Registering episode at: " + file);
|
|
||||||
await ExtractEpisodeData(file, folderPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Watch(string folderPath, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
Debug.WriteLine("Folder watching not implemented yet.");
|
|
||||||
//Debug.WriteLine("&Watching " + folderPath + " for changes");
|
|
||||||
//using (FileSystemWatcher watcher = new FileSystemWatcher())
|
|
||||||
//{
|
|
||||||
// watcher.Path = folderPath;
|
|
||||||
// watcher.IncludeSubdirectories = true;
|
|
||||||
// watcher.NotifyFilter = NotifyFilters.LastAccess
|
|
||||||
// | NotifyFilters.LastWrite
|
|
||||||
// | NotifyFilters.FileName
|
|
||||||
// | NotifyFilters.Size
|
|
||||||
// | NotifyFilters.DirectoryName;
|
|
||||||
|
|
||||||
// watcher.Created += FileCreated;
|
|
||||||
// watcher.Changed += FileChanged;
|
|
||||||
// watcher.Renamed += FileRenamed;
|
|
||||||
// watcher.Deleted += FileDeleted;
|
|
||||||
|
|
||||||
|
|
||||||
// watcher.EnableRaisingEvents = true;
|
|
||||||
|
|
||||||
// while (!cancellationToken.IsCancellationRequested);
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
|
|
||||||
//private void FileCreated(object sender, FileSystemEventArgs e)
|
|
||||||
//{
|
|
||||||
// Debug.WriteLine("&File Created at " + e.FullPath);
|
|
||||||
// if (IsVideo(e.FullPath))
|
|
||||||
// {
|
|
||||||
// Debug.WriteLine("&Created file is a video");
|
|
||||||
// _ = TryRegisterEpisode(e.FullPath);
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
//private void FileChanged(object sender, FileSystemEventArgs e)
|
|
||||||
//{
|
|
||||||
// Debug.WriteLine("&File Changed at " + e.FullPath);
|
|
||||||
//}
|
|
||||||
|
|
||||||
//private void FileRenamed(object sender, RenamedEventArgs e)
|
|
||||||
//{
|
|
||||||
// Debug.WriteLine("&File Renamed at " + e.FullPath);
|
|
||||||
//}
|
|
||||||
|
|
||||||
//private void FileDeleted(object sender, FileSystemEventArgs e)
|
|
||||||
//{
|
|
||||||
// Debug.WriteLine("&File Deleted at " + e.FullPath);
|
|
||||||
//}
|
|
||||||
|
|
||||||
private async Task ExtractEpisodeData(string episodePath, string libraryPath)
|
|
||||||
{
|
|
||||||
if (!libraryManager.IsEpisodeRegistered(episodePath))
|
|
||||||
{
|
|
||||||
string relativePath = episodePath.Substring(libraryPath.Length);
|
|
||||||
string patern = config.GetValue<string>("regex");
|
|
||||||
Regex regex = new Regex(patern, RegexOptions.IgnoreCase);
|
|
||||||
Match match = regex.Match(relativePath);
|
|
||||||
|
|
||||||
string showPath = Path.GetDirectoryName(episodePath);
|
|
||||||
string collectionName = match.Groups["Collection"]?.Value;
|
|
||||||
string showName = match.Groups["ShowTitle"].Value;
|
|
||||||
bool seasonSuccess = long.TryParse(match.Groups["Season"].Value, out long seasonNumber);
|
|
||||||
bool episodeSucess = long.TryParse(match.Groups["Episode"].Value, out long episodeNumber);
|
|
||||||
long absoluteNumber = -1;
|
|
||||||
|
|
||||||
if (!seasonSuccess || !episodeSucess)
|
|
||||||
{
|
|
||||||
//Considering that the episode is using absolute path.
|
|
||||||
seasonNumber = -1;
|
|
||||||
episodeNumber = -1;
|
|
||||||
|
|
||||||
regex = new Regex(config.GetValue<string>("absoluteRegex"));
|
|
||||||
match = regex.Match(relativePath);
|
|
||||||
|
|
||||||
showName = match.Groups["ShowTitle"].Value;
|
|
||||||
bool absoluteSucess = long.TryParse(match.Groups["AbsoluteNumber"].Value, out absoluteNumber);
|
|
||||||
|
|
||||||
if (!absoluteSucess)
|
|
||||||
{
|
|
||||||
Debug.WriteLine("&Couldn't find basic data for the episode (regexs didn't match)" + relativePath);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Show show = await RegisterOrGetShow(collectionName, showName, showPath, libraryPath);
|
|
||||||
if (show != null)
|
|
||||||
await RegisterEpisode(show, seasonNumber, episodeNumber, absoluteNumber, episodePath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<Show> RegisterOrGetShow(string collectionName, string showTitle, string showPath, string libraryPath)
|
|
||||||
{
|
|
||||||
string showProviderIDs;
|
|
||||||
|
|
||||||
if (!libraryManager.IsShowRegistered(showPath, out long showID))
|
|
||||||
{
|
|
||||||
Show show = await metadataProvider.GetShowFromName(showTitle, showPath);
|
|
||||||
showProviderIDs = show.ExternalIDs;
|
|
||||||
showID = libraryManager.RegisterShow(show);
|
|
||||||
|
|
||||||
if (showID == -1)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
libraryManager.RegisterInLibrary(showID, libraryPath);
|
|
||||||
if (!string.IsNullOrEmpty(collectionName))
|
|
||||||
{
|
|
||||||
if (!libraryManager.IsCollectionRegistered(Slugifier.ToSlug(collectionName), out long collectionID))
|
|
||||||
{
|
|
||||||
Collection collection = await metadataProvider.GetCollectionFromName(collectionName);
|
|
||||||
collectionID = libraryManager.RegisterCollection(collection);
|
|
||||||
}
|
|
||||||
libraryManager.AddShowToCollection(showID, collectionID);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<People> actors = await metadataProvider.GetPeople(show.ExternalIDs);
|
|
||||||
libraryManager.RegisterShowPeople(showID, actors);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
showProviderIDs = libraryManager.GetShowExternalIDs(showID);
|
|
||||||
|
|
||||||
return new Show { id = showID, ExternalIDs = showProviderIDs, Title = showTitle };
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task RegisterEpisode(Show show, long seasonNumber, long episodeNumber, long absoluteNumber, string episodePath)
|
|
||||||
{
|
|
||||||
long seasonID = -1;
|
|
||||||
if (seasonNumber != -1)
|
|
||||||
{
|
|
||||||
if (!libraryManager.IsSeasonRegistered(show.id, seasonNumber, out seasonID))
|
|
||||||
{
|
|
||||||
Season season = await metadataProvider.GetSeason(show.Title, seasonNumber);
|
|
||||||
season.ShowID = show.id;
|
|
||||||
seasonID = libraryManager.RegisterSeason(season);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Episode episode = await metadataProvider.GetEpisode(show.ExternalIDs, seasonNumber, episodeNumber, absoluteNumber, episodePath);
|
|
||||||
episode.ShowID = show.id;
|
|
||||||
|
|
||||||
if (seasonID == -1)
|
|
||||||
{
|
|
||||||
if (!libraryManager.IsSeasonRegistered(show.id, episode.seasonNumber, out seasonID))
|
|
||||||
{
|
|
||||||
Season season = await metadataProvider.GetSeason(show.Title, episode.seasonNumber);
|
|
||||||
season.ShowID = show.id;
|
|
||||||
seasonID = libraryManager.RegisterSeason(season);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
episode.SeasonID = seasonID;
|
|
||||||
episode.id = libraryManager.RegisterEpisode(episode);
|
|
||||||
|
|
||||||
Track[] tracks = await transcoder.GetTrackInfo(episode.Path);
|
|
||||||
int subcount = 0;
|
|
||||||
foreach (Track track in tracks)
|
|
||||||
{
|
|
||||||
if (track.Type == StreamType.Subtitle)
|
|
||||||
{
|
|
||||||
subcount++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
track.episodeID = episode.id;
|
|
||||||
libraryManager.RegisterTrack(track);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (episode.Path.EndsWith(".mkv"))
|
|
||||||
{
|
|
||||||
if (CountExtractedSubtitles(episode) != subcount)
|
|
||||||
{
|
|
||||||
Track[] subtitles = await transcoder.ExtractSubtitles(episode.Path);
|
|
||||||
if (subtitles != null)
|
|
||||||
{
|
|
||||||
foreach (Track track in subtitles)
|
|
||||||
{
|
|
||||||
track.episodeID = episode.id;
|
|
||||||
libraryManager.RegisterTrack(track);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int CountExtractedSubtitles(Episode episode)
|
|
||||||
{
|
|
||||||
string path = Path.Combine(Path.GetDirectoryName(episode.Path), "Subtitles");
|
|
||||||
int subcount = 0;
|
|
||||||
|
|
||||||
if (!Directory.Exists(path))
|
|
||||||
return 0;
|
|
||||||
foreach (string sub in Directory.EnumerateFiles(path, "", SearchOption.AllDirectories))
|
|
||||||
{
|
|
||||||
string episodeLink = Path.GetFileNameWithoutExtension(episode.Path);
|
|
||||||
|
|
||||||
if (sub.Contains(episodeLink))
|
|
||||||
{
|
|
||||||
string language = sub.Substring(Path.GetDirectoryName(sub).Length + episodeLink.Length + 2, 3);
|
|
||||||
bool isDefault = sub.Contains("default");
|
|
||||||
bool isForced = sub.Contains("forced");
|
|
||||||
Track track = new Track(StreamType.Subtitle, null, language, isDefault, isForced, null, false, sub) { episodeID = episode.id };
|
|
||||||
|
|
||||||
if (Path.GetExtension(sub) == ".ass")
|
|
||||||
track.Codec = "ass";
|
|
||||||
else if (Path.GetExtension(sub) == ".srt")
|
|
||||||
track.Codec = "subrip";
|
|
||||||
else
|
|
||||||
track.Codec = null;
|
|
||||||
libraryManager.RegisterTrack(track);
|
|
||||||
subcount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return subcount;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static readonly string[] VideoExtensions = { ".webm", ".mkv", ".flv", ".vob", ".ogg", ".ogv", ".avi", ".mts", ".m2ts", ".ts", ".mov", ".qt", ".asf", ".mp4", ".m4p", ".m4v", ".mpg", ".mp2", ".mpeg", ".mpe", ".mpv", ".m2v", ".3gp", ".3g2" };
|
|
||||||
|
|
||||||
private static bool IsVideo(string filePath)
|
|
||||||
{
|
|
||||||
return VideoExtensions.Contains(Path.GetExtension(filePath));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Task StopAsync()
|
|
||||||
{
|
|
||||||
cancellation.Cancel();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
using Kyoo.Models;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Kyoo.InternalAPI
|
|
||||||
{
|
|
||||||
public interface IMetadataProvider
|
|
||||||
{
|
|
||||||
//For the collection
|
|
||||||
Task<Collection> GetCollectionFromName(string name);
|
|
||||||
|
|
||||||
//For the show
|
|
||||||
Task<Show> GetShowByID(string id);
|
|
||||||
Task<Show> GetShowFromName(string showName, string showPath);
|
|
||||||
Task<Show> GetImages(Show show);
|
|
||||||
Task<List<People>> GetPeople(string id);
|
|
||||||
|
|
||||||
//For the seasons
|
|
||||||
Task<Season> GetSeason(string showName, long seasonNumber);
|
|
||||||
Task<string> GetSeasonImage(string showName, long seasonNumber);
|
|
||||||
|
|
||||||
//For the episodes
|
|
||||||
Task<Episode> GetEpisode(string externalIDs, long seasonNumber, long episodeNumber, long absoluteNumber, string episodePath);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,68 +0,0 @@
|
|||||||
using Kyoo.Models;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using System;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Net;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Kyoo.InternalAPI.MetadataProvider.TheTvDB
|
|
||||||
{
|
|
||||||
public class HelperTvDB : ProviderHelper
|
|
||||||
{
|
|
||||||
public override string Provider => "TvDB";
|
|
||||||
|
|
||||||
private string token;
|
|
||||||
private DateTime tokenDate;
|
|
||||||
|
|
||||||
protected async Task<string> Authentificate()
|
|
||||||
{
|
|
||||||
if (DateTime.Now.Subtract(tokenDate) < TimeSpan.FromDays(1))
|
|
||||||
return token;
|
|
||||||
|
|
||||||
HttpClient client = new HttpClient();
|
|
||||||
HttpContent content = new StringContent("{ \"apikey\": \"IM2OXA8UHUIU0GH6\" }", Encoding.UTF8, "application/json");
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
HttpResponseMessage response = await client.PostAsync("https://api.thetvdb.com/login", content);
|
|
||||||
|
|
||||||
if (response.StatusCode == HttpStatusCode.OK)
|
|
||||||
{
|
|
||||||
string resp = await response.Content.ReadAsStringAsync();
|
|
||||||
var obj = new {Token = ""};
|
|
||||||
|
|
||||||
token = JsonConvert.DeserializeAnonymousType(resp, obj).Token;
|
|
||||||
tokenDate = DateTime.UtcNow;
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
Debug.WriteLine("&Couldn't authentificate in TheTvDB API.\nError status: " + response.StatusCode + " Message: " + response.RequestMessage);
|
|
||||||
}
|
|
||||||
catch (WebException ex)
|
|
||||||
{
|
|
||||||
Debug.WriteLine("&Couldn't authentificate in TheTvDB API.\nError status: " + ex.Status);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected static long? GetYear(string firstAired)
|
|
||||||
{
|
|
||||||
if (firstAired?.Length >= 4 && long.TryParse(firstAired.Substring(0, 4), out long year))
|
|
||||||
return year;
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Status? GetStatus(string status)
|
|
||||||
{
|
|
||||||
if (status == "Ended")
|
|
||||||
return Status.Finished;
|
|
||||||
if (status == "Continuing")
|
|
||||||
return Status.Airing;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,325 +0,0 @@
|
|||||||
using Kyoo.InternalAPI.MetadataProvider.TheTvDB;
|
|
||||||
using Kyoo.InternalAPI.Utility;
|
|
||||||
using Kyoo.Models;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Web;
|
|
||||||
#pragma warning disable 1998
|
|
||||||
|
|
||||||
namespace Kyoo.InternalAPI.MetadataProvider
|
|
||||||
{
|
|
||||||
[MetaProvider]
|
|
||||||
public class ProviderTheTvDB : HelperTvDB, IMetadataProvider
|
|
||||||
{
|
|
||||||
public async Task<Collection> GetCollectionFromName(string name)
|
|
||||||
{
|
|
||||||
return new Collection(-1, Slugifier.ToSlug(name), name, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<Show> GetShowFromName(string showName, string showPath)
|
|
||||||
{
|
|
||||||
string token = await Authentificate();
|
|
||||||
|
|
||||||
if (token != null)
|
|
||||||
{
|
|
||||||
WebRequest request = WebRequest.Create("https://api.thetvdb.com/search/series?name=" + HttpUtility.UrlEncode(showName));
|
|
||||||
request.Method = "GET";
|
|
||||||
request.Timeout = 12000;
|
|
||||||
request.ContentType = "application/json";
|
|
||||||
request.Headers.Add(HttpRequestHeader.Authorization, "Bearer " + token);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
HttpWebResponse response = (HttpWebResponse)await request.GetResponseAsync();
|
|
||||||
|
|
||||||
if (response.StatusCode == HttpStatusCode.OK)
|
|
||||||
{
|
|
||||||
Stream stream = response.GetResponseStream();
|
|
||||||
if (stream != null)
|
|
||||||
{
|
|
||||||
using StreamReader reader = new StreamReader(stream);
|
|
||||||
string content = await reader.ReadToEndAsync();
|
|
||||||
stream.Close();
|
|
||||||
response.Close();
|
|
||||||
|
|
||||||
dynamic obj = JsonConvert.DeserializeObject(content);
|
|
||||||
dynamic data = obj.data[0];
|
|
||||||
|
|
||||||
Show show = new Show(-1,
|
|
||||||
ToSlug(showName),
|
|
||||||
(string) data.seriesName,
|
|
||||||
((JArray) data.aliases).ToObject<IEnumerable<string>>(),
|
|
||||||
showPath,
|
|
||||||
(string) data.overview,
|
|
||||||
null, //trailer
|
|
||||||
null, //genres (no info with this request)
|
|
||||||
GetStatus((string) data.status),
|
|
||||||
GetYear((string) data.firstAired),
|
|
||||||
null, //endYear
|
|
||||||
string.Format("{0}={1}|", Provider, (string) data.id));
|
|
||||||
return (await GetShowByID(GetID(show.ExternalIDs))).Set(show.Slug, show.Path) ?? show;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Debug.WriteLine("&TheTvDB Provider couldn't work for this show: " + showName + ".\nError Code: " + response.StatusCode + " Message: " + response.StatusDescription);
|
|
||||||
response.Close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (WebException ex)
|
|
||||||
{
|
|
||||||
Debug.WriteLine("&TheTvDB Provider couldn't work for this show: " + showName + ".\nError Code: " + ex.Status);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Show() { Slug = ToSlug(showName), Title = showName, Path = showPath };
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<Show> GetShowByID(string id)
|
|
||||||
{
|
|
||||||
string token = await Authentificate();
|
|
||||||
|
|
||||||
if (token == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
WebRequest request = WebRequest.Create("https://api.thetvdb.com/series/" + id);
|
|
||||||
request.Method = "GET";
|
|
||||||
request.Timeout = 12000;
|
|
||||||
request.ContentType = "application/json";
|
|
||||||
request.Headers.Add(HttpRequestHeader.Authorization, "Bearer " + token);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
HttpWebResponse response = (HttpWebResponse)await request.GetResponseAsync();
|
|
||||||
|
|
||||||
if (response.StatusCode == HttpStatusCode.OK)
|
|
||||||
{
|
|
||||||
Stream stream = response.GetResponseStream();
|
|
||||||
if (stream != null)
|
|
||||||
{
|
|
||||||
using StreamReader reader = new StreamReader(stream);
|
|
||||||
string content = await reader.ReadToEndAsync();
|
|
||||||
stream.Close();
|
|
||||||
response.Close();
|
|
||||||
|
|
||||||
dynamic model = JsonConvert.DeserializeObject(content);
|
|
||||||
dynamic data = model.data;
|
|
||||||
|
|
||||||
Show show = new Show(-1,
|
|
||||||
null, //Slug
|
|
||||||
(string) data.seriesName,
|
|
||||||
((JArray) data.aliases).ToObject<IEnumerable<string>>(),
|
|
||||||
null, //Path
|
|
||||||
(string) data.overview,
|
|
||||||
null, //Trailer
|
|
||||||
GetGenres(((JArray) data.genre).ToObject<string[]>()),
|
|
||||||
GetStatus((string) data.status),
|
|
||||||
GetYear((string) data.firstAired),
|
|
||||||
null, //endYear
|
|
||||||
$"TvDB={id}|");
|
|
||||||
await GetImages(show);
|
|
||||||
return show;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Debug.WriteLine("&TheTvDB Provider couldn't work for the show with the id: " + id + ".\nError Code: " + response.StatusCode + " Message: " + response.StatusDescription);
|
|
||||||
response.Close();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
catch(WebException ex)
|
|
||||||
{
|
|
||||||
Debug.WriteLine("&TheTvDB Provider couldn't work for the show with the id: " + id + ".\nError Code: " + ex.Status);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<Show> GetImages(Show show)
|
|
||||||
{
|
|
||||||
Debug.WriteLine("&Getting images for: " + show.Title);
|
|
||||||
string id = GetID(show.ExternalIDs);
|
|
||||||
|
|
||||||
if (id == null)
|
|
||||||
return show;
|
|
||||||
|
|
||||||
string token = await Authentificate();
|
|
||||||
|
|
||||||
if (token == null)
|
|
||||||
return show;
|
|
||||||
|
|
||||||
Dictionary<ImageType, string> imageTypes = new Dictionary<ImageType, string> { { ImageType.Poster, "poster" }, { ImageType.Background, "fanart" } };
|
|
||||||
|
|
||||||
foreach (KeyValuePair<ImageType, string> type in imageTypes)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
WebRequest request = WebRequest.Create("https://api.thetvdb.com/series/" + id + "/images/query?keyType=" + type.Value);
|
|
||||||
request.Method = "GET";
|
|
||||||
request.Timeout = 12000;
|
|
||||||
request.ContentType = "application/json";
|
|
||||||
request.Headers.Add(HttpRequestHeader.Authorization, "Bearer " + token);
|
|
||||||
|
|
||||||
HttpWebResponse response = (HttpWebResponse)await request.GetResponseAsync();
|
|
||||||
|
|
||||||
if (response.StatusCode == HttpStatusCode.OK)
|
|
||||||
{
|
|
||||||
Stream stream = response.GetResponseStream();
|
|
||||||
if (stream != null)
|
|
||||||
{
|
|
||||||
using StreamReader reader = new StreamReader(stream);
|
|
||||||
string content = await reader.ReadToEndAsync();
|
|
||||||
stream.Close();
|
|
||||||
response.Close();
|
|
||||||
|
|
||||||
dynamic model = JsonConvert.DeserializeObject(content);
|
|
||||||
//Should implement language selection here
|
|
||||||
dynamic data = ((IEnumerable<dynamic>) model.data)
|
|
||||||
.OrderByDescending(x => x.ratingsInfo.average)
|
|
||||||
.ThenByDescending(x => x.ratingsInfo.count).FirstOrDefault();
|
|
||||||
if (data != null)
|
|
||||||
SetImage(show, "https://www.thetvdb.com/banners/" + data.fileName, type.Key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Debug.WriteLine("&TheTvDB Provider couldn't get " + type + " for the show with the id: " + id + ".\nError Code: " + response.StatusCode + " Message: " + response.StatusDescription);
|
|
||||||
response.Close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (WebException ex)
|
|
||||||
{
|
|
||||||
Debug.WriteLine("&TheTvDB Provider couldn't get " + type + " for the show with the id: " + id + ".\nError Code: " + ex.Status);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return show;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<Season> GetSeason(string showName, long seasonNumber)
|
|
||||||
{
|
|
||||||
return new Season(-1, -1, seasonNumber, "Season " + seasonNumber, null, null, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<string> GetSeasonImage(string showName, long seasonNumber)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<Episode> GetEpisode(string externalIDs, long seasonNumber, long episodeNumber, long absoluteNumber, string episodePath)
|
|
||||||
{
|
|
||||||
string id = GetID(externalIDs);
|
|
||||||
|
|
||||||
if (id == null)
|
|
||||||
return new Episode(seasonNumber, episodeNumber, absoluteNumber, null, null, null, -1, null, externalIDs);
|
|
||||||
|
|
||||||
string token = await Authentificate();
|
|
||||||
|
|
||||||
if (token == null)
|
|
||||||
return new Episode(seasonNumber, episodeNumber, absoluteNumber, null, null, null, -1, null, externalIDs);
|
|
||||||
|
|
||||||
WebRequest request;
|
|
||||||
if (absoluteNumber != -1)
|
|
||||||
request = WebRequest.Create("https://api.thetvdb.com/series/" + id + "/episodes/query?absoluteNumber=" + absoluteNumber);
|
|
||||||
else
|
|
||||||
request = WebRequest.Create("https://api.thetvdb.com/series/" + id + "/episodes/query?airedSeason=" + seasonNumber + "&airedEpisode=" + episodeNumber);
|
|
||||||
|
|
||||||
request.Method = "GET";
|
|
||||||
request.Timeout = 12000;
|
|
||||||
request.ContentType = "application/json";
|
|
||||||
request.Headers.Add(HttpRequestHeader.Authorization, "Bearer " + token);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
HttpWebResponse response = (HttpWebResponse)await request.GetResponseAsync();
|
|
||||||
|
|
||||||
if (response.StatusCode == HttpStatusCode.OK)
|
|
||||||
{
|
|
||||||
Stream stream = response.GetResponseStream();
|
|
||||||
if (stream != null)
|
|
||||||
{
|
|
||||||
using StreamReader reader = new StreamReader(stream);
|
|
||||||
string content = await reader.ReadToEndAsync();
|
|
||||||
stream.Close();
|
|
||||||
response.Close();
|
|
||||||
|
|
||||||
dynamic data = JsonConvert.DeserializeObject(content);
|
|
||||||
dynamic episode = data.data[0];
|
|
||||||
|
|
||||||
DateTime dateTime = DateTime.ParseExact((string)episode.firstAired, "yyyy-MM-dd", CultureInfo.InvariantCulture);
|
|
||||||
|
|
||||||
if (absoluteNumber == -1)
|
|
||||||
absoluteNumber = (long?)episode.absoluteNumber ?? -1;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
seasonNumber = episode.airedSeason;
|
|
||||||
episodeNumber = episode.airedEpisodeNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Episode(seasonNumber, episodeNumber, absoluteNumber, (string)episode.episodeName, (string)episode.overview, dateTime, -1, "https://www.thetvdb.com/banners/" + episode.filename, string.Format("TvDB={0}|", episode.id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Debug.WriteLine("&TheTvDB Provider couldn't work for the episode number: " + episodeNumber + ".\nError Code: " + response.StatusCode + " Message: " + response.StatusDescription);
|
|
||||||
response.Close();
|
|
||||||
return new Episode(seasonNumber, episodeNumber, absoluteNumber, null, null, null, -1, null, externalIDs);
|
|
||||||
}
|
|
||||||
catch (WebException ex)
|
|
||||||
{
|
|
||||||
Debug.WriteLine("&TheTvDB Provider couldn't work for the episode number: " + episodeNumber + ".\nError Code: " + ex.Status);
|
|
||||||
return new Episode(seasonNumber, episodeNumber, absoluteNumber, null, null, null, -1, null, externalIDs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<List<People>> GetPeople(string externalIDs)
|
|
||||||
{
|
|
||||||
string id = GetID(externalIDs);
|
|
||||||
|
|
||||||
if (id == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
string token = await Authentificate();
|
|
||||||
|
|
||||||
if (token == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
WebRequest request = WebRequest.Create("https://api.thetvdb.com/series/" + id + "/actors");
|
|
||||||
request.Method = "GET";
|
|
||||||
request.Timeout = 12000;
|
|
||||||
request.ContentType = "application/json";
|
|
||||||
request.Headers.Add(HttpRequestHeader.Authorization, "Bearer " + token);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
HttpWebResponse response = (HttpWebResponse)await request.GetResponseAsync();
|
|
||||||
|
|
||||||
if (response.StatusCode == HttpStatusCode.OK)
|
|
||||||
{
|
|
||||||
Stream stream = response.GetResponseStream();
|
|
||||||
if (stream != null)
|
|
||||||
{
|
|
||||||
using StreamReader reader = new StreamReader(stream);
|
|
||||||
string content = await reader.ReadToEndAsync();
|
|
||||||
stream.Close();
|
|
||||||
response.Close();
|
|
||||||
|
|
||||||
dynamic data = JsonConvert.DeserializeObject(content);
|
|
||||||
return (((IEnumerable<dynamic>)data.data).OrderBy(x => x.sortOrder)).ToList().ConvertAll(x => { return new People(-1, ToSlug((string)x.name), (string)x.name, (string)x.role, null, "https://www.thetvdb.com/banners/" + (string)x.image, string.Format("TvDB={0}|", x.id)); });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Debug.WriteLine("&TheTvDB Provider couldn't work for the actors of the show: " + id + ".\nError Code: " + response.StatusCode + " Message: " + response.StatusDescription);
|
|
||||||
response.Close();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
catch (WebException ex)
|
|
||||||
{
|
|
||||||
Debug.WriteLine("&TheTvDB Provider couldn't work for the actors of the show: " + id + ".\nError Code: " + ex.Status);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Kyoo.InternalAPI.MetadataProvider
|
|
||||||
{
|
|
||||||
[AttributeUsage(AttributeTargets.Class)]
|
|
||||||
public class MetaProvider : Attribute
|
|
||||||
{
|
|
||||||
public MetaProvider()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,60 +0,0 @@
|
|||||||
using Kyoo.InternalAPI.Utility;
|
|
||||||
using Kyoo.Models;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Kyoo.InternalAPI.MetadataProvider
|
|
||||||
{
|
|
||||||
public abstract class ProviderHelper
|
|
||||||
{
|
|
||||||
public abstract string Provider { get; }
|
|
||||||
|
|
||||||
public string GetID(string externalIDs)
|
|
||||||
{
|
|
||||||
if (externalIDs?.Contains(Provider) == true)
|
|
||||||
{
|
|
||||||
int startIndex = externalIDs.IndexOf(Provider) + Provider.Length + 1; //The + 1 is for the '='
|
|
||||||
return externalIDs.Substring(startIndex, externalIDs.IndexOf('|', startIndex) - startIndex);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string ToSlug(string showTitle)
|
|
||||||
{
|
|
||||||
return Slugifier.ToSlug(showTitle);
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum ImageType { Poster, Background, Thumbnail, Logo }
|
|
||||||
|
|
||||||
public void SetImage(Show show, string imgUrl, ImageType type)
|
|
||||||
{
|
|
||||||
switch(type)
|
|
||||||
{
|
|
||||||
case ImageType.Poster:
|
|
||||||
show.ImgPrimary = imgUrl;
|
|
||||||
break;
|
|
||||||
case ImageType.Thumbnail:
|
|
||||||
show.ImgThumb = imgUrl;
|
|
||||||
break;
|
|
||||||
case ImageType.Logo:
|
|
||||||
show.ImgLogo = imgUrl;
|
|
||||||
break;
|
|
||||||
case ImageType.Background:
|
|
||||||
show.ImgBackdrop = imgUrl;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<Genre> GetGenres(string[] input)
|
|
||||||
{
|
|
||||||
List<Genre> genres = new List<Genre>();
|
|
||||||
|
|
||||||
foreach (string genre in input)
|
|
||||||
genres.Add(new Genre(ToSlug(genre), genre));
|
|
||||||
|
|
||||||
return genres;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,163 +0,0 @@
|
|||||||
using Kyoo.InternalAPI.MetadataProvider;
|
|
||||||
using Kyoo.InternalAPI.ThumbnailsManager;
|
|
||||||
using Kyoo.Models;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Kyoo.InternalAPI
|
|
||||||
{
|
|
||||||
public class ProviderManager : IMetadataProvider
|
|
||||||
{
|
|
||||||
private readonly List<IMetadataProvider> providers = new List<IMetadataProvider>();
|
|
||||||
private readonly IThumbnailsManager thumbnailsManager;
|
|
||||||
private readonly IConfiguration config;
|
|
||||||
|
|
||||||
public ProviderManager(IThumbnailsManager thumbnailsManager, IConfiguration configuration)
|
|
||||||
{
|
|
||||||
this.thumbnailsManager = thumbnailsManager;
|
|
||||||
config = configuration;
|
|
||||||
LoadProviders();
|
|
||||||
}
|
|
||||||
|
|
||||||
void LoadProviders()
|
|
||||||
{
|
|
||||||
providers.Clear();
|
|
||||||
providers.Add(new ProviderTheTvDB());
|
|
||||||
|
|
||||||
string pluginFolder = config.GetValue<string>("providerPlugins");
|
|
||||||
|
|
||||||
if (Directory.Exists(pluginFolder))
|
|
||||||
{
|
|
||||||
string[] pluginsPaths = Directory.GetFiles(pluginFolder);
|
|
||||||
List<Assembly> plugins = new List<Assembly>();
|
|
||||||
List<Type> types = new List<Type>();
|
|
||||||
|
|
||||||
for (int i = 0; i < pluginsPaths.Length; i++)
|
|
||||||
{
|
|
||||||
plugins.Add(Assembly.LoadFile(pluginsPaths[i]));
|
|
||||||
types.AddRange(plugins[i].GetTypes());
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Type> providersPlugins = types.FindAll(x =>
|
|
||||||
{
|
|
||||||
object[] atr = x.GetCustomAttributes(typeof(MetaProvider), false);
|
|
||||||
|
|
||||||
if (atr == null || atr.Length == 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
List<Type> interfaces = new List<Type>(x.GetInterfaces());
|
|
||||||
|
|
||||||
if (interfaces.Contains(typeof(IMetadataProvider)))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
providers.AddRange(providersPlugins.ConvertAll<IMetadataProvider>(x => Activator.CreateInstance(x) as IMetadataProvider));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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(); //Should do something if the return is null;
|
|
||||||
}
|
|
||||||
|
|
||||||
//For all the following methods, it should use all providers and merge the data.
|
|
||||||
|
|
||||||
public Task<Collection> GetCollectionFromName(string name)
|
|
||||||
{
|
|
||||||
return providers[0].GetCollectionFromName(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<Show> GetImages(Show show)
|
|
||||||
{
|
|
||||||
return providers[0].GetImages(show);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<Season> GetSeason(string showName, int 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 async Task<Show> GetShowByID(string 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 async Task<Show> GetShowFromName(string showName, string 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 await thumbnailsManager.Validate(show);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<Season> GetSeason(string showName, long 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)
|
|
||||||
{
|
|
||||||
//Should select the best provider for this show.
|
|
||||||
|
|
||||||
return providers[0].GetSeasonImage(showName, seasonNumber);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<Episode> GetEpisode(string externalIDs, long seasonNumber, long episodeNumber, long absoluteNumber, string episodePath)
|
|
||||||
{
|
|
||||||
List<Episode> datas = new List<Episode>();
|
|
||||||
for (int i = 0; i < providers.Count; i++)
|
|
||||||
{
|
|
||||||
datas.Add(await providers[i].GetEpisode(externalIDs, seasonNumber, episodeNumber, absoluteNumber, episodePath));
|
|
||||||
}
|
|
||||||
|
|
||||||
Episode episode = Merge(datas);
|
|
||||||
episode.Path = episodePath;
|
|
||||||
return await thumbnailsManager.Validate(episode);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<List<People>> GetPeople(string id)
|
|
||||||
{
|
|
||||||
List<People> actors = await providers[0].GetPeople(id);
|
|
||||||
return await thumbnailsManager.Validate(actors);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
using System.Text.RegularExpressions;
|
|
||||||
|
|
||||||
namespace Kyoo.InternalAPI.Utility
|
|
||||||
{
|
|
||||||
public class Slugifier
|
|
||||||
{
|
|
||||||
public static string ToSlug(string showTitle)
|
|
||||||
{
|
|
||||||
if (showTitle == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
//First to lower case
|
|
||||||
showTitle = showTitle.ToLowerInvariant();
|
|
||||||
|
|
||||||
//Remove all accents
|
|
||||||
//var bytes = Encoding.GetEncoding("Cyrillic").GetBytes(showTitle);
|
|
||||||
//showTitle = Encoding.ASCII.GetString(bytes);
|
|
||||||
|
|
||||||
//Replace spaces
|
|
||||||
showTitle = Regex.Replace(showTitle, @"\s", "-", RegexOptions.Compiled);
|
|
||||||
|
|
||||||
//Remove invalid chars
|
|
||||||
showTitle = Regex.Replace(showTitle, @"[^\w\s\p{Pd}]", "", RegexOptions.Compiled);
|
|
||||||
|
|
||||||
//Trim dashes from end
|
|
||||||
showTitle = showTitle.Trim('-', '_');
|
|
||||||
|
|
||||||
//Replace double occurences of - or \_
|
|
||||||
showTitle = Regex.Replace(showTitle, @"([-_]){2,}", "$1", RegexOptions.Compiled);
|
|
||||||
|
|
||||||
return showTitle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -17,6 +17,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.7" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.0.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.SpaServices" Version="3.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.SpaServices" Version="3.0.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="3.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="3.0.0" />
|
||||||
@ -29,6 +30,8 @@
|
|||||||
<Content Remove="$(SpaRoot)**" />
|
<Content Remove="$(SpaRoot)**" />
|
||||||
<None Remove="$(SpaRoot)**" />
|
<None Remove="$(SpaRoot)**" />
|
||||||
<None Include="$(SpaRoot)**" Exclude="$(SpaRoot)node_modules\**" />
|
<None Include="$(SpaRoot)**" Exclude="$(SpaRoot)node_modules\**" />
|
||||||
|
<None Remove="Controllers\MetadataProvider\**" />
|
||||||
|
<Content Remove="Controllers\MetadataProvider\**" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@ -44,6 +47,15 @@
|
|||||||
<EmbeddedResource Include="kyoo.sh">
|
<EmbeddedResource Include="kyoo.sh">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</EmbeddedResource>
|
</EmbeddedResource>
|
||||||
|
<EmbeddedResource Remove="Controllers\MetadataProvider\**" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Kyoo.Common\Kyoo.Common.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Remove="Controllers\MetadataProvider\**" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="DebugEnsureNodeEnv" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') ">
|
<Target Name="DebugEnsureNodeEnv" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') ">
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=controllers_005Cmetadataprovider_005Cimplementations/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=controllers_005Cmetadataprovider_005Cimplementations_005Cthetvdb/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=controllers_005Ctranscoder/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=internalapi_005Ccrawler/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=internalapi_005Ccrawler/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=internalapi_005Clibrarymanager/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=internalapi_005Clibrarymanager/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=internalapi_005Cmetadataprovider_005Cimplementations/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=internalapi_005Cmetadataprovider_005Cimplementations/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
@ -1,50 +0,0 @@
|
|||||||
using Kyoo.InternalAPI;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Kyoo.Models
|
|
||||||
{
|
|
||||||
public class Collection
|
|
||||||
{
|
|
||||||
[JsonIgnore] public long id;
|
|
||||||
public string Slug;
|
|
||||||
public string Name;
|
|
||||||
public string Poster;
|
|
||||||
public string Overview;
|
|
||||||
[JsonIgnore] public string ImgPrimary;
|
|
||||||
public IEnumerable<Show> Shows;
|
|
||||||
|
|
||||||
public Collection() { }
|
|
||||||
|
|
||||||
public Collection(long id, string slug, string name, string overview, string imgPrimary)
|
|
||||||
{
|
|
||||||
this.id = id;
|
|
||||||
Slug = slug;
|
|
||||||
Name = name;
|
|
||||||
Overview = overview;
|
|
||||||
ImgPrimary = imgPrimary;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Collection FromReader(System.Data.SQLite.SQLiteDataReader reader)
|
|
||||||
{
|
|
||||||
Collection col = new Collection((long)reader["id"],
|
|
||||||
reader["slug"] as string,
|
|
||||||
reader["name"] as string,
|
|
||||||
reader["overview"] as string,
|
|
||||||
reader["imgPrimary"] as string);
|
|
||||||
col.Poster = "poster/" + col.Slug;
|
|
||||||
return col;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Show AsShow()
|
|
||||||
{
|
|
||||||
return new Show(-1, Slug, Name, null, null, Overview, null, null, null, null, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Collection SetShows(ILibraryManager libraryManager)
|
|
||||||
{
|
|
||||||
Shows = libraryManager.GetShowsInCollection(id);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Kyoo.Models
|
|
||||||
{
|
|
||||||
public class SearchResult
|
|
||||||
{
|
|
||||||
public string query;
|
|
||||||
public IEnumerable<Show> shows;
|
|
||||||
public IEnumerable<Episode> episodes;
|
|
||||||
public IEnumerable<People> people;
|
|
||||||
public IEnumerable<Genre> genres;
|
|
||||||
public IEnumerable<Studio> studios;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,14 +1,27 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Kyoo.Controllers;
|
||||||
using Microsoft.AspNetCore;
|
using Microsoft.AspNetCore;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
|
|
||||||
namespace Kyoo
|
namespace Kyoo
|
||||||
{
|
{
|
||||||
public class Program
|
public class Program
|
||||||
{
|
{
|
||||||
public static void Main(string[] args)
|
public static async Task Main(string[] args)
|
||||||
{
|
{
|
||||||
CreateWebHostBuilder(args).Build().Run();
|
IWebHost host = CreateWebHostBuilder(args).Build();
|
||||||
|
|
||||||
|
using (IServiceScope serviceScope = host.Services.CreateScope())
|
||||||
|
{
|
||||||
|
IPluginManager pluginManager = serviceScope.ServiceProvider.GetService<IPluginManager>();
|
||||||
|
pluginManager.ReloadPlugins();
|
||||||
|
|
||||||
|
ICrawler crawler = serviceScope.ServiceProvider.GetService<ICrawler>();
|
||||||
|
crawler.Start();
|
||||||
|
}
|
||||||
|
await host.RunAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
|
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
using Kyoo.InternalAPI;
|
using Kyoo.Controllers;
|
||||||
using Kyoo.InternalAPI.ThumbnailsManager;
|
using Kyoo.Controllers.ThumbnailsManager;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.SpaServices.AngularCli;
|
using Microsoft.AspNetCore.SpaServices.AngularCli;
|
||||||
@ -30,15 +30,14 @@ namespace Kyoo
|
|||||||
});
|
});
|
||||||
|
|
||||||
services.AddControllers().AddNewtonsoftJson();
|
services.AddControllers().AddNewtonsoftJson();
|
||||||
|
services.AddHttpClient();
|
||||||
//Services needed in the private and in the public API
|
|
||||||
services.AddSingleton<ILibraryManager, LibraryManager>();
|
services.AddSingleton<ILibraryManager, LibraryManager>();
|
||||||
services.AddSingleton<ITranscoder, Transcoder>();
|
services.AddSingleton<ITranscoder, Transcoder>();
|
||||||
|
|
||||||
//Services used to get informations about files and register them
|
|
||||||
services.AddSingleton<ICrawler, Crawler>();
|
|
||||||
services.AddSingleton<IThumbnailsManager, ThumbnailsManager>();
|
services.AddSingleton<IThumbnailsManager, ThumbnailsManager>();
|
||||||
services.AddSingleton<IMetadataProvider, ProviderManager>();
|
services.AddSingleton<IProviderManager, ProviderManager>();
|
||||||
|
services.AddSingleton<ICrawler, Crawler>();
|
||||||
|
services.AddSingleton<IPluginManager, PluginManager>();
|
||||||
}
|
}
|
||||||
|
|
||||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
"transmuxTempPath": "/tmp/cached/kyoo/transmux",
|
"transmuxTempPath": "/tmp/cached/kyoo/transmux",
|
||||||
"transcodeTempPath": "/tmp/cached/kyoo/transcode",
|
"transcodeTempPath": "/tmp/cached/kyoo/transcode",
|
||||||
"peoplePath": "/tmp/people",
|
"peoplePath": "/tmp/people",
|
||||||
"plugins": "/tmp/plugins",
|
"plugins": "plugins/",
|
||||||
"regex": "^(\\/(?<Collection>.+?))?\\/.*\\/(?<ShowTitle>.+?) S(?<Season>\\d+)E(?<Episode>\\d+)",
|
"regex": "^(\\/(?<Collection>.+?))?\\/.*\\/(?<ShowTitle>.+?) S(?<Season>\\d+)E(?<Episode>\\d+)",
|
||||||
"absoluteRegex": ".*\\/(?<ShowTitle>.+?) (?<AbsoluteNumber>\\d+)"
|
"absoluteRegex": ".*\\/(?<ShowTitle>.+?) (?<AbsoluteNumber>\\d+)"
|
||||||
}
|
}
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
using Kyoo.InternalAPI;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using NUnit.Framework;
|
|
||||||
|
|
||||||
namespace UnitTests.Kyoo_InternalAPI
|
|
||||||
{
|
|
||||||
public class LibraryTests
|
|
||||||
{
|
|
||||||
private IConfiguration config;
|
|
||||||
private ILibraryManager libraryManager;
|
|
||||||
|
|
||||||
[SetUp]
|
|
||||||
public void Setup()
|
|
||||||
{
|
|
||||||
config = new ConfigurationBuilder()
|
|
||||||
.AddJsonFile("appsettings.json")
|
|
||||||
.Build();
|
|
||||||
libraryManager = new LibraryManager(config);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,40 +0,0 @@
|
|||||||
using Kyoo.InternalAPI;
|
|
||||||
using Kyoo.InternalAPI.ThumbnailsManager;
|
|
||||||
using Kyoo.Models;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
|
||||||
using NUnit.Framework;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace UnitTests.Kyoo_InternalAPI
|
|
||||||
{
|
|
||||||
public class ThumbnailsTests
|
|
||||||
{
|
|
||||||
private IConfiguration config;
|
|
||||||
|
|
||||||
[SetUp]
|
|
||||||
public void Setup()
|
|
||||||
{
|
|
||||||
config = new ConfigurationBuilder()
|
|
||||||
.AddJsonFile("appsettings.json")
|
|
||||||
.Build();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public async Task DownloadShowImages()
|
|
||||||
{
|
|
||||||
LibraryManager library = new LibraryManager(config);
|
|
||||||
ThumbnailsManager manager = new ThumbnailsManager(config);
|
|
||||||
Show show = library.GetShowBySlug(library.GetShows().FirstOrDefault().Slug);
|
|
||||||
Debug.WriteLine("&Show: " + show.Path);
|
|
||||||
string posterPath = Path.Combine(show.Path, "poster.jpg");
|
|
||||||
File.Delete(posterPath);
|
|
||||||
|
|
||||||
await manager.Validate(show);
|
|
||||||
long posterLength = new FileInfo(posterPath).Length;
|
|
||||||
Assert.IsTrue(posterLength > 0, "Poster size is zero for the tested show (" + posterPath + ")");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
|
||||||
<RootNamespace>UnitTests</RootNamespace>
|
|
||||||
|
|
||||||
<IsPackable>false</IsPackable>
|
|
||||||
|
|
||||||
<AssemblyName>UnitTests</AssemblyName>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.0.0" />
|
|
||||||
<PackageReference Include="nunit" Version="3.12.0" />
|
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="3.13.0" />
|
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\Kyoo\Kyoo.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<None Update="appsettings.json">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"server.urls": "http://0.0.0.0:5000",
|
|
||||||
"https_port": 44300,
|
|
||||||
"Logging": {
|
|
||||||
"LogLevel": {
|
|
||||||
"Default": "Warning"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"AllowedHosts": "*",
|
|
||||||
|
|
||||||
"databasePath": "C://Projects/database.db",
|
|
||||||
"tempPath": "C:\\\\Projects\\temp",
|
|
||||||
"peoplePath": "D:\\\\Videos\\People",
|
|
||||||
"plugins": "C:\\Projects\\Kyoo\\Debug",
|
|
||||||
"providerPlugins": "C://Projects/Plugins/Providers",
|
|
||||||
"regex": "^(\\\\(?<Collection>.+?))?\\\\.*\\\\(?<ShowTitle>.+?) S(?<Season>\\d+)E(?<Episode>\\d+)",
|
|
||||||
"absoluteRegex": ".*\\\\(?<ShowTitle>.+?) (?<AbsoluteNumber>\\d+)"
|
|
||||||
}
|
|
@ -1 +1 @@
|
|||||||
Subproject commit d8f72ff87020f2d38a4552f6083ef0659bf1f2be
|
Subproject commit bedf98f85d2cf669a1d1c4af35271841133cfec2
|
Loading…
x
Reference in New Issue
Block a user