From 201f059349c07992adcf2f70ede40b2bcbad4966 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 31 Oct 2020 02:02:41 +0100 Subject: [PATCH] Adding fonts & chapters to the api --- Kyoo.Common/Controllers/ITranscoder.cs | 10 +--- Kyoo.Common/Models/Resources/Track.cs | 3 +- Kyoo.Common/Models/WatchItem.cs | 53 ++++++++++++++++++-- Kyoo.CommonAPI/CrudApi.cs | 6 +-- Kyoo/Controllers/Transcoder/Transcoder.cs | 20 ++------ Kyoo/Controllers/Transcoder/TranscoderAPI.cs | 53 ++++---------------- Kyoo/Tasks/Crawler.cs | 49 +----------------- Kyoo/Views/API/ShowApi.cs | 33 ++++++++++++ transcoder | 2 +- 9 files changed, 106 insertions(+), 123 deletions(-) diff --git a/Kyoo.Common/Controllers/ITranscoder.cs b/Kyoo.Common/Controllers/ITranscoder.cs index b2e1ef96..0eee0f59 100644 --- a/Kyoo.Common/Controllers/ITranscoder.cs +++ b/Kyoo.Common/Controllers/ITranscoder.cs @@ -6,16 +6,8 @@ namespace Kyoo.Controllers { public interface ITranscoder { - // Should transcode to a mp4 container (same video/audio format if possible, no subtitles). + Task ExtractInfos(string path); Task Transmux(Episode episode); - - // Should transcode to a mp4 container with a h264 video format and a AAC audio format, no subtitles. Task Transcode(Episode episode); - - // Get video and audio tracks infos (codec, name, language...) - Task GetTrackInfo(string path); - - // Extract all subtitles of a video and save them in the subtitles sub-folder. - Task ExtractSubtitles(string path); } } diff --git a/Kyoo.Common/Models/Resources/Track.cs b/Kyoo.Common/Models/Resources/Track.cs index 88953eb2..486c684a 100644 --- a/Kyoo.Common/Models/Resources/Track.cs +++ b/Kyoo.Common/Models/Resources/Track.cs @@ -13,7 +13,8 @@ namespace Kyoo.Models Unknow = 0, Video = 1, Audio = 2, - Subtitle = 3 + Subtitle = 3, + Font = 4 } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] diff --git a/Kyoo.Common/Models/WatchItem.cs b/Kyoo.Common/Models/WatchItem.cs index 4e1bfbcc..f6dac2d3 100644 --- a/Kyoo.Common/Models/WatchItem.cs +++ b/Kyoo.Common/Models/WatchItem.cs @@ -1,12 +1,29 @@ using Newtonsoft.Json; using System; using System.Collections.Generic; +using System.IO; +using System.Linq; using System.Threading.Tasks; using Kyoo.Controllers; using Kyoo.Models.Watch; +using PathIO = System.IO.Path; namespace Kyoo.Models { + public class Chapter + { + public long StartTime; + public long EndTime; + public string Name; + + public Chapter(long startTime, long endTime, string name) + { + StartTime = startTime; + EndTime = endTime; + Name = name; + } + } + public class WatchItem { [JsonIgnore] public readonly int EpisodeID = -1; @@ -27,10 +44,11 @@ namespace Kyoo.Models public Track Video; public IEnumerable Audios; public IEnumerable Subtitles; + public IEnumerable Chapters; public WatchItem() { } - public WatchItem(int episodeID, + private WatchItem(int episodeID, string showTitle, string showSlug, int seasonNumber, @@ -52,7 +70,7 @@ namespace Kyoo.Models Slug = Episode.GetSlug(ShowSlug, seasonNumber, episodeNumber); } - public WatchItem(int episodeID, + private WatchItem(int episodeID, string showTitle, string showSlug, int seasonNumber, @@ -92,7 +110,7 @@ namespace Kyoo.Models else next = await library.GetEpisode(ep.ShowID, ep.SeasonNumber, ep.EpisodeNumber + 1); } - + return new WatchItem(ep.ID, show.Title, show.Slug, @@ -107,8 +125,35 @@ namespace Kyoo.Models { IsMovie = show.IsMovie, PreviousEpisode = previous, - NextEpisode = next + NextEpisode = next, + Chapters = await GetChapters(ep.Path) }; } + + private static async Task> GetChapters(string episodePath) + { + string path = PathIO.Combine( + PathIO.GetDirectoryName(episodePath)!, + "Chapters", + PathIO.GetFileNameWithoutExtension(episodePath) + ".txt" + ); + if (!File.Exists(path)) + return new Chapter[0]; + try + { + return (await File.ReadAllLinesAsync(path)) + .Select(x => + { + string[] values = x.Split(' '); + return new Chapter(long.Parse(values[0]), long.Parse(values[1]), values[2]); + }) + .ToArray(); + } + catch + { + await Console.Error.WriteLineAsync($"Invalid chapter file at {path}"); + return new Chapter[0]; + } + } } } diff --git a/Kyoo.CommonAPI/CrudApi.cs b/Kyoo.CommonAPI/CrudApi.cs index dffcb7b3..23d409ec 100644 --- a/Kyoo.CommonAPI/CrudApi.cs +++ b/Kyoo.CommonAPI/CrudApi.cs @@ -15,12 +15,12 @@ namespace Kyoo.CommonApi public class CrudApi : ControllerBase where T : class, IResource { private readonly IRepository _repository; - private readonly string _baseURL; + protected readonly string BaseURL; public CrudApi(IRepository repository, IConfiguration configuration) { _repository = repository; - _baseURL = configuration.GetValue("public_url").TrimEnd('/'); + BaseURL = configuration.GetValue("public_url").TrimEnd('/'); } [HttpGet("{id:int}")] @@ -90,7 +90,7 @@ namespace Kyoo.CommonApi where TResult : IResource { return new Page(resources, - _baseURL + Request.Path, + BaseURL + Request.Path, Request.Query.ToDictionary(x => x.Key, x => x.Value.ToString(), StringComparer.InvariantCultureIgnoreCase), limit); } diff --git a/Kyoo/Controllers/Transcoder/Transcoder.cs b/Kyoo/Controllers/Transcoder/Transcoder.cs index c1335486..3fc54028 100644 --- a/Kyoo/Controllers/Transcoder/Transcoder.cs +++ b/Kyoo/Controllers/Transcoder/Transcoder.cs @@ -25,27 +25,15 @@ namespace Kyoo.Controllers throw new BadTranscoderException(); } - public Task GetTrackInfo(string path) - { - return Task.Factory.StartNew(() => - { - TranscoderAPI.GetTrackInfo(path, out Track[] tracks); - return tracks; - }, TaskCreationOptions.LongRunning); - } - - public Task ExtractSubtitles(string path) + public Task ExtractInfos(string path) { string dir = Path.GetDirectoryName(path); if (dir == null) throw new ArgumentException("Invalid path."); - string output = Path.Combine(dir, "Subtitles"); - Directory.CreateDirectory(output); - return Task.Factory.StartNew(() => - { - TranscoderAPI.ExtractSubtitles(path, output, out Track[] tracks); - return tracks; + return Task.Factory.StartNew(() => + { + return TranscoderAPI.ExtractInfos(path, dir); }, TaskCreationOptions.LongRunning); } diff --git a/Kyoo/Controllers/Transcoder/TranscoderAPI.cs b/Kyoo/Controllers/Transcoder/TranscoderAPI.cs index f55b4e6f..3f1bcd3b 100644 --- a/Kyoo/Controllers/Transcoder/TranscoderAPI.cs +++ b/Kyoo/Controllers/Transcoder/TranscoderAPI.cs @@ -20,24 +20,19 @@ namespace Kyoo.Controllers.TranscoderLink public static extern int transcode(string path, string out_path, out float playableDuration); [DllImport(TranscoderPath, CallingConvention = CallingConvention.Cdecl)] - private static extern IntPtr get_track_info(string path, out int array_length, out int track_count); + private static extern IntPtr extract_infos(string path, string outpath, out int length, out int track_count); [DllImport(TranscoderPath, CallingConvention = CallingConvention.Cdecl)] - private static extern IntPtr extract_subtitles(string path, string out_path, out int array_length, out int track_count); - - // [DllImport(TranscoderPath, CallingConvention = CallingConvention.Cdecl)] - // private static extern void free_streams(IntPtr stream_ptr); + private static extern void free(IntPtr stream_ptr); - [DllImport(TranscoderPath, CallingConvention = CallingConvention.Cdecl)] - private static extern void free(IntPtr ptr); - - - public static void GetTrackInfo(string path, out Track[] tracks) + + public static Track[] ExtractInfos(string path, string outPath) { int size = Marshal.SizeOf(); - IntPtr ptr = get_track_info(path, out int arrayLength, out int trackCount); + IntPtr ptr = extract_infos(path, outPath, out int arrayLength, out int trackCount); IntPtr streamsPtr = ptr; - + Track[] tracks; + if (trackCount > 0 && ptr != IntPtr.Zero) { tracks = new Track[trackCount]; @@ -46,7 +41,7 @@ namespace Kyoo.Controllers.TranscoderLink for (int i = 0; i < arrayLength; i++) { Stream stream = Marshal.PtrToStructure(streamsPtr); - if (stream!.Type != StreamType.Unknow) + if (stream!.Type != StreamType.Unknow && stream.Type != StreamType.Font) { tracks[j] = new Track(stream); j++; @@ -57,35 +52,9 @@ namespace Kyoo.Controllers.TranscoderLink else tracks = new Track[0]; - free(ptr); - } - - public static void ExtractSubtitles(string path, string outPath, out Track[] tracks) - { - int size = Marshal.SizeOf(); - IntPtr ptr = extract_subtitles(path, outPath, out int arrayLength, out int trackCount); - IntPtr streamsPtr = ptr; - - if (trackCount > 0 && ptr != IntPtr.Zero) - { - tracks = new Track[trackCount]; - - int j = 0; - for (int i = 0; i < arrayLength; i++) - { - Stream stream = Marshal.PtrToStructure(streamsPtr); - if (stream!.Type != StreamType.Unknow) - { - tracks[j] = new Track(stream); - j++; - } - streamsPtr += size; - } - } - else - tracks = new Track[0]; - - free(ptr); + if (ptr != IntPtr.Zero) + free(ptr); // free_streams is not necesarry since the Marshal free the unmanaged pointers. + return tracks; } } } diff --git a/Kyoo/Tasks/Crawler.cs b/Kyoo/Tasks/Crawler.cs index bdc48342..596f8f2a 100644 --- a/Kyoo/Tasks/Crawler.cs +++ b/Kyoo/Tasks/Crawler.cs @@ -8,7 +8,6 @@ using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Kyoo.Models.Exceptions; -using Kyoo.Models.Watch; using Microsoft.Extensions.DependencyInjection; namespace Kyoo.Controllers @@ -307,52 +306,8 @@ namespace Kyoo.Controllers private async Task> GetTracks(Episode episode) { - IEnumerable tracks = await _transcoder.GetTrackInfo(episode.Path); - List epTracks = tracks.Where(x => x.Type != StreamType.Subtitle) - .Concat(GetExtractedSubtitles(episode)) - .ToList(); - if (epTracks.Count(x => !x.IsExternal) < tracks.Count()) - epTracks.AddRange(await _transcoder.ExtractSubtitles(episode.Path)); - episode.Tracks = epTracks; - return epTracks; - } - - private static IEnumerable GetExtractedSubtitles(Episode episode) - { - List tracks = new List(); - - if (episode.Path == null) - return tracks; - string path = Path.Combine(Path.GetDirectoryName(episode.Path)!, "Subtitles"); - - if (!Directory.Exists(path)) - return tracks; - foreach (string sub in Directory.EnumerateFiles(path, "", SearchOption.AllDirectories)) - { - string episodeLink = Path.GetFileNameWithoutExtension(episode.Path); - string subName = Path.GetFileName(sub); - - if (episodeLink == null - || subName?.Contains(episodeLink) == false - || subName.Length < episodeLink.Length + 5) - continue; - string language = subName.Substring(episodeLink.Length + 1, 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, - Codec = Path.GetExtension(sub) switch - { - ".ass" => "ass", - ".srt" => "subrip", - _ => null - } - }; - - tracks.Add(track); - } - return tracks; + episode.Tracks = await _transcoder.ExtractInfos(episode.Path); + return episode.Tracks; } private static readonly string[] VideoExtensions = diff --git a/Kyoo/Views/API/ShowApi.cs b/Kyoo/Views/API/ShowApi.cs index 65aed963..c64accec 100644 --- a/Kyoo/Views/API/ShowApi.cs +++ b/Kyoo/Views/API/ShowApi.cs @@ -2,12 +2,14 @@ using Kyoo.Models; using Microsoft.AspNetCore.Mvc; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Threading.Tasks; using Kyoo.CommonApi; using Kyoo.Controllers; using Kyoo.Models.Exceptions; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.StaticFiles; using Microsoft.Extensions.Configuration; namespace Kyoo.Api @@ -18,6 +20,7 @@ namespace Kyoo.Api public class ShowApi : CrudApi { private readonly ILibraryManager _libraryManager; + private FileExtensionContentTypeProvider _provider; public ShowApi(ILibraryManager libraryManager, IConfiguration configuration) : base(libraryManager.ShowRepository, configuration) @@ -410,5 +413,35 @@ namespace Kyoo.Api return BadRequest(new {Error = ex.Message}); } } + + [HttpGet("{slug}/font")] + [HttpGet("{slug}/fonts")] + [Authorize(Policy = "Read")] + public async Task>> GetFonts(string slug) + { + string path = (await _libraryManager.GetShow(slug))?.Path; + if (path == null) + return NotFound(); + return Directory.GetFiles(path) + .ToDictionary(Path.GetFileNameWithoutExtension, x => $"{BaseURL}/shows/{slug}/fonts/{x}"); + } + + [HttpGet("{showSlug}/font/{slug}")] + [HttpGet("{showSlug}/fonts/{slug}")] + [Authorize(Policy = "Read")] + public async Task>> GetFont(string showSlug, string slug) + { + string path = (await _libraryManager.GetShow(showSlug))?.Path; + if (path == null) + return NotFound(); + string fontPath = Path.Combine(path, "Subtitles", "fonts", slug); + if (!System.IO.File.Exists(fontPath)) + return NotFound(); + + if (_provider == null) + _provider = new FileExtensionContentTypeProvider(); + _provider.TryGetContentType(path, out string contentType); + return PhysicalFile(fontPath, contentType ?? "application/x-font-ttf"); + } } } diff --git a/transcoder b/transcoder index d2d90e28..00b52325 160000 --- a/transcoder +++ b/transcoder @@ -1 +1 @@ -Subproject commit d2d90e28cd697a20fdd2e1c28fdfc2038c7f1d7c +Subproject commit 00b52325c5bda97f03fcb8e33caf9fef239c6c2c