diff --git a/Kyoo.Common/Models/Exceptions/TaskFailedException.cs b/Kyoo.Common/Models/Exceptions/TaskFailedException.cs new file mode 100644 index 00000000..5fe65f7c --- /dev/null +++ b/Kyoo.Common/Models/Exceptions/TaskFailedException.cs @@ -0,0 +1,45 @@ +using System; +using System.Runtime.Serialization; +using Kyoo.Controllers; + +namespace Kyoo.Models.Exceptions +{ + /// + /// An exception raised when an failed. + /// + [Serializable] + public class TaskFailedException : AggregateException + { + /// + /// Create a new with a default message. + /// + public TaskFailedException() + : base("A task failed.") + {} + + /// + /// Create a new with a custom message. + /// + /// The message to use. + public TaskFailedException(string message) + : base(message) + {} + + /// + /// Create a new wrapping another exception. + /// + /// The exception to wrap. + public TaskFailedException(Exception exception) + : base(exception) + {} + + /// + /// The serialization constructor + /// + /// Serialization infos + /// The serialization context + protected TaskFailedException(SerializationInfo info, StreamingContext context) + : base(info, context) + { } + } +} \ No newline at end of file diff --git a/Kyoo.Common/Models/Resources/Episode.cs b/Kyoo.Common/Models/Resources/Episode.cs index cdb7fb8f..e7cf056a 100644 --- a/Kyoo.Common/Models/Resources/Episode.cs +++ b/Kyoo.Common/Models/Resources/Episode.cs @@ -20,9 +20,11 @@ namespace Kyoo.Models { get { - if (ShowSlug == null && Show == null) - return GetSlug(ShowID.ToString(), SeasonNumber, EpisodeNumber, AbsoluteNumber); - return GetSlug(ShowSlug ?? Show.Slug, SeasonNumber, EpisodeNumber, AbsoluteNumber); + if (ShowSlug != null || Show != null) + return GetSlug(ShowSlug ?? Show.Slug, SeasonNumber, EpisodeNumber, AbsoluteNumber); + return ShowID != 0 + ? GetSlug(ShowID.ToString(), SeasonNumber, EpisodeNumber, AbsoluteNumber) + : null; } [UsedImplicitly] [NotNull] private set { diff --git a/Kyoo.Common/Models/Resources/Track.cs b/Kyoo.Common/Models/Resources/Track.cs index df00e94e..d82cdbe1 100644 --- a/Kyoo.Common/Models/Resources/Track.cs +++ b/Kyoo.Common/Models/Resources/Track.cs @@ -156,30 +156,17 @@ namespace Kyoo.Models } /// - /// Utility method to edit a track slug (this only return a slug with the modification, nothing is stored) + /// Utility method to create a track slug from a incomplete slug (only add the type of the track). /// /// The slug to edit /// The new type of this - /// - /// - /// /// - public static string EditSlug(string baseSlug, - StreamType type = StreamType.Unknown, - string language = null, - int? index = null, - bool? forced = null) + public static string BuildSlug(string baseSlug, + StreamType type) { - Track track = new() {Slug = baseSlug}; - if (type != StreamType.Unknown) - track.Type = type; - if (language != null) - track.Language = language; - if (index != null) - track.TrackIndex = index.Value; - if (forced != null) - track.IsForced = forced.Value; - return track.Slug; + return baseSlug.EndsWith($".{type}", StringComparison.InvariantCultureIgnoreCase) + ? baseSlug + : $"{baseSlug}.{type.ToString().ToLowerInvariant()}"; } } } diff --git a/Kyoo.Common/Models/WatchItem.cs b/Kyoo.Common/Models/WatchItem.cs index 3b64d54e..eaf437c7 100644 --- a/Kyoo.Common/Models/WatchItem.cs +++ b/Kyoo.Common/Models/WatchItem.cs @@ -176,6 +176,7 @@ namespace Kyoo.Models return new WatchItem { EpisodeID = ep.ID, + Slug = ep.Slug, ShowSlug = ep.Show.Slug, SeasonNumber = ep.SeasonNumber, EpisodeNumber = ep.EpisodeNumber, @@ -183,6 +184,7 @@ namespace Kyoo.Models Title = ep.Title, ReleaseDate = ep.ReleaseDate, Path = ep.Path, + Container = PathIO.GetExtension(ep.Path)![1..], Video = ep.Tracks.FirstOrDefault(x => x.Type == StreamType.Video), Audios = ep.Tracks.Where(x => x.Type == StreamType.Audio).ToArray(), Subtitles = ep.Tracks.Where(x => x.Type == StreamType.Subtitle).ToArray(), diff --git a/Kyoo.Tests/Library/SpecificTests/CollectionsTests.cs b/Kyoo.Tests/Library/SpecificTests/CollectionsTests.cs index 7a5976de..73691bf7 100644 --- a/Kyoo.Tests/Library/SpecificTests/CollectionsTests.cs +++ b/Kyoo.Tests/Library/SpecificTests/CollectionsTests.cs @@ -3,7 +3,7 @@ using Kyoo.Models; using Xunit; using Xunit.Abstractions; -namespace Kyoo.Tests.Library +namespace Kyoo.Tests.Database { namespace SqLite { diff --git a/Kyoo.Tests/Library/SpecificTests/EpisodeTests.cs b/Kyoo.Tests/Library/SpecificTests/EpisodeTests.cs index 6b1adf27..d9e0e9ff 100644 --- a/Kyoo.Tests/Library/SpecificTests/EpisodeTests.cs +++ b/Kyoo.Tests/Library/SpecificTests/EpisodeTests.cs @@ -4,7 +4,7 @@ using Kyoo.Models; using Xunit; using Xunit.Abstractions; -namespace Kyoo.Tests.Library +namespace Kyoo.Tests.Database { namespace SqLite { diff --git a/Kyoo.Tests/Library/SpecificTests/GenreTests.cs b/Kyoo.Tests/Library/SpecificTests/GenreTests.cs index d79dba5e..dc820187 100644 --- a/Kyoo.Tests/Library/SpecificTests/GenreTests.cs +++ b/Kyoo.Tests/Library/SpecificTests/GenreTests.cs @@ -3,7 +3,7 @@ using Kyoo.Models; using Xunit; using Xunit.Abstractions; -namespace Kyoo.Tests.Library +namespace Kyoo.Tests.Database { namespace SqLite { diff --git a/Kyoo.Tests/Library/SpecificTests/LibraryItemTest.cs b/Kyoo.Tests/Library/SpecificTests/LibraryItemTest.cs index b2db4f66..c5639dbb 100644 --- a/Kyoo.Tests/Library/SpecificTests/LibraryItemTest.cs +++ b/Kyoo.Tests/Library/SpecificTests/LibraryItemTest.cs @@ -5,7 +5,7 @@ using Kyoo.Models; using Xunit; using Xunit.Abstractions; -namespace Kyoo.Tests.Library +namespace Kyoo.Tests.Database { namespace SqLite { diff --git a/Kyoo.Tests/Library/SpecificTests/LibraryTests.cs b/Kyoo.Tests/Library/SpecificTests/LibraryTests.cs index fbed1793..079f50cf 100644 --- a/Kyoo.Tests/Library/SpecificTests/LibraryTests.cs +++ b/Kyoo.Tests/Library/SpecificTests/LibraryTests.cs @@ -1,8 +1,11 @@ +using System.Linq; +using System.Threading.Tasks; using Kyoo.Controllers; +using Kyoo.Models; using Xunit; using Xunit.Abstractions; -namespace Kyoo.Tests.Library +namespace Kyoo.Tests.Database { namespace SqLite { @@ -23,7 +26,7 @@ namespace Kyoo.Tests.Library } } - public abstract class ALibraryTests : RepositoryTests + public abstract class ALibraryTests : RepositoryTests { private readonly ILibraryRepository _repository; @@ -32,5 +35,17 @@ namespace Kyoo.Tests.Library { _repository = Repositories.LibraryManager.LibraryRepository; } + + [Fact] + public async Task CreateWithProvider() + { + Library library = TestSample.GetNew(); + library.Providers = new[] { TestSample.Get() }; + await _repository.Create(library); + Library retrieved = await _repository.Get(2); + await Repositories.LibraryManager.Load(retrieved, x => x.Providers); + Assert.Equal(1, retrieved.Providers.Count); + Assert.Equal(TestSample.Get().Slug, retrieved.Providers.First().Slug); + } } } \ No newline at end of file diff --git a/Kyoo.Tests/Library/SpecificTests/PeopleTests.cs b/Kyoo.Tests/Library/SpecificTests/PeopleTests.cs index fc8b788d..23d40bfe 100644 --- a/Kyoo.Tests/Library/SpecificTests/PeopleTests.cs +++ b/Kyoo.Tests/Library/SpecificTests/PeopleTests.cs @@ -3,7 +3,7 @@ using Kyoo.Models; using Xunit; using Xunit.Abstractions; -namespace Kyoo.Tests.Library +namespace Kyoo.Tests.Database { namespace SqLite { diff --git a/Kyoo.Tests/Library/SpecificTests/ProviderTests.cs b/Kyoo.Tests/Library/SpecificTests/ProviderTests.cs index 853e34a1..9c022875 100644 --- a/Kyoo.Tests/Library/SpecificTests/ProviderTests.cs +++ b/Kyoo.Tests/Library/SpecificTests/ProviderTests.cs @@ -3,7 +3,7 @@ using Kyoo.Models; using Xunit; using Xunit.Abstractions; -namespace Kyoo.Tests.Library +namespace Kyoo.Tests.Database { namespace SqLite { diff --git a/Kyoo.Tests/Library/SpecificTests/SanityTests.cs b/Kyoo.Tests/Library/SpecificTests/SanityTests.cs index 78637d35..933bbf82 100644 --- a/Kyoo.Tests/Library/SpecificTests/SanityTests.cs +++ b/Kyoo.Tests/Library/SpecificTests/SanityTests.cs @@ -5,7 +5,7 @@ using Kyoo.Models; using Xunit; using Xunit.Abstractions; -namespace Kyoo.Tests.Library +namespace Kyoo.Tests.Database { public class GlobalTests : IDisposable, IAsyncDisposable { diff --git a/Kyoo.Tests/Library/SpecificTests/SeasonTests.cs b/Kyoo.Tests/Library/SpecificTests/SeasonTests.cs index 39be8b82..b1692747 100644 --- a/Kyoo.Tests/Library/SpecificTests/SeasonTests.cs +++ b/Kyoo.Tests/Library/SpecificTests/SeasonTests.cs @@ -4,7 +4,7 @@ using Kyoo.Models; using Xunit; using Xunit.Abstractions; -namespace Kyoo.Tests.Library +namespace Kyoo.Tests.Database { namespace SqLite { diff --git a/Kyoo.Tests/Library/SpecificTests/ShowTests.cs b/Kyoo.Tests/Library/SpecificTests/ShowTests.cs index 8940f0c3..63207710 100644 --- a/Kyoo.Tests/Library/SpecificTests/ShowTests.cs +++ b/Kyoo.Tests/Library/SpecificTests/ShowTests.cs @@ -8,7 +8,7 @@ using Microsoft.EntityFrameworkCore; using Xunit; using Xunit.Abstractions; -namespace Kyoo.Tests.Library +namespace Kyoo.Tests.Database { namespace SqLite { diff --git a/Kyoo.Tests/Library/SpecificTests/StudioTests.cs b/Kyoo.Tests/Library/SpecificTests/StudioTests.cs index f5093b19..c727f67a 100644 --- a/Kyoo.Tests/Library/SpecificTests/StudioTests.cs +++ b/Kyoo.Tests/Library/SpecificTests/StudioTests.cs @@ -3,7 +3,7 @@ using Kyoo.Models; using Xunit; using Xunit.Abstractions; -namespace Kyoo.Tests.Library +namespace Kyoo.Tests.Database { namespace SqLite { diff --git a/Kyoo.Tests/Library/SpecificTests/TrackTests.cs b/Kyoo.Tests/Library/SpecificTests/TrackTests.cs index 3aebaef9..0ff0c156 100644 --- a/Kyoo.Tests/Library/SpecificTests/TrackTests.cs +++ b/Kyoo.Tests/Library/SpecificTests/TrackTests.cs @@ -4,7 +4,7 @@ using Kyoo.Models; using Xunit; using Xunit.Abstractions; -namespace Kyoo.Tests.Library +namespace Kyoo.Tests.Database { namespace SqLite { diff --git a/Kyoo.Tests/Library/SpecificTests/UserTests.cs b/Kyoo.Tests/Library/SpecificTests/UserTests.cs index be67296d..24bfc789 100644 --- a/Kyoo.Tests/Library/SpecificTests/UserTests.cs +++ b/Kyoo.Tests/Library/SpecificTests/UserTests.cs @@ -3,7 +3,7 @@ using Kyoo.Models; using Xunit; using Xunit.Abstractions; -namespace Kyoo.Tests.Library +namespace Kyoo.Tests.Database { namespace SqLite { diff --git a/Kyoo.Tests/Library/TestSample.cs b/Kyoo.Tests/Library/TestSample.cs index adbe7d84..96cd63c1 100644 --- a/Kyoo.Tests/Library/TestSample.cs +++ b/Kyoo.Tests/Library/TestSample.cs @@ -9,8 +9,14 @@ namespace Kyoo.Tests private static readonly Dictionary> NewSamples = new() { { - typeof(Show), - () => new Show() + typeof(Library), + () => new Library + { + ID = 2, + Slug = "new-library", + Name = "New Library", + Paths = new [] {"/a/random/path"} + } } }; @@ -18,8 +24,8 @@ namespace Kyoo.Tests private static readonly Dictionary> Samples = new() { { - typeof(Models.Library), - () => new Models.Library + typeof(Library), + () => new Library { ID = 1, Slug = "deck", diff --git a/Kyoo.WebApp b/Kyoo.WebApp index 22a02671..dcdebad1 160000 --- a/Kyoo.WebApp +++ b/Kyoo.WebApp @@ -1 +1 @@ -Subproject commit 22a02671918201d6d9d4e80a76f01b59b216a82d +Subproject commit dcdebad14cbcdf1f9486cb9178e6518d10c0e97f diff --git a/Kyoo/Controllers/RegexIdentifier.cs b/Kyoo/Controllers/RegexIdentifier.cs index 3e5d82fe..f578aa30 100644 --- a/Kyoo/Controllers/RegexIdentifier.cs +++ b/Kyoo/Controllers/RegexIdentifier.cs @@ -87,7 +87,7 @@ namespace Kyoo.Controllers public Task IdentifyTrack(string path, string relativePath) { Regex regex = new(_configuration.Value.SubtitleRegex, RegexOptions.IgnoreCase | RegexOptions.Compiled); - Match match = regex.Match(relativePath); + Match match = regex.Match(path); if (!match.Success) throw new IdentificationFailed($"The subtitle at {path} does not match the subtitle's regex."); diff --git a/Kyoo/Controllers/Repositories/LibraryRepository.cs b/Kyoo/Controllers/Repositories/LibraryRepository.cs index 02194b77..b7b782bc 100644 --- a/Kyoo/Controllers/Repositories/LibraryRepository.cs +++ b/Kyoo/Controllers/Repositories/LibraryRepository.cs @@ -30,7 +30,7 @@ namespace Kyoo.Controllers /// Create a new instance. /// /// The database handle - /// The providere repository + /// The provider repository public LibraryRepository(DatabaseContext database, IProviderRepository providers) : base(database) { @@ -53,8 +53,8 @@ namespace Kyoo.Controllers public override async Task Create(Library obj) { await base.Create(obj); - obj.ProviderLinks = obj.Providers?.Select(x => Link.Create(obj, x)).ToList(); _database.Entry(obj).State = EntityState.Added; + obj.ProviderLinks.ForEach(x => _database.Entry(x).State = EntityState.Added); await _database.SaveChangesAsync($"Trying to insert a duplicated library (slug {obj.Slug} already exists)."); return obj; } @@ -63,6 +63,9 @@ namespace Kyoo.Controllers protected override async Task Validate(Library resource) { await base.Validate(resource); + resource.ProviderLinks = resource.Providers? + .Select(x => Link.Create(resource, x)) + .ToList(); await resource.ProviderLinks.ForEachAsync(async id => { id.Second = await _providers.CreateIfNotExists(id.Second); diff --git a/Kyoo/Controllers/TaskManager.cs b/Kyoo/Controllers/TaskManager.cs index 980bbab0..65d623dc 100644 --- a/Kyoo/Controllers/TaskManager.cs +++ b/Kyoo/Controllers/TaskManager.cs @@ -1,11 +1,9 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reflection; using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; -using Kyoo.Models.Attributes; using Kyoo.Models.Exceptions; using Kyoo.Models.Options; using Microsoft.Extensions.DependencyInjection; @@ -112,6 +110,10 @@ namespace Kyoo.Controllers { await RunTask(task, progress, args); } + catch (TaskFailedException ex) + { + _logger.LogWarning("The task \"{Task}\" failed: {Message}", task.Name, ex.Message); + } catch (Exception e) { _logger.LogError(e, "An unhandled exception occured while running the task {Task}", task.Name); diff --git a/Kyoo/Tasks/Crawler.cs b/Kyoo/Tasks/Crawler.cs index a2e28493..e65bdd87 100644 --- a/Kyoo/Tasks/Crawler.cs +++ b/Kyoo/Tasks/Crawler.cs @@ -150,6 +150,7 @@ namespace Kyoo.Tasks string[] subtitles = files .Where(FileExtensions.IsSubtitle) + .Where(x => x.Contains("/Extra/")) .Where(x => tracks.All(y => y.Path != x)) .ToArray(); percent = 0; diff --git a/Kyoo/Tasks/RegisterEpisode.cs b/Kyoo/Tasks/RegisterEpisode.cs index 3c01c03d..0498dd68 100644 --- a/Kyoo/Tasks/RegisterEpisode.cs +++ b/Kyoo/Tasks/RegisterEpisode.cs @@ -79,54 +79,66 @@ namespace Kyoo.Tasks if (library.Providers == null) await LibraryManager.Load(library, x => x.Providers); MetadataProvider.UseProviders(library.Providers); - (Collection collection, Show show, Season season, Episode episode) = await Identifier.Identify(path, - relativePath); - progress.Report(15); - - collection = await _RegisterAndFill(collection); - progress.Report(20); - - Show registeredShow = await _RegisterAndFill(show); - if (registeredShow.Path != show.Path) + try { - if (show.StartAir.HasValue) + (Collection collection, Show show, Season season, Episode episode) = await Identifier.Identify(path, + relativePath); + progress.Report(15); + + collection = await _RegisterAndFill(collection); + progress.Report(20); + + Show registeredShow = await _RegisterAndFill(show); + if (registeredShow.Path != show.Path) { - show.Slug += $"-{show.StartAir.Value.Year}"; - show = await LibraryManager.Create(show); + if (show.StartAir.HasValue) + { + show.Slug += $"-{show.StartAir.Value.Year}"; + show = await LibraryManager.Create(show); + } + else + { + throw new TaskFailedException($"Duplicated show found ({show.Slug}) " + + $"at {registeredShow.Path} and {show.Path}"); + } } else - { - throw new DuplicatedItemException($"Duplicated show found ({show.Slug}) " + - $"at {registeredShow.Path} and {show.Path}"); - } + show = registeredShow; + + // If they are not already loaded, load external ids to allow metadata providers to use them. + if (show.ExternalIDs == null) + await LibraryManager.Load(show, x => x.ExternalIDs); + progress.Report(50); + + if (season != null) + season.Show = show; + + season = await _RegisterAndFill(season); + progress.Report(60); + + episode = await MetadataProvider.Get(episode); + progress.Report(70); + episode.Show = show; + episode.Season = season; + episode.Tracks = (await Transcoder.ExtractInfos(episode, false)) + .Where(x => x.Type != StreamType.Attachment) + .ToArray(); + await ThumbnailsManager.DownloadImages(episode); + progress.Report(90); + + await LibraryManager.Create(episode); + progress.Report(95); + await LibraryManager.AddShowLink(show, library, collection); + progress.Report(100); + } + catch (IdentificationFailed ex) + { + throw new TaskFailedException(ex); + } + catch (DuplicatedItemException ex) + { + throw new TaskFailedException(ex); } - else - show = registeredShow; - // If they are not already loaded, load external ids to allow metadata providers to use them. - if (show.ExternalIDs == null) - await LibraryManager.Load(show, x => x.ExternalIDs); - progress.Report(50); - - if (season != null) - season.Show = show; - - season = await _RegisterAndFill(season); - progress.Report(60); - - episode = await MetadataProvider.Get(episode); - progress.Report(70); - episode.Show = show; - episode.Season = season; - episode.Tracks = (await Transcoder.ExtractInfos(episode, false)) - .Where(x => x.Type != StreamType.Attachment) - .ToArray(); - await ThumbnailsManager.DownloadImages(episode); - progress.Report(90); - - await LibraryManager.Create(episode); - progress.Report(95); - await LibraryManager.AddShowLink(show, library, collection); - progress.Report(100); } /// diff --git a/Kyoo/Tasks/RegisterSubtitle.cs b/Kyoo/Tasks/RegisterSubtitle.cs index 66dd5a48..07620e6c 100644 --- a/Kyoo/Tasks/RegisterSubtitle.cs +++ b/Kyoo/Tasks/RegisterSubtitle.cs @@ -59,30 +59,37 @@ namespace Kyoo.Tasks { string path = arguments["path"].As(); string relativePath = arguments["relativePath"].As(); - - progress.Report(0); - Track track = await Identifier.IdentifyTrack(path, relativePath); - progress.Report(25); - - if (track.Episode == null) - throw new IdentificationFailed($"No episode identified for the track at {path}"); - if (track.Episode.ID == 0) + + try { - if (track.Episode.Slug != null) - track.Episode = await LibraryManager.Get(track.Episode.Slug); - else if (track.Episode.Path != null) + progress.Report(0); + Track track = await Identifier.IdentifyTrack(path, relativePath); + progress.Report(25); + + if (track.Episode == null) + throw new TaskFailedException($"No episode identified for the track at {path}"); + if (track.Episode.ID == 0) { - track.Episode = await LibraryManager.GetOrDefault(x => x.Path == track.Episode.Path); - if (track.Episode == null) - throw new ItemNotFoundException($"No episode found for subtitle at: ${path}."); + if (track.Episode.Slug != null) + track.Episode = await LibraryManager.Get(track.Episode.Slug); + else if (track.Episode.Path != null) + { + track.Episode = await LibraryManager.GetOrDefault(x => x.Path.StartsWith(track.Episode.Path)); + if (track.Episode == null) + throw new TaskFailedException($"No episode found for the track at: {path}."); + } + else + throw new TaskFailedException($"No episode identified for the track at {path}"); } - else - throw new IdentificationFailed($"No episode identified for the track at {path}"); + + progress.Report(50); + await LibraryManager.Create(track); + progress.Report(100); + } + catch (IdentificationFailed ex) + { + throw new TaskFailedException(ex); } - - progress.Report(50); - await LibraryManager.Create(track); - progress.Report(100); } } } \ No newline at end of file diff --git a/Kyoo/Views/ShowApi.cs b/Kyoo/Views/ShowApi.cs index 605ac28e..73822a9c 100644 --- a/Kyoo/Views/ShowApi.cs +++ b/Kyoo/Views/ShowApi.cs @@ -386,7 +386,7 @@ namespace Kyoo.Api string path = Path.Combine(_files.GetExtraDirectory(show), "Attachments"); return (await _files.ListFiles(path)) .ToDictionary(Path.GetFileNameWithoutExtension, - x => $"{BaseURL}/api/shows/{slug}/fonts/{Path.GetFileName(x)}"); + x => $"{BaseURL}api/shows/{slug}/fonts/{Path.GetFileName(x)}"); } catch (ItemNotFoundException) { diff --git a/Kyoo/Views/SubtitleApi.cs b/Kyoo/Views/SubtitleApi.cs index 7f05eedf..5426078d 100644 --- a/Kyoo/Views/SubtitleApi.cs +++ b/Kyoo/Views/SubtitleApi.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Mvc; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Threading.Tasks; using Kyoo.Controllers; using Kyoo.Models.Permissions; @@ -21,12 +22,43 @@ namespace Kyoo.Api _files = files; } - - [HttpGet("{slug}.{extension}")] + [HttpGet("{id:int}")] [Permission(nameof(SubtitleApi), Kind.Read)] - public async Task GetSubtitle(string slug, string extension) + public async Task GetSubtitle(int id) { - Track subtitle = await _libraryManager.GetOrDefault(Track.EditSlug(slug, StreamType.Subtitle)); + Track subtitle = await _libraryManager.GetOrDefault(id); + return subtitle != null + ? _files.FileResult(subtitle.Path) + : NotFound(); + } + + [HttpGet("{id:int}.{extension}")] + [Permission(nameof(SubtitleApi), Kind.Read)] + public async Task GetSubtitle(int id, string extension) + { + Track subtitle = await _libraryManager.GetOrDefault(id); + if (subtitle == null) + return NotFound(); + if (subtitle.Codec == "subrip" && extension == "vtt") + return new ConvertSubripToVtt(subtitle.Path, _files); + return _files.FileResult(subtitle.Path); + } + + + [HttpGet("{slug}")] + [Permission(nameof(SubtitleApi), Kind.Read)] + public async Task GetSubtitle(string slug) + { + string extension = null; + + if (slug.Count(x => x == '.') == 2) + { + int idx = slug.LastIndexOf('.'); + extension = slug[(idx + 1)..]; + slug = slug[..idx]; + } + + Track subtitle = await _libraryManager.GetOrDefault(Track.BuildSlug(slug, StreamType.Subtitle)); if (subtitle == null) return NotFound(); if (subtitle.Codec == "subrip" && extension == "vtt") diff --git a/Kyoo/Views/TrackApi.cs b/Kyoo/Views/TrackApi.cs index 0eadaf3b..07df38ce 100644 --- a/Kyoo/Views/TrackApi.cs +++ b/Kyoo/Views/TrackApi.cs @@ -45,8 +45,6 @@ namespace Kyoo.Api { try { - // TODO This won't work with the local repository implementation. - // TODO Implement something like this (a dotnet-ef's QueryCompilationContext): https://stackoverflow.com/questions/62687811/how-can-i-convert-a-custom-function-to-a-sql-expression-for-entity-framework-cor return await _libraryManager.Get(x => x.Tracks.Any(y => y.Slug == slug)); } catch (ItemNotFoundException)