mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-24 02:02:36 -04:00
Adding a task to rescan a show and it's related properties
This commit is contained in:
parent
562c39b970
commit
40ea4a37af
@ -61,7 +61,9 @@ namespace Kyoo.Controllers
|
||||
long EditShow(Show show);
|
||||
long RegisterMovie(Episode movie);
|
||||
long RegisterSeason(Season season);
|
||||
long EditSeason(Season season);
|
||||
long RegisterEpisode(Episode episode);
|
||||
long EditEpisode(Episode episode);
|
||||
long RegisterTrack(Track track);
|
||||
void RegisterShowLinks(Library library, Collection collection, Show show);
|
||||
IEnumerable<MetadataID> ValidateExternalIDs(IEnumerable<MetadataID> ids);
|
||||
|
@ -7,7 +7,8 @@ namespace Kyoo.Controllers
|
||||
public interface IThumbnailsManager
|
||||
{
|
||||
Task<Show> Validate(Show show, bool alwaysDownload = false);
|
||||
Task<IEnumerable<PeopleLink>> Validate(IEnumerable<PeopleLink> actors, bool alwaysDownload = false);
|
||||
Task<Season> Validate(Season season, bool alwaysDownload = false);
|
||||
Task<Episode> Validate(Episode episode, bool alwaysDownload = false);
|
||||
Task<IEnumerable<PeopleLink>> Validate(IEnumerable<PeopleLink> actors, bool alwaysDownload = false);
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,8 @@ namespace Kyoo.Models
|
||||
[JsonIgnore] public long ShowID { get; set; }
|
||||
|
||||
public long SeasonNumber { get; set; } = -1;
|
||||
|
||||
public string Slug => $"{Show.Title}-s{SeasonNumber}";
|
||||
public string Title { get; set; }
|
||||
public string Overview { get; set; }
|
||||
public long? Year { get; set; }
|
||||
|
@ -1,6 +1,5 @@
|
||||
using Kyoo.Models.Watch;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
@ -68,8 +67,47 @@ namespace Kyoo.Models
|
||||
get => isForced;
|
||||
set => isForced = value;
|
||||
}
|
||||
public string DisplayName;
|
||||
public string Link;
|
||||
|
||||
public string DisplayName
|
||||
{
|
||||
get
|
||||
{
|
||||
string language = GetLanguage(Language);
|
||||
|
||||
if (language == null)
|
||||
return $"Unknown Language (id: {ID.ToString()})";
|
||||
CultureInfo info = CultureInfo.GetCultures(CultureTypes.NeutralCultures)
|
||||
.FirstOrDefault(x => x.ThreeLetterISOLanguageName == language);
|
||||
string name = info?.EnglishName ?? language;
|
||||
if (IsForced)
|
||||
name += " Forced";
|
||||
if (Title != null && Title.Length > 1)
|
||||
name += " - " + Title;
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
public string Link
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Type != StreamType.Subtitle)
|
||||
return null;
|
||||
string link = "/subtitle/" + Episode.Link + "." + Language;
|
||||
if (IsForced)
|
||||
link += "-forced";
|
||||
switch (Codec)
|
||||
{
|
||||
case "ass":
|
||||
link += ".ass";
|
||||
break;
|
||||
case "subrip":
|
||||
link += ".srt";
|
||||
break;
|
||||
}
|
||||
return link;
|
||||
}
|
||||
}
|
||||
|
||||
[JsonIgnore] public bool IsExternal { get; set; }
|
||||
[JsonIgnore] public virtual Episode Episode { get; set; }
|
||||
@ -88,46 +126,12 @@ namespace Kyoo.Models
|
||||
IsExternal = false;
|
||||
}
|
||||
|
||||
public Track SetLink(string episodeSlug)
|
||||
//Converting mkv track language to c# system language tag.
|
||||
public static string GetLanguage(string mkvLanguage)
|
||||
{
|
||||
if (Type == StreamType.Subtitle)
|
||||
{
|
||||
string language = Language;
|
||||
//Converting mkv track language to c# system language tag.
|
||||
if (language == "fre")
|
||||
language = "fra";
|
||||
|
||||
if (language == null)
|
||||
{
|
||||
Language = ID.ToString();
|
||||
DisplayName = $"Unknown Language (id: {ID.ToString()})";
|
||||
}
|
||||
else
|
||||
DisplayName = CultureInfo.GetCultures(CultureTypes.NeutralCultures).FirstOrDefault(x => x.ThreeLetterISOLanguageName == language)?.EnglishName ?? language;
|
||||
Link = "/subtitle/" + episodeSlug + "." + Language;
|
||||
|
||||
if (IsForced)
|
||||
{
|
||||
DisplayName += " Forced";
|
||||
Link += "-forced";
|
||||
}
|
||||
|
||||
if (Title != null && Title.Length > 1)
|
||||
DisplayName += " - " + Title;
|
||||
|
||||
switch (Codec)
|
||||
{
|
||||
case "ass":
|
||||
Link += ".ass";
|
||||
break;
|
||||
case "subrip":
|
||||
Link += ".srt";
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
Link = null;
|
||||
return this;
|
||||
if (mkvLanguage == "fre")
|
||||
return "fra";
|
||||
return mkvLanguage;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Kyoo.Models;
|
||||
|
||||
@ -14,31 +16,28 @@ namespace Kyoo
|
||||
|
||||
public static class Utility
|
||||
{
|
||||
public static string ToSlug(string name)
|
||||
public static string ToSlug(string str)
|
||||
{
|
||||
if (name == null)
|
||||
if (str == null)
|
||||
return null;
|
||||
|
||||
//First to lower case
|
||||
name = name.ToLowerInvariant();
|
||||
str = str.ToLowerInvariant();
|
||||
|
||||
string normalizedString = str.Normalize(NormalizationForm.FormD);
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
foreach (char c in normalizedString)
|
||||
{
|
||||
UnicodeCategory unicodeCategory = CharUnicodeInfo.GetUnicodeCategory(c);
|
||||
if (unicodeCategory != UnicodeCategory.NonSpacingMark)
|
||||
stringBuilder.Append(c);
|
||||
}
|
||||
str = stringBuilder.ToString().Normalize(NormalizationForm.FormC);
|
||||
|
||||
//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;
|
||||
str = Regex.Replace(str, @"\s", "-", RegexOptions.Compiled);
|
||||
str = Regex.Replace(str, @"[^\w\s\p{Pd}]", "", RegexOptions.Compiled);
|
||||
str = str.Trim('-', '_');
|
||||
str = Regex.Replace(str, @"([-_]){2,}", "$1", RegexOptions.Compiled);
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
|
@ -5,7 +5,6 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Kyoo.Models.Exceptions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Query;
|
||||
|
||||
namespace Kyoo.Controllers
|
||||
{
|
||||
@ -39,9 +38,9 @@ namespace Kyoo.Controllers
|
||||
public (Track video, IEnumerable<Track> audios, IEnumerable<Track> subtitles) GetStreams(long episodeID, string episodeSlug)
|
||||
{
|
||||
IEnumerable<Track> tracks = _database.Tracks.Where(track => track.EpisodeID == episodeID);
|
||||
return ((from track in tracks where track.Type == StreamType.Video select track.SetLink(episodeSlug)).FirstOrDefault(),
|
||||
from track in tracks where track.Type == StreamType.Audio select track.SetLink(episodeSlug),
|
||||
from track in tracks where track.Type == StreamType.Subtitle select track.SetLink(episodeSlug));
|
||||
return (tracks.FirstOrDefault(x => x.Type == StreamType.Video),
|
||||
tracks.Where(x => x.Type == StreamType.Audio),
|
||||
tracks.Where(track => track.Type == StreamType.Subtitle));
|
||||
}
|
||||
|
||||
public Track GetSubtitle(string showSlug, long seasonNumber, long episodeNumber, string languageTag, bool forced)
|
||||
@ -478,6 +477,48 @@ namespace Kyoo.Controllers
|
||||
_database.SaveChanges();
|
||||
return season.ID;
|
||||
}
|
||||
|
||||
public long EditSeason(Season edited)
|
||||
{
|
||||
if (edited == null)
|
||||
throw new ArgumentNullException(nameof(edited));
|
||||
|
||||
_database.ChangeTracker.LazyLoadingEnabled = false;
|
||||
_database.ChangeTracker.AutoDetectChangesEnabled = false;
|
||||
|
||||
try
|
||||
{
|
||||
var query = _database.Seasons
|
||||
.Include(x => x.ExternalIDs)
|
||||
.Include(x => x.Episodes);
|
||||
Season season = _database.Entry(edited).IsKeySet
|
||||
? query.FirstOrDefault(x => x.ID == edited.ID)
|
||||
: query.FirstOrDefault(x => x.Slug == edited.Slug);
|
||||
|
||||
if (season == null)
|
||||
throw new ItemNotFound($"No season could be found with the id {edited.ID} or the slug {edited.Slug}");
|
||||
|
||||
Utility.Complete(season, edited);
|
||||
|
||||
season.Episodes = edited.Episodes?.Select(x =>
|
||||
{
|
||||
return _database.Episodes.FirstOrDefault(y => y.ShowID == x.ShowID
|
||||
&& y.SeasonNumber == x.SeasonNumber
|
||||
&& y.EpisodeNumber == x.EpisodeNumber) ?? x;
|
||||
}).ToList();
|
||||
season.ExternalIDs = ValidateExternalIDs(season.ExternalIDs);
|
||||
|
||||
_database.ChangeTracker.DetectChanges();
|
||||
_database.SaveChanges();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_database.ChangeTracker.LazyLoadingEnabled = true;
|
||||
_database.ChangeTracker.AutoDetectChangesEnabled = true;
|
||||
}
|
||||
|
||||
return edited.ID;
|
||||
}
|
||||
|
||||
public long RegisterEpisode(Episode episode)
|
||||
{
|
||||
@ -488,6 +529,45 @@ namespace Kyoo.Controllers
|
||||
_database.SaveChanges();
|
||||
return episode.ID;
|
||||
}
|
||||
|
||||
public long EditEpisode(Episode edited)
|
||||
{
|
||||
if (edited == null)
|
||||
throw new ArgumentNullException(nameof(edited));
|
||||
|
||||
_database.ChangeTracker.LazyLoadingEnabled = false;
|
||||
_database.ChangeTracker.AutoDetectChangesEnabled = false;
|
||||
|
||||
try
|
||||
{
|
||||
var query = _database.Episodes
|
||||
.Include(x => x.Tracks)
|
||||
.Include(x => x.Season)
|
||||
.Include(x => x.ExternalIDs);
|
||||
Episode episode = query.FirstOrDefault(x => x.ID == edited.ID);
|
||||
|
||||
if (episode == null)
|
||||
throw new ItemNotFound($"No episode could be found with the id {edited.ID}");
|
||||
|
||||
Utility.Complete(episode, edited);
|
||||
|
||||
episode.Season = _database.Seasons
|
||||
.FirstOrDefault(x => x.ShowID == episode.ShowID
|
||||
&& x.SeasonNumber == edited.SeasonNumber) ?? episode.Season;
|
||||
episode.Season.ExternalIDs = ValidateExternalIDs(episode.Season.ExternalIDs);
|
||||
episode.ExternalIDs = ValidateExternalIDs(episode.ExternalIDs);
|
||||
|
||||
_database.ChangeTracker.DetectChanges();
|
||||
_database.SaveChanges();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_database.ChangeTracker.LazyLoadingEnabled = true;
|
||||
_database.ChangeTracker.AutoDetectChangesEnabled = true;
|
||||
}
|
||||
|
||||
return edited.ID;
|
||||
}
|
||||
|
||||
public long RegisterTrack(Track track)
|
||||
{
|
||||
|
@ -77,6 +77,20 @@ namespace Kyoo.Controllers
|
||||
return people;
|
||||
}
|
||||
|
||||
public async Task<Season> Validate(Season season, bool alwaysDownload)
|
||||
{
|
||||
if (season?.Show?.Path == null)
|
||||
return default;
|
||||
|
||||
if (season.ImgPrimary == null)
|
||||
{
|
||||
string localPath = Path.Combine(season.Show.Path, $"season-{season.SeasonNumber}.jpg");
|
||||
if (alwaysDownload || !File.Exists(localPath))
|
||||
await DownloadImage(season.ImgPrimary, localPath, $"The poster of {season.Show.Title}'s season {season.SeasonNumber}");
|
||||
}
|
||||
return season;
|
||||
}
|
||||
|
||||
public async Task<Episode> Validate(Episode episode, bool alwaysDownload)
|
||||
{
|
||||
if (episode?.Path == null)
|
||||
|
@ -162,6 +162,7 @@ namespace Kyoo.Controllers
|
||||
{
|
||||
season = await _metadataProvider.GetSeason(show, seasonNumber, library);
|
||||
season.ExternalIDs = _libraryManager.ValidateExternalIDs(season.ExternalIDs);
|
||||
await _thumbnailsManager.Validate(season);
|
||||
}
|
||||
season.Show = show;
|
||||
return season;
|
||||
|
98
Kyoo/Tasks/ReScan.cs
Normal file
98
Kyoo/Tasks/ReScan.cs
Normal file
@ -0,0 +1,98 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Kyoo.Controllers;
|
||||
using Kyoo.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Kyoo.Tasks
|
||||
{
|
||||
public class ReScan: ITask
|
||||
{
|
||||
public string Slug => "re-scan";
|
||||
public string Name => "ReScan";
|
||||
public string Description => "Re download metadata of an item using it's external ids.";
|
||||
public string HelpMessage => null;
|
||||
public bool RunOnStartup => false;
|
||||
public int Priority => 0;
|
||||
|
||||
|
||||
private ILibraryManager _libraryManager;
|
||||
private IThumbnailsManager _thumbnailsManager;
|
||||
private IProviderManager _providerManager;
|
||||
private DatabaseContext _database;
|
||||
|
||||
public Task Run(IServiceProvider serviceProvider, CancellationToken cancellationToken, string arguments = null)
|
||||
{
|
||||
using IServiceScope serviceScope = serviceProvider.CreateScope();
|
||||
_libraryManager = serviceScope.ServiceProvider.GetService<ILibraryManager>();
|
||||
_thumbnailsManager = serviceScope.ServiceProvider.GetService<IThumbnailsManager>();
|
||||
_providerManager = serviceScope.ServiceProvider.GetService<IProviderManager>();
|
||||
_database = serviceScope.ServiceProvider.GetService<DatabaseContext>();
|
||||
|
||||
if (arguments == null || !arguments.Contains('/'))
|
||||
return Task.CompletedTask;
|
||||
|
||||
string slug = arguments.Substring(arguments.IndexOf('/') + 1);
|
||||
return arguments.Substring(0, arguments.IndexOf('/')) switch
|
||||
{
|
||||
"show" => ReScanShow(slug),
|
||||
"season" => ReScanSeason(slug),
|
||||
_ => Task.CompletedTask
|
||||
};
|
||||
}
|
||||
|
||||
private async Task ReScanShow(string slug)
|
||||
{
|
||||
Show old = _database.Shows.AsNoTracking().FirstOrDefault(x => x.Slug == slug);
|
||||
if (old == null)
|
||||
return;
|
||||
Library library = _libraryManager.GetLibraryForShow(slug);
|
||||
Show edited = await _providerManager.CompleteShow(old, library);
|
||||
edited.ID = old.ID;
|
||||
edited.Slug = old.Slug;
|
||||
edited.Path = old.Path;
|
||||
_libraryManager.EditShow(edited);
|
||||
await _thumbnailsManager.Validate(edited, true);
|
||||
await Task.WhenAll(edited.Seasons.Select(x => ReScanSeason(edited, x)));
|
||||
}
|
||||
|
||||
private async Task ReScanSeason(Show show, Season old)
|
||||
{
|
||||
Library library = _libraryManager.GetLibraryForShow(show.Slug);
|
||||
Season edited = await _providerManager.GetSeason(show, old.SeasonNumber, library);
|
||||
edited.ID = old.ID;
|
||||
_libraryManager.EditSeason(edited);
|
||||
await _thumbnailsManager.Validate(edited, true);
|
||||
await Task.WhenAll(edited.Episodes.Select(x => ReScanEpisode(show, x)));
|
||||
}
|
||||
|
||||
private async Task ReScanSeason(string slug)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private async Task ReScanEpisode(Show show, Episode old)
|
||||
{
|
||||
Library library = _libraryManager.GetLibraryForShow(show.Slug);
|
||||
Episode edited = await _providerManager.GetEpisode(show, old.Path, old.SeasonNumber, old.EpisodeNumber, old.AbsoluteNumber, library);
|
||||
edited.ID = old.ID;
|
||||
_libraryManager.EditEpisode(edited);
|
||||
await _thumbnailsManager.Validate(edited, true);
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetPossibleParameters()
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
public int? Progress()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -4,7 +4,6 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Kyoo.Controllers;
|
||||
using Kyoo.Models.Exceptions;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
@ -19,13 +18,19 @@ namespace Kyoo.Api
|
||||
private readonly IProviderManager _providerManager;
|
||||
private readonly DatabaseContext _database;
|
||||
private readonly IThumbnailsManager _thumbnailsManager;
|
||||
private readonly ITaskManager _taskManager;
|
||||
|
||||
public ShowsAPI(ILibraryManager libraryManager, IProviderManager providerManager, DatabaseContext database, IThumbnailsManager thumbnailsManager)
|
||||
public ShowsAPI(ILibraryManager libraryManager,
|
||||
IProviderManager providerManager,
|
||||
DatabaseContext database,
|
||||
IThumbnailsManager thumbnailsManager,
|
||||
ITaskManager taskManager)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
_providerManager = providerManager;
|
||||
_database = database;
|
||||
_thumbnailsManager = thumbnailsManager;
|
||||
_taskManager = taskManager;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
@ -67,19 +72,16 @@ namespace Kyoo.Api
|
||||
|
||||
[HttpPost("re-identify/{slug}")]
|
||||
[Authorize(Policy = "Write")]
|
||||
public async Task<IActionResult> ReIdentityShow(string slug, [FromBody] Show show)
|
||||
public IActionResult ReIdentityShow(string slug, [FromBody] IEnumerable<MetadataID> externalIDs)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(show);
|
||||
Show old = _database.Shows.FirstOrDefault(x => x.Slug == slug);
|
||||
if (old == null)
|
||||
return BadRequest(externalIDs);
|
||||
Show show = _database.Shows.FirstOrDefault(x => x.Slug == slug);
|
||||
if (show == null)
|
||||
return NotFound();
|
||||
Show edited = await _providerManager.CompleteShow(show, _libraryManager.GetLibraryForShow(slug));
|
||||
edited.ID = old.ID;
|
||||
edited.Slug = old.Slug;
|
||||
edited.Path = old.Path;
|
||||
_libraryManager.EditShow(edited);
|
||||
await _thumbnailsManager.Validate(edited, true);
|
||||
show.ExternalIDs = externalIDs;
|
||||
_libraryManager.EditShow(show);
|
||||
_taskManager.StartTask("re-scan-show", $"show/{slug}");
|
||||
return Ok();
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,7 @@ namespace Kyoo.Api
|
||||
}
|
||||
|
||||
|
||||
[HttpGet("{taskSlug}/{args?}")]
|
||||
[HttpGet("{taskSlug}/{*args}")]
|
||||
[Authorize(Policy="Admin")]
|
||||
public IActionResult RunTask(string taskSlug, string args = null)
|
||||
{
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit e4cd29a489d5f20de42bb89f7db2c8299b7c5171
|
||||
Subproject commit 4135c957573c13b4655fa858746ac0e7de97b3ea
|
Loading…
x
Reference in New Issue
Block a user