diff --git a/Kyoo.sln b/Kyoo.sln index 61d5219f..c240e473 100644 --- a/Kyoo.sln +++ b/Kyoo.sln @@ -25,6 +25,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.Host.Console", "src\Ky EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.Swagger", "src\Kyoo.Swagger\Kyoo.Swagger.csproj", "{7D1A7596-73F6-4D35-842E-A5AD9C620596}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{FEAE1B0E-D797-470F-9030-0EF743575ECC}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Providers", "Providers", "{8D28F5EF-0CD7-4697-A2A7-24EC31A48F21}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Databases", "Databases", "{865461CA-EC06-4B42-91CF-8723B0A9BB67}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Hosts", "Hosts", "{C569FF25-7E01-484C-9F72-5B99845AD94B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -88,4 +96,14 @@ Global {7D1A7596-73F6-4D35-842E-A5AD9C620596}.Release|Any CPU.ActiveCfg = Release|Any CPU {7D1A7596-73F6-4D35-842E-A5AD9C620596}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {0C8AA7EA-E723-4532-852F-35AA4E8AFED5} = {FEAE1B0E-D797-470F-9030-0EF743575ECC} + {BAB270D4-E0EA-4329-BA65-512FDAB01001} = {8D28F5EF-0CD7-4697-A2A7-24EC31A48F21} + {D06BF829-23F5-40F3-A62D-627D9F4B4D6C} = {8D28F5EF-0CD7-4697-A2A7-24EC31A48F21} + {6F91B645-F785-46BB-9C4F-1EFC83E489B6} = {865461CA-EC06-4B42-91CF-8723B0A9BB67} + {3213C96D-0BF3-460B-A8B5-B9977229408A} = {865461CA-EC06-4B42-91CF-8723B0A9BB67} + {6515380E-1E57-42DA-B6E3-E1C8A848818A} = {865461CA-EC06-4B42-91CF-8723B0A9BB67} + {D8658BEA-8949-45AC-BEBB-A4FFC4F800F5} = {C569FF25-7E01-484C-9F72-5B99845AD94B} + {98851001-40DD-46A6-94B3-2F8D90722076} = {C569FF25-7E01-484C-9F72-5B99845AD94B} + EndGlobalSection EndGlobal diff --git a/src/Kyoo.Abstractions/Controllers/IFileSystem.cs b/src/Kyoo.Abstractions/Controllers/IFileSystem.cs index a8cfd96c..f70a577b 100644 --- a/src/Kyoo.Abstractions/Controllers/IFileSystem.cs +++ b/src/Kyoo.Abstractions/Controllers/IFileSystem.cs @@ -31,8 +31,6 @@ namespace Kyoo.Abstractions.Controllers /// public interface IFileSystem { - // TODO find a way to handle Transmux/Transcode with this system. - /// /// Used for http queries returning a file. This should be used to return local files /// or proxy them from a distant server. @@ -51,7 +49,7 @@ namespace Kyoo.Abstractions.Controllers /// If the type is not specified, it will be deduced automatically (from the extension or by sniffing the file). /// /// An representing the file returned. - public IActionResult FileResult([CanBeNull] string path, bool rangeSupport = false, string type = null); + IActionResult FileResult([CanBeNull] string path, bool rangeSupport = false, string type = null); /// /// Read a file present at . The reader can be used in an arbitrary context. @@ -60,7 +58,7 @@ namespace Kyoo.Abstractions.Controllers /// The path of the file /// If the file could not be found. /// A reader to read the file. - public Task GetReader([NotNull] string path); + Task GetReader([NotNull] string path); /// /// Read a file present at . The reader can be used in an arbitrary context. @@ -70,28 +68,28 @@ namespace Kyoo.Abstractions.Controllers /// The mime type of the opened file. /// If the file could not be found. /// A reader to read the file. - public Task GetReader([NotNull] string path, AsyncRef mime); + Task GetReader([NotNull] string path, AsyncRef mime); /// /// Create a new file at . /// /// The path of the new file. /// A writer to write to the new file. - public Task NewFile([NotNull] string path); + Task NewFile([NotNull] string path); /// /// Create a new directory at the given path /// /// The path of the directory /// The path of the newly created directory is returned. - public Task CreateDirectory([NotNull] string path); + Task CreateDirectory([NotNull] string path); /// /// Combine multiple paths. /// /// The paths to combine /// The combined path. - public string Combine(params string[] paths); + string Combine(params string[] paths); /// /// List files in a directory. @@ -99,7 +97,7 @@ namespace Kyoo.Abstractions.Controllers /// The path of the directory /// Should the search be recursive or not. /// A list of files's path. - public Task> ListFiles([NotNull] string path, + Task> ListFiles([NotNull] string path, SearchOption options = SearchOption.TopDirectoryOnly); /// @@ -107,7 +105,7 @@ namespace Kyoo.Abstractions.Controllers /// /// The path to check /// True if the path exists, false otherwise - public Task Exists([NotNull] string path); + Task Exists([NotNull] string path); /// /// Get the extra directory of a resource . @@ -117,6 +115,25 @@ namespace Kyoo.Abstractions.Controllers /// The resource to proceed /// The type of the resource. /// The extra directory of the resource. - public Task GetExtraDirectory([NotNull] T resource); + Task GetExtraDirectory([NotNull] T resource); + + /// + /// Retrieve tracks for a specific episode. + /// Subtitles, chapters and fonts should also be extracted and cached when calling this method. + /// + /// The episode to retrieve tracks for. + /// Should the cache be invalidated and subtitles and others be re-extracted? + /// The list of tracks available for this episode. + Task> ExtractInfos([NotNull] Episode episode, bool reExtract); + + /// + /// Transmux the selected episode to hls. + /// + /// The episode to transmux. + /// The master file (m3u8) of the transmuxed hls file. + IActionResult Transmux([NotNull] Episode episode); + + // Maybe add options for to select the codec. + // IActionResult Transcode(Episode episode); } } diff --git a/src/Kyoo.Abstractions/Controllers/ITranscoder.cs b/src/Kyoo.Abstractions/Controllers/ITranscoder.cs deleted file mode 100644 index 20213526..00000000 --- a/src/Kyoo.Abstractions/Controllers/ITranscoder.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Kyoo - A portable and vast media library solution. -// Copyright (c) Kyoo. -// -// See AUTHORS.md and LICENSE file in the project root for full license information. -// -// Kyoo is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// any later version. -// -// Kyoo is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Kyoo. If not, see . - -using System.Threading.Tasks; -using Kyoo.Abstractions.Models; - -namespace Kyoo.Abstractions.Controllers -{ - public interface ITranscoder - { - Task ExtractInfos(Episode episode, bool reextract); - - Task Transmux(Episode episode); - - Task Transcode(Episode episode); - } -} diff --git a/src/Kyoo.Abstractions/Models/Utils/Identifier.cs b/src/Kyoo.Abstractions/Models/Utils/Identifier.cs index 228a6a6e..7ded5348 100644 --- a/src/Kyoo.Abstractions/Models/Utils/Identifier.cs +++ b/src/Kyoo.Abstractions/Models/Utils/Identifier.cs @@ -107,7 +107,8 @@ namespace Kyoo.Abstractions.Models.Utils { ConstantExpression self = Expression.Constant(_id.HasValue ? _id.Value : _slug); BinaryExpression equal = Expression.Equal(_id.HasValue ? idGetter.Body : slugGetter.Body, self); - return Expression.Lambda>(equal); + ICollection parameters = _id.HasValue ? idGetter.Parameters : slugGetter.Parameters; + return Expression.Lambda>(equal, parameters); } /// @@ -124,7 +125,8 @@ namespace Kyoo.Abstractions.Models.Utils { ConstantExpression self = Expression.Constant(_id.HasValue ? _id.Value : _slug); BinaryExpression equal = Expression.Equal(_id.HasValue ? idGetter.Body : slugGetter.Body, self); - return Expression.Lambda>(equal); + ICollection parameters = _id.HasValue ? idGetter.Parameters : slugGetter.Parameters; + return Expression.Lambda>(equal, parameters); } /// diff --git a/src/Kyoo.Core/Controllers/FileSystems/FileSystemComposite.cs b/src/Kyoo.Core/Controllers/FileSystems/FileSystemComposite.cs index 343b4277..8973d26b 100644 --- a/src/Kyoo.Core/Controllers/FileSystems/FileSystemComposite.cs +++ b/src/Kyoo.Core/Controllers/FileSystems/FileSystemComposite.cs @@ -214,5 +214,19 @@ namespace Kyoo.Core.Controllers }; return await CreateDirectory(path); } + + /// + public Task> ExtractInfos(Episode episode, bool reExtract) + { + IFileSystem fs = _GetFileSystemForPath(episode.Path, out string _); + return fs.ExtractInfos(episode, reExtract); + } + + /// + public IActionResult Transmux(Episode episode) + { + IFileSystem fs = _GetFileSystemForPath(episode.Path, out string _); + return fs.Transmux(episode); + } } } diff --git a/src/Kyoo.Core/Controllers/FileSystems/HttpFileSystem.cs b/src/Kyoo.Core/Controllers/FileSystems/HttpFileSystem.cs index 662d1825..e004b796 100644 --- a/src/Kyoo.Core/Controllers/FileSystems/HttpFileSystem.cs +++ b/src/Kyoo.Core/Controllers/FileSystems/HttpFileSystem.cs @@ -110,6 +110,18 @@ namespace Kyoo.Core.Controllers throw new NotSupportedException("Extras can not be stored inside an http filesystem."); } + /// + public Task> ExtractInfos(Episode episode, bool reExtract) + { + throw new NotSupportedException("Extracting infos is not supported on an http filesystem."); + } + + /// + public IActionResult Transmux(Episode episode) + { + throw new NotSupportedException("Transmuxing is not supported on an http filesystem."); + } + /// /// An to proxy an http request. /// diff --git a/src/Kyoo.Core/Controllers/FileSystems/LocalFileSystem.cs b/src/Kyoo.Core/Controllers/FileSystems/LocalFileSystem.cs index 67bcc97c..74d3a7c6 100644 --- a/src/Kyoo.Core/Controllers/FileSystems/LocalFileSystem.cs +++ b/src/Kyoo.Core/Controllers/FileSystems/LocalFileSystem.cs @@ -41,6 +41,11 @@ namespace Kyoo.Core.Controllers /// private readonly IContentTypeProvider _provider; + /// + /// The transcoder of local files. + /// + private readonly ITranscoder _transcoder; + /// /// Options to check if the metadata should be kept in the show directory or in a kyoo's directory. /// @@ -51,10 +56,14 @@ namespace Kyoo.Core.Controllers /// /// The options to use. /// An extension provider to get content types from files extensions. - public LocalFileSystem(IOptionsMonitor options, IContentTypeProvider provider) + /// The transcoder of local files. + public LocalFileSystem(IOptionsMonitor options, + IContentTypeProvider provider, + ITranscoder transcoder) { _options = options; _provider = provider; + _transcoder = transcoder; } /// @@ -155,5 +164,15 @@ namespace Kyoo.Core.Controllers _ => null }); } + + public Task> ExtractInfos(Episode episode, bool reExtract) + { + return _transcoder.ExtractInfos(episode, reExtract); + } + + public IActionResult Transmux(Episode episode) + { + return _transcoder.Transmux(episode); + } } } diff --git a/src/Kyoo.Core/Controllers/Transcoder.cs b/src/Kyoo.Core/Controllers/Transcoder.cs index 04510a3d..42135a86 100644 --- a/src/Kyoo.Core/Controllers/Transcoder.cs +++ b/src/Kyoo.Core/Controllers/Transcoder.cs @@ -17,36 +17,70 @@ // along with Kyoo. If not, see . using System; +using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; using System.Threading.Tasks; using Kyoo.Abstractions.Controllers; using Kyoo.Abstractions.Models; using Kyoo.Core.Models.Options; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -// We use threads so tasks are not always awaited. -#pragma warning disable 4014 -// Private items that are external can't start with an _ -#pragma warning disable IDE1006 - namespace Kyoo.Core.Controllers { + /// + /// The transcoder used by the . + /// public class Transcoder : ITranscoder { + /// + /// The class that interact with the transcoder written in C. + /// private static class TranscoderAPI { + /// + /// The name of the library. For windows '.dll' should be appended, on linux or macos it should be prefixed + /// by 'lib' and '.so' or '.dylib' should be appended. + /// private const string TranscoderPath = "transcoder"; + /// + /// Initialize the C library, setup the logger and return the size of a . + /// + /// The size of a [DllImport(TranscoderPath, CallingConvention = CallingConvention.Cdecl)] private static extern int init(); + /// + /// Initialize the C library, setup the logger and return the size of a . + /// + /// The size of a public static int Init() => init(); + /// + /// Transmux the file at the specified path. The path must be a local one with '/' as a separator. + /// + /// The path of a local file with '/' as a separators. + /// The path of the hls output file. + /// + /// The number of seconds currently playable. This is incremented as the file gets transmuxed. + /// + /// 0 on success, non 0 on failure. [DllImport(TranscoderPath, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true)] - private static extern int transmux(string path, string outpath, out float playableDuration); + private static extern int transmux(string path, string outPath, out float playableDuration); + /// + /// Transmux the file at the specified path. The path must be a local one. + /// + /// The path of a local file. + /// The path of the hls output file. + /// + /// The number of seconds currently playable. This is incremented as the file gets transmuxed. + /// + /// 0 on success, non 0 on failure. public static int Transmux(string path, string outPath, out float playableDuration) { path = path.Replace('\\', '/'); @@ -54,24 +88,47 @@ namespace Kyoo.Core.Controllers return transmux(path, outPath, out playableDuration); } + /// + /// Retrieve tracks from a video file and extract subtitles, fonts and chapters to an external file. + /// + /// + /// The path of the video file to analyse. This must be a local path with '/' as a separator. + /// + /// The directory that will be used to store extracted files. + /// The size of the returned array. + /// The number of tracks in the returned array. + /// Should the cache be invalidated and information re-extracted or not? + /// A pointer to an array of [DllImport(TranscoderPath, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true)] private static extern IntPtr extract_infos(string path, - string outpath, + string outPath, out uint length, out uint trackCount, - bool reextracct); + bool reExtract); + /// + /// An helper method to free an array of . + /// + /// A pointer to the first element of the array + /// The number of items in the array. [DllImport(TranscoderPath, CallingConvention = CallingConvention.Cdecl)] private static extern void free_streams(IntPtr streams, uint count); - public static Track[] ExtractInfos(string path, string outPath, bool reextract) + /// + /// Retrieve tracks from a video file and extract subtitles, fonts and chapters to an external file. + /// + /// The path of the video file to analyse. This must be a local path. + /// The directory that will be used to store extracted files. + /// Should the cache be invalidated and information re-extracted or not? + /// An array of . + public static Track[] ExtractInfos(string path, string outPath, bool reExtract) { path = path.Replace('\\', '/'); outPath = outPath.Replace('\\', '/'); int size = Marshal.SizeOf(); - IntPtr ptr = extract_infos(path, outPath, out uint arrayLength, out uint trackCount, reextract); + IntPtr ptr = extract_infos(path, outPath, out uint arrayLength, out uint trackCount, reExtract); IntPtr streamsPtr = ptr; Track[] tracks; @@ -100,67 +157,161 @@ namespace Kyoo.Core.Controllers } } - public class BadTranscoderException : Exception { } - + /// + /// The file system used to retrieve the extra directory of shows to know where to extract information. + /// private readonly IFileSystem _files; - private readonly IOptions _options; - private readonly Lazy _library; - public Transcoder(IFileSystem files, IOptions options, Lazy library) + /// + /// Options to know where to cache transmuxed/transcoded episodes. + /// + private readonly IOptions _options; + + /// + /// The logger to use. This is also used by the wrapped C library. + /// + private readonly ILogger _logger; + + /// + /// Create a new . + /// + /// + /// The file system used to retrieve the extra directory of shows to know where to extract information. + /// + /// Options to know where to cache transmuxed/transcoded episodes. + /// The logger to use. This is also used by the wrapped C library. + public Transcoder(IFileSystem files, IOptions options, ILogger logger) { _files = files; _options = options; - _library = library; + _logger = logger; if (TranscoderAPI.Init() != Marshal.SizeOf()) - throw new BadTranscoderException(); + _logger.LogCritical("The transcoder library could not be initialized correctly"); } - public async Task ExtractInfos(Episode episode, bool reextract) + /// + public async Task> ExtractInfos(Episode episode, bool reExtract) { - await _library.Value.Load(episode, x => x.Show); - string dir = await _files.GetExtraDirectory(episode.Show); + string dir = await _files.GetExtraDirectory(episode); if (dir == null) throw new ArgumentException("Invalid path."); return await Task.Factory.StartNew( - () => TranscoderAPI.ExtractInfos(episode.Path, dir, reextract), - TaskCreationOptions.LongRunning); + () => TranscoderAPI.ExtractInfos(episode.Path, dir, reExtract), + TaskCreationOptions.LongRunning + ); } - public async Task Transmux(Episode episode) + /// + public IActionResult Transmux(Episode episode) { - if (!File.Exists(episode.Path)) - throw new ArgumentException("Path does not exists. Can't transcode."); - string folder = Path.Combine(_options.Value.TransmuxPath, episode.Slug); - string manifest = Path.Combine(folder, episode.Slug + ".m3u8"); - float playableDuration = 0; - bool transmuxFailed = false; + string manifest = Path.GetFullPath(Path.Combine(folder, episode.Slug + ".m3u8")); try { Directory.CreateDirectory(folder); if (File.Exists(manifest)) - return manifest; + return new PhysicalFileResult(manifest, "application/x-mpegurl"); } catch (UnauthorizedAccessException) { - await Console.Error.WriteLineAsync($"Access to the path {manifest} is denied. Please change your transmux path in the config."); - return null; + _logger.LogCritical("Access to the path {Manifest} is denied. " + + "Please change your transmux path in the config", manifest); + return new StatusCodeResult(500); } - Task.Factory.StartNew(() => - { - transmuxFailed = TranscoderAPI.Transmux(episode.Path, manifest, out playableDuration) != 0; - }, TaskCreationOptions.LongRunning); - while (playableDuration < 10 || (!File.Exists(manifest) && !transmuxFailed)) - await Task.Delay(10); - return transmuxFailed ? null : manifest; + return new TransmuxResult(episode.Path, manifest, _logger); } - public Task Transcode(Episode episode) + /// + /// An action result that runs the transcoder and return the created manifest file after a few seconds of + /// the video has been proceeded. If the transcoder fails, it returns a 500 error code. + /// + private class TransmuxResult : IActionResult { - return Task.FromResult(null); // Not implemented yet. + /// + /// The path of the episode to transmux. It must be a local one. + /// + private readonly string _path; + + /// + /// The path of the manifest file to create. It must be a local one. + /// + private readonly string _manifest; + + /// + /// The logger to use in case of issue. + /// + private readonly ILogger _logger; + + /// + /// Create a new . + /// + /// The path of the episode to transmux. It must be a local one. + /// The path of the manifest file to create. It must be a local one. + /// The logger to use in case of issue. + public TransmuxResult(string path, string manifest, ILogger logger) + { + _path = path; + _manifest = Path.GetFullPath(manifest); + _logger = logger; + } + +// We use threads so tasks are not always awaited. +#pragma warning disable 4014 + + /// + public async Task ExecuteResultAsync(ActionContext context) + { + float playableDuration = 0; + bool transmuxFailed = false; + + Task.Factory.StartNew(() => + { + transmuxFailed = TranscoderAPI.Transmux(_path, _manifest, out playableDuration) != 0; + }, TaskCreationOptions.LongRunning); + + while (playableDuration < 10 || (!File.Exists(_manifest) && !transmuxFailed)) + await Task.Delay(10); + + if (!transmuxFailed) + { + new PhysicalFileResult(_manifest, "application/x-mpegurl") + .ExecuteResultAsync(context); + } + else + { + _logger.LogCritical("The transmuxing failed on the C library"); + new StatusCodeResult(500) + .ExecuteResultAsync(context); + } + } + +#pragma warning restore 4014 } } + + /// + /// The transcoder used by the . This is on a different interface than the file system + /// to offset the work. + /// + public interface ITranscoder + { + /// + /// Retrieve tracks for a specific episode. + /// Subtitles, chapters and fonts should also be extracted and cached when calling this method. + /// + /// The episode to retrieve tracks for. + /// Should the cache be invalidated and subtitles and others be re-extracted? + /// The list of tracks available for this episode. + Task> ExtractInfos(Episode episode, bool reExtract); + + /// + /// Transmux the selected episode to hls. + /// + /// The episode to transmux. + /// The master file (m3u8) of the transmuxed hls file. + IActionResult Transmux(Episode episode); + } } diff --git a/src/Kyoo.Core/Tasks/RegisterEpisode.cs b/src/Kyoo.Core/Tasks/RegisterEpisode.cs index 5ad39046..0a4d6085 100644 --- a/src/Kyoo.Core/Tasks/RegisterEpisode.cs +++ b/src/Kyoo.Core/Tasks/RegisterEpisode.cs @@ -56,7 +56,7 @@ namespace Kyoo.Core.Tasks /// /// The transcoder used to extract subtitles and metadata. /// - private readonly ITranscoder _transcoder; + private readonly IFileSystem _transcoder; /// /// Create a new task. @@ -74,13 +74,13 @@ namespace Kyoo.Core.Tasks /// The thumbnail manager used to download images. /// /// - /// The transcoder used to extract subtitles and metadata. + /// The file manager used to retrieve episodes metadata. /// public RegisterEpisode(IIdentifier identifier, ILibraryManager libraryManager, AProviderComposite metadataProvider, IThumbnailsManager thumbnailsManager, - ITranscoder transcoder) + IFileSystem transcoder) { _identifier = identifier; _libraryManager = libraryManager; diff --git a/src/Kyoo.Core/Views/VideoApi.cs b/src/Kyoo.Core/Views/VideoApi.cs deleted file mode 100644 index e5c205e4..00000000 --- a/src/Kyoo.Core/Views/VideoApi.cs +++ /dev/null @@ -1,133 +0,0 @@ -// Kyoo - A portable and vast media library solution. -// Copyright (c) Kyoo. -// -// See AUTHORS.md and LICENSE file in the project root for full license information. -// -// Kyoo is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// any later version. -// -// Kyoo is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Kyoo. If not, see . - -using System.IO; -using System.Threading.Tasks; -using Kyoo.Abstractions.Controllers; -using Kyoo.Abstractions.Models; -using Kyoo.Abstractions.Models.Exceptions; -using Kyoo.Abstractions.Models.Permissions; -using Kyoo.Core.Models.Options; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Filters; -using Microsoft.Extensions.Options; - -namespace Kyoo.Core.Api -{ - [Route("video")] - [ApiController] - public class VideoApi : Controller - { - private readonly ILibraryManager _libraryManager; - private readonly ITranscoder _transcoder; - private readonly IOptions _options; - private readonly IFileSystem _files; - - public VideoApi(ILibraryManager libraryManager, - ITranscoder transcoder, - IOptions options, - IFileSystem files) - { - _libraryManager = libraryManager; - _transcoder = transcoder; - _options = options; - _files = files; - } - - public override void OnActionExecuted(ActionExecutedContext ctx) - { - base.OnActionExecuted(ctx); - // Disabling the cache prevent an issue on firefox that skip the last 30 seconds of HLS files. - ctx.HttpContext.Response.Headers.Add("Cache-Control", "no-cache, no-store, must-revalidate"); - ctx.HttpContext.Response.Headers.Add("Pragma", "no-cache"); - ctx.HttpContext.Response.Headers.Add("Expires", "0"); - } - - // TODO enable the following line, this is disabled since the web app can't use bearers. [Permission("video", Kind.Read)] - [HttpGet("{slug}")] - [HttpGet("direct/{slug}")] - public async Task Direct(string slug) - { - try - { - Episode episode = await _libraryManager.Get(slug); - return _files.FileResult(episode.Path, true); - } - catch (ItemNotFoundException) - { - return NotFound(); - } - } - - [HttpGet("transmux/{slug}/master.m3u8")] - [Permission("video", Kind.Read)] - public async Task Transmux(string slug) - { - try - { - Episode episode = await _libraryManager.Get(slug); - string path = await _transcoder.Transmux(episode); - - if (path == null) - return StatusCode(500); - return _files.FileResult(path, true); - } - catch (ItemNotFoundException) - { - return NotFound(); - } - } - - [HttpGet("transcode/{slug}/master.m3u8")] - [Permission("video", Kind.Read)] - public async Task Transcode(string slug) - { - try - { - Episode episode = await _libraryManager.Get(slug); - string path = await _transcoder.Transcode(episode); - - if (path == null) - return StatusCode(500); - return _files.FileResult(path, true); - } - catch (ItemNotFoundException) - { - return NotFound(); - } - } - - [HttpGet("transmux/{episodeLink}/segments/{chunk}")] - [Permission("video", Kind.Read)] - public IActionResult GetTransmuxedChunk(string episodeLink, string chunk) - { - string path = Path.GetFullPath(Path.Combine(_options.Value.TransmuxPath, episodeLink)); - path = Path.Combine(path, "segments", chunk); - return PhysicalFile(path, "video/MP2T"); - } - - [HttpGet("transcode/{episodeLink}/segments/{chunk}")] - [Permission("video", Kind.Read)] - public IActionResult GetTranscodedChunk(string episodeLink, string chunk) - { - string path = Path.GetFullPath(Path.Combine(_options.Value.TranscodePath, episodeLink)); - path = Path.Combine(path, "segments", chunk); - return PhysicalFile(path, "video/MP2T"); - } - } -} diff --git a/src/Kyoo.Core/Views/Watch/VideoApi.cs b/src/Kyoo.Core/Views/Watch/VideoApi.cs new file mode 100644 index 00000000..03d5ed31 --- /dev/null +++ b/src/Kyoo.Core/Views/Watch/VideoApi.cs @@ -0,0 +1,134 @@ +// Kyoo - A portable and vast media library solution. +// Copyright (c) Kyoo. +// +// See AUTHORS.md and LICENSE file in the project root for full license information. +// +// Kyoo is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// Kyoo is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Kyoo. If not, see . + +using System.IO; +using System.Threading.Tasks; +using Kyoo.Abstractions.Controllers; +using Kyoo.Abstractions.Models; +using Kyoo.Abstractions.Models.Attributes; +using Kyoo.Abstractions.Models.Permissions; +using Kyoo.Abstractions.Models.Utils; +using Kyoo.Core.Models.Options; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.Options; +using static Kyoo.Abstractions.Models.Utils.Constants; + +namespace Kyoo.Core.Api +{ + /// + /// Get the video in a raw format or transcoded in the codec you want. + /// + [Route("videos")] + [Route("video", Order = AlternativeRoute)] + [ApiController] + [ApiDefinition("Videos", Group = WatchGroup)] + public class VideoApi : Controller + { + private readonly ILibraryManager _libraryManager; + private readonly IFileSystem _files; + + public VideoApi(ILibraryManager libraryManager, + IFileSystem files) + { + _libraryManager = libraryManager; + _files = files; + } + + /// + /// + /// Disabling the cache prevent an issue on firefox that skip the last 30 seconds of HLS files + /// + public override void OnActionExecuted(ActionExecutedContext ctx) + { + base.OnActionExecuted(ctx); + ctx.HttpContext.Response.Headers.Add("Cache-Control", "no-cache, no-store, must-revalidate"); + ctx.HttpContext.Response.Headers.Add("Pragma", "no-cache"); + ctx.HttpContext.Response.Headers.Add("Expires", "0"); + } + + /// + /// Direct video + /// + /// + /// Retrieve the raw video stream, in the same container as the one on the server. No transcoding or + /// transmuxing is done. + /// + /// The identifier of the episode to retrieve. + /// The raw video stream + /// No episode exists for the given identifier. + // TODO enable the following line, this is disabled since the web app can't use bearers. [Permission("video", Kind.Read)] + [HttpGet("direct/{identifier:id}")] + [HttpGet("{identifier:id}", Order = AlternativeRoute)] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task Direct(Identifier identifier) + { + Episode episode = await identifier.Match( + id => _libraryManager.GetOrDefault(id), + slug => _libraryManager.GetOrDefault(slug) + ); + return _files.FileResult(episode?.Path, true); + } + + /// + /// Transmux video + /// + /// + /// Change the container of the video to hls but don't re-encode the video or audio. This doesn't require mutch + /// resources from the server. + /// + /// The identifier of the episode to retrieve. + /// The transmuxed video stream + /// No episode exists for the given identifier. + [HttpGet("transmux/{identifier:id}/master.m3u8")] + [Permission("video", Kind.Read)] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task Transmux(Identifier identifier) + { + Episode episode = await identifier.Match( + id => _libraryManager.GetOrDefault(id), + slug => _libraryManager.GetOrDefault(slug) + ); + return _files.Transmux(episode); + } + + /// + /// Transmuxed chunk + /// + /// + /// Retrieve a chunk of a transmuxed video. + /// + /// The identifier of the episode. + /// The identifier of the chunk to retrieve. + /// The options used to retrieve the path of the segments. + /// A transmuxed video chunk. + [HttpGet("transmux/{episodeLink}/segments/{chunk}", Order = AlternativeRoute)] + [Permission("video", Kind.Read)] + [ProducesResponseType(StatusCodes.Status200OK)] + public IActionResult GetTransmuxedChunk(string episodeLink, string chunk, + [FromServices] IOptions options) + { + string path = Path.GetFullPath(Path.Combine(options.Value.TransmuxPath, episodeLink)); + path = Path.Combine(path, "segments", chunk); + return PhysicalFile(path, "video/MP2T"); + } + } +} diff --git a/src/Kyoo.Swagger/SwaggerModule.cs b/src/Kyoo.Swagger/SwaggerModule.cs index 1d8e56e1..b2da0668 100644 --- a/src/Kyoo.Swagger/SwaggerModule.cs +++ b/src/Kyoo.Swagger/SwaggerModule.cs @@ -28,7 +28,6 @@ using NJsonSchema; using NJsonSchema.Generation.TypeMappers; using NSwag; using NSwag.Generation.AspNetCore; -using NSwag.Generation.Processors.Security; using static Kyoo.Abstractions.Models.Utils.Constants; namespace Kyoo.Swagger @@ -90,40 +89,35 @@ namespace Kyoo.Swagger x.Type = JsonObjectType.String | JsonObjectType.Integer; })); - document.AddSecurity("Kyoo", new OpenApiSecurityScheme() + document.AddSecurity("Kyoo", new OpenApiSecurityScheme { Type = OpenApiSecuritySchemeType.OpenIdConnect, - Description = "Kyoo's OpenID Authentication", - Flow = OpenApiOAuth2Flow.AccessCode, - Flows = new OpenApiOAuthFlows() - { - Implicit = new OpenApiOAuthFlow() - { - Scopes = new Dictionary - { - { "read", "Read access to protected resources" }, - { "write", "Write access to protected resources" } - }, - AuthorizationUrl = "https://localhost:44333/core/connect/authorize", - TokenUrl = "https://localhost:44333/core/connect/token" - } - } + OpenIdConnectUrl = "/.well-known/openid-configuration", + Description = "You can login via an OIDC client, clients must be first registered in kyoo. " + + "Documentation coming soon." }); document.OperationProcessors.Add(new OperationPermissionProcessor()); - document.DocumentProcessors.Add(new SecurityDefinitionAppender(Group.Overall.ToString(), new OpenApiSecurityScheme + // This does not respect the swagger's specification but it works for swaggerUi and ReDoc so honestly this will do. + document.AddSecurity(Group.Overall.ToString(), new OpenApiSecurityScheme { - Type = OpenApiSecuritySchemeType.ApiKey, - Name = "Authorization", - In = OpenApiSecurityApiKeyLocation.Header, - Description = "Type into the textbox: Bearer {your JWT token}. You can get a JWT token from /Authorization/Authenticate." - })); - document.DocumentProcessors.Add(new SecurityDefinitionAppender(Group.Admin.ToString(), new OpenApiSecurityScheme + ExtensionData = new Dictionary + { + ["type"] = "OpenID Connect or Api Key" + }, + Description = "Kyoo's permissions work by groups. Permissions are attributed to " + + "a specific group and if a user has a group permission, it will be the same as having every " + + "permission in the group. For example, having overall.read gives you collections.read, " + + "shows.read and so on." + }); + document.AddSecurity(Group.Admin.ToString(), new OpenApiSecurityScheme { - Type = OpenApiSecuritySchemeType.ApiKey, - Name = "Authorization", - In = OpenApiSecurityApiKeyLocation.Header, - Description = "Type into the textbox: Bearer {your JWT token}. You can get a JWT token from /Authorization/Authenticate." - })); + ExtensionData = new Dictionary + { + ["type"] = "OpenID Connect or Api Key" + }, + Description = "The permission group used for administrative items like tasks, account management " + + "and library creation." + }); }); }