diff --git a/Kyoo/ClientApp/src/app/player/player.component.html b/Kyoo/ClientApp/src/app/player/player.component.html index cf824fe2..90ce7779 100644 --- a/Kyoo/ClientApp/src/app/player/player.component.html +++ b/Kyoo/ClientApp/src/app/player/player.component.html @@ -1,7 +1,8 @@
diff --git a/Kyoo/Controllers/SubtitleController.cs b/Kyoo/Controllers/SubtitleController.cs index e1f71fad..15591ca1 100644 --- a/Kyoo/Controllers/SubtitleController.cs +++ b/Kyoo/Controllers/SubtitleController.cs @@ -1,4 +1,5 @@ using Kyoo.InternalAPI; +using Kyoo.Models.Watch; using Microsoft.AspNetCore.Mvc; namespace Kyoo.Controllers @@ -14,10 +15,16 @@ namespace Kyoo.Controllers this.libraryManager = libraryManager; } - [HttpGet("{showSlug}-s{seasonNumber}e{episodeNumber}-{languageTag}.ass")] - public IActionResult GetSubtitle(string showSlug, long seasonNumber, long episodeNumber, string languageTag) + [HttpGet("{showSlug}-s{seasonNumber}e{episodeNumber}-{languageTag}.{format?}")] + public IActionResult GetSubtitle(string showSlug, long seasonNumber, long episodeNumber, string languageTag, string format) { - return PhysicalFile(@"D:\Videos\Devilman\Subtitles\fre\Devilman Crybaby S01E01.fre.ass", "text/x-ssa"); + Stream subtitle = libraryManager.GetSubtitle(showSlug, seasonNumber, episodeNumber, languageTag); + + if (subtitle == null) + return NotFound(); + + //Should use appropriate mime type here + return PhysicalFile(subtitle.Path, "text/x-ssa"); } } } \ No newline at end of file diff --git a/Kyoo/Controllers/VideoController.cs b/Kyoo/Controllers/VideoController.cs index 2eeebade..0d379422 100644 --- a/Kyoo/Controllers/VideoController.cs +++ b/Kyoo/Controllers/VideoController.cs @@ -22,11 +22,35 @@ namespace Kyoo.Controllers { WatchItem episode = libraryManager.GetWatchItem(showSlug, seasonNumber, episodeNumber); + if (System.IO.File.Exists(episode.Path)) + return PhysicalFile(episode.Path, "video/x-matroska", true); + else + return NotFound(); + } + + [HttpGet("{showSlug}-s{seasonNumber}e{episodeNumber}/stream")] + public IActionResult Stream(string showSlug, long seasonNumber, long episodeNumber) + { + WatchItem episode = libraryManager.GetWatchItem(showSlug, seasonNumber, episodeNumber); + if (System.IO.File.Exists(episode.Path)) { - //Should check if video is playable on the client and transcode if needed. - //Should use the right mime type - return PhysicalFile(episode.Path, "video/mp4", true); + string path = transcoder.Stream(episode.Path); + return PhysicalFile(path, "video/mp4", true); + } + else + return NotFound(); + } + + [HttpGet("{showSlug}-s{seasonNumber}e{episodeNumber}/transcode")] + public IActionResult Transcode(string showSlug, long seasonNumber, long episodeNumber) + { + WatchItem episode = libraryManager.GetWatchItem(showSlug, seasonNumber, episodeNumber); + + if (System.IO.File.Exists(episode.Path)) + { + string path = transcoder.Transcode(episode.Path); + return PhysicalFile(path, "video/mp4", true); } else return NotFound(); diff --git a/Kyoo/InternalAPI/LibraryManager/ILibraryManager.cs b/Kyoo/InternalAPI/LibraryManager/ILibraryManager.cs index 79c9ac2e..8d5d821e 100644 --- a/Kyoo/InternalAPI/LibraryManager/ILibraryManager.cs +++ b/Kyoo/InternalAPI/LibraryManager/ILibraryManager.cs @@ -15,7 +15,10 @@ namespace Kyoo.InternalAPI List GetGenreForShow(long showID); List GetSeasons(long showID); int GetSeasonCount(string showSlug, long seasonNumber); - (VideoStream video, List audios, List subtitles) GetStreams(long episodeID); + + //Internal HTML read + (List audios, List subtitles) GetStreams(long episodeID); + Stream GetSubtitle(string showSlug, long seasonNumber, long episodeNumber, string languageTag); //Public read IEnumerable GetLibraries(); diff --git a/Kyoo/InternalAPI/LibraryManager/LibraryManager.cs b/Kyoo/InternalAPI/LibraryManager/LibraryManager.cs index 55870ab8..0efb67bf 100644 --- a/Kyoo/InternalAPI/LibraryManager/LibraryManager.cs +++ b/Kyoo/InternalAPI/LibraryManager/LibraryManager.cs @@ -73,24 +73,14 @@ namespace Kyoo.InternalAPI CREATE TABLE streams( id INTEGER PRIMARY KEY UNIQUE, episodeID INTEGER, - streamIndex INTEGER, streamType TEXT, - codec TEXT, + tile TEXT, language TEXT, - channelLayout TEXT, - profile TEXT, - aspectRatio TEXT, - bitRate INTEGER, - sampleRate INTEGER, + codec TEXT, isDefault BOOLEAN, isForced BOOLEAN, - isExternal BOOLEAN, - height INTEGER, - width INTEGER, - frameRate NUMBER, - level NUMBER, - pixelFormat TEXT, - bitDepth INTEGER, + isExternal BOOLEAN, + path TEXT, FOREIGN KEY(episodeID) REFERENCES episodes(id) ); @@ -199,6 +189,52 @@ namespace Kyoo.InternalAPI } + public (List audios, List subtitles) GetStreams(long episodeID) + { + string query = "SELECT * FROM streams WHERE episodeID = $episodeID;"; + + using (SQLiteCommand cmd = new SQLiteCommand(query, sqlConnection)) + { + cmd.Parameters.AddWithValue("$episodeID", episodeID); + SQLiteDataReader reader = cmd.ExecuteReader(); + + List audios = new List(); + List subtitles = new List(); + + while (reader.Read()) + { + Stream stream = Stream.FromReader(reader); + + if (stream.type == StreamType.Audio) + audios.Add(stream); + else if (stream.type == StreamType.Subtitle) + subtitles.Add(stream); + } + + return (audios, subtitles); + } + } + + public Stream GetSubtitle(string showSlug, long seasonNumber, long episodeNumber, string languageTag) + { + string query = "SELECT streams.* FROM streams JOIN episodes ON streams.episodeID = episodes.id JOIN shows ON episodes.showID = shows.id WHERE shows.showSlug = $showSlug, episodes.seasonNumber = $seasonNumber, episodes.episodeNumber = $episodeNumber, streams.language = $languageTag;"; + + using (SQLiteCommand cmd = new SQLiteCommand(query, sqlConnection)) + { + cmd.Parameters.AddWithValue("$showSlug", showSlug); + cmd.Parameters.AddWithValue("$seasonNumber", seasonNumber); + cmd.Parameters.AddWithValue("$episodeNumber", episodeNumber); + cmd.Parameters.AddWithValue("$languageTag", languageTag); + SQLiteDataReader reader = cmd.ExecuteReader(); + + if (reader.Read()) + return Stream.FromReader(reader); + + return null; + } + } + + public IEnumerable QueryShows(string selection) { string query = "SELECT * FROM shows ORDER BY title;"; @@ -341,25 +377,6 @@ namespace Kyoo.InternalAPI } } - public (VideoStream video, List audios, List subtitles) GetStreams(long episodeID) - { - return (new VideoStream(), null, null); - //string query = "SELECT genres.id, genres.slug, genres.name FROM genres JOIN genresLinks l ON l.genreID = genres.id WHERE l.showID = $showID;"; - - //using (SQLiteCommand cmd = new SQLiteCommand(query, sqlConnection)) - //{ - // cmd.Parameters.AddWithValue("$episodeID", episodeID); - // SQLiteDataReader reader = cmd.ExecuteReader(); - - // List genres = new List(); - - // while (reader.Read()) - // genres.Add(Stream.FromReader(reader)); - - // return genres; - //} - } - public List GetPeople(long showID) { string query = "SELECT people.id, people.slug, people.name, people.imgPrimary, people.externalIDs, l.role, l.type FROM people JOIN peopleLinks l ON l.peopleID = people.id WHERE l.showID = $showID;"; diff --git a/Kyoo/InternalAPI/Transcoder/ITranscoder.cs b/Kyoo/InternalAPI/Transcoder/ITranscoder.cs index 37f6728b..8c7277a7 100644 --- a/Kyoo/InternalAPI/Transcoder/ITranscoder.cs +++ b/Kyoo/InternalAPI/Transcoder/ITranscoder.cs @@ -2,6 +2,12 @@ namespace Kyoo.InternalAPI { public interface ITranscoder { + //Should transcode to a mp4 container (same video/audio format if possible, no subtitles). + string Stream(string path); + + //Should transcode to a mp4 container with a h264 video format and a AAC audio format, no subtitles. + string Transcode(string path); + void GetVideo(string Path); dynamic ScanVideo(string Path); diff --git a/Kyoo/InternalAPI/Transcoder/Transcoder.cs b/Kyoo/InternalAPI/Transcoder/Transcoder.cs index 79d73909..16938d7f 100644 --- a/Kyoo/InternalAPI/Transcoder/Transcoder.cs +++ b/Kyoo/InternalAPI/Transcoder/Transcoder.cs @@ -22,5 +22,15 @@ namespace Kyoo.InternalAPI { return TranscoderAPI.ScanVideo(path); } + + public string Stream(string path) + { + return @"D:\Videos\Anohana\AnoHana S01E01.mp4"; + } + + public string Transcode(string path) + { + return @"D:\Videos\Anohana\AnoHana S01E01.mp4"; + } } } diff --git a/Kyoo/Models/Stream.cs b/Kyoo/Models/Stream.cs index 0e1c3153..dd91de5e 100644 --- a/Kyoo/Models/Stream.cs +++ b/Kyoo/Models/Stream.cs @@ -1,17 +1,46 @@ -namespace Kyoo.Models.Watch +using Newtonsoft.Json; + +namespace Kyoo.Models.Watch { - public struct Stream + public enum StreamType { + Audio, Subtitle, Unknow + } + + public class Stream + { + [JsonIgnore] public StreamType type; public string Title; public string Language; public bool IsDefault; public bool IsForced; public string Format; - } - public struct VideoStream - { - public string Title; - public string Language; + [JsonIgnore] public bool IsExternal; + [JsonIgnore] public string Path; + + public Stream(StreamType type, string title, string language, bool isDefault, bool isForced, string format, bool isExternal, string path) + { + this.type = type; + Title = title; + Language = language; + IsDefault = isDefault; + IsForced = isForced; + Format = format; + IsExternal = isExternal; + Path = path; + } + + public static Stream FromReader(System.Data.SQLite.SQLiteDataReader reader) + { + return new Stream(reader["streamType"] as StreamType? ?? StreamType.Unknow, + reader["title"] as string, + reader["language"] as string, + reader["isDefault"] as bool? ?? false, + reader["isForced"] as bool? ?? false, + reader["codec"] as string, + reader["isExternal"] as bool? ?? false, + reader["path"] as string); + } } } diff --git a/Kyoo/Models/WatchItem.cs b/Kyoo/Models/WatchItem.cs index d7fcfe3b..6e6c7f88 100644 --- a/Kyoo/Models/WatchItem.cs +++ b/Kyoo/Models/WatchItem.cs @@ -21,7 +21,6 @@ namespace Kyoo.Models public string previousEpisode; public Episode nextEpisode; - [JsonIgnore] public VideoStream video; public IEnumerable audios; public IEnumerable subtitles; @@ -61,8 +60,7 @@ namespace Kyoo.Models public WatchItem SetStreams(ILibraryManager libraryManager) { - (VideoStream video, IEnumerable audios, IEnumerable subtitles) streams = libraryManager.GetStreams(episodeID); - video = streams.video; + (IEnumerable audios, IEnumerable subtitles) streams = libraryManager.GetStreams(episodeID); audios = streams.audios; subtitles = streams.subtitles; return this;