diff --git a/Kyoo.Transcoder/Kyoo.Transcoder.vcxproj b/Kyoo.Transcoder/Kyoo.Transcoder.vcxproj index 164a00b5..65a992ca 100644 --- a/Kyoo.Transcoder/Kyoo.Transcoder.vcxproj +++ b/Kyoo.Transcoder/Kyoo.Transcoder.vcxproj @@ -159,6 +159,7 @@ + diff --git a/Kyoo.Transcoder/Kyoo.Transcoder.vcxproj.filters b/Kyoo.Transcoder/Kyoo.Transcoder.vcxproj.filters index f4008013..3be349ea 100644 --- a/Kyoo.Transcoder/Kyoo.Transcoder.vcxproj.filters +++ b/Kyoo.Transcoder/Kyoo.Transcoder.vcxproj.filters @@ -13,6 +13,9 @@ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + {a553acdb-cb65-47bc-8809-c5374fe91cae} + @@ -24,6 +27,9 @@ Header Files + + Models + diff --git a/Kyoo.Transcoder/Stream.h b/Kyoo.Transcoder/Stream.h new file mode 100644 index 00000000..0a69763f --- /dev/null +++ b/Kyoo.Transcoder/Stream.h @@ -0,0 +1,28 @@ +#pragma once +#ifdef TRANSCODER_EXPORTS +#define API __declspec(dllexport) +#else +#define API __declspec(dllimport) +#endif + +#include + +extern "C" API struct Video +{ + std::string title; + Audio* audios; + Subtitle* subtitles; + long duration; +}; + +extern "C" API struct Audio +{ + std::string title; + std::string languageCode; +}; + +extern "C" API struct Subtitle +{ + std::string title; + std::string languageCode; +}; \ No newline at end of file diff --git a/Kyoo.Transcoder/Transcoder.cpp b/Kyoo.Transcoder/Transcoder.cpp index 97fcfa91..3affad80 100644 --- a/Kyoo.Transcoder/Transcoder.cpp +++ b/Kyoo.Transcoder/Transcoder.cpp @@ -4,4 +4,9 @@ int Init() { return 42; +} + +Video ScanVideo(std::string path) +{ + } \ No newline at end of file diff --git a/Kyoo.Transcoder/Transcoder.h b/Kyoo.Transcoder/Transcoder.h index 7b3ca8b3..8b142199 100644 --- a/Kyoo.Transcoder/Transcoder.h +++ b/Kyoo.Transcoder/Transcoder.h @@ -1,9 +1,14 @@ #pragma once - #ifdef TRANSCODER_EXPORTS #define API __declspec(dllexport) #else #define API __declspec(dllimport) #endif +#include +#include "Stream.h" + + extern "C" API int Init(); + +extern "C" API Video ScanVideo(std::string path); diff --git a/Kyoo.Transcoder/pch.h b/Kyoo.Transcoder/pch.h index 885d5d62..cb101bfa 100644 --- a/Kyoo.Transcoder/pch.h +++ b/Kyoo.Transcoder/pch.h @@ -9,5 +9,6 @@ // add headers that you want to pre-compile here #include "framework.h" +#include #endif //PCH_H diff --git a/Kyoo/Controllers/VideoController.cs b/Kyoo/Controllers/VideoController.cs new file mode 100644 index 00000000..69dac0fd --- /dev/null +++ b/Kyoo/Controllers/VideoController.cs @@ -0,0 +1,35 @@ +using Kyoo.InternalAPI; +using Kyoo.Models; +using Microsoft.AspNetCore.Mvc; + +namespace Kyoo.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class VideoController : ControllerBase + { + private readonly ILibraryManager libraryManager; + private readonly ITranscoder transcoder; + + public VideoController(ILibraryManager libraryManager, ITranscoder transcoder) + { + this.libraryManager = libraryManager; + this.transcoder = transcoder; + } + + [HttpGet("{showSlug}-s{seasonNumber}e{episodeNumber}")] + public IActionResult Index(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 new PhysicalFileResult(episode.Path, "video/mp4"); + } + else + return NotFound(); + } + } +} \ No newline at end of file diff --git a/Kyoo/Controllers/WatchController.cs b/Kyoo/Controllers/WatchController.cs index ff68c592..0de4d726 100644 --- a/Kyoo/Controllers/WatchController.cs +++ b/Kyoo/Controllers/WatchController.cs @@ -8,29 +8,25 @@ using System.Diagnostics; namespace Kyoo.Controllers { [Route("api/[controller]")] - public class WatchController : Controller + [ApiController] + public class WatchController : ControllerBase { private readonly ILibraryManager libraryManager; - private readonly ITranscoder transcoder; - public WatchController(ILibraryManager libraryManager, ITranscoder transcoder) + public WatchController(ILibraryManager libraryManager) { this.libraryManager = libraryManager; - this.transcoder = transcoder; } - [HttpGet("{showSlug}-s{seasonNumber}e{episodeNumber}")] - public IActionResult Index(string showSlug, long seasonNumber, long episodeNumber) + public ActionResult Index(string showSlug, long seasonNumber, long episodeNumber) { - Debug.WriteLine("&Trying to watch " + showSlug + " season " + seasonNumber + " episode " + episodeNumber); + WatchItem item = libraryManager.GetWatchItem(showSlug, seasonNumber, episodeNumber); - Episode episode = libraryManager.GetEpisode(showSlug, seasonNumber, episodeNumber); + if(item == null) + return NotFound(); - Debug.WriteLine("&Transcoding at: " + episode.Path); - transcoder.GetVideo(episode.Path); - - return NotFound(); + return item; } } } diff --git a/Kyoo/InternalAPI/LibraryManager/ILibraryManager.cs b/Kyoo/InternalAPI/LibraryManager/ILibraryManager.cs index 30b7e929..a1cefe6b 100644 --- a/Kyoo/InternalAPI/LibraryManager/ILibraryManager.cs +++ b/Kyoo/InternalAPI/LibraryManager/ILibraryManager.cs @@ -1,4 +1,5 @@ using Kyoo.Models; +using Kyoo.Models.Watch; using System.Collections.Generic; namespace Kyoo.InternalAPI @@ -13,6 +14,7 @@ namespace Kyoo.InternalAPI List GetPeople(long showID); List GetGenreForShow(long showID); List GetSeasons(long showID); + (VideoStream video, List audios, List subtitles) GetStreams(long episodeID); //Public read IEnumerable GetLibraries(); @@ -20,6 +22,7 @@ namespace Kyoo.InternalAPI Season GetSeason(string showSlug, long seasonNumber); List GetEpisodes(string showSlug, long seasonNumber); Episode GetEpisode(string showSlug, long seasonNumber, long episodeNumber); + WatchItem GetWatchItem(string showSlug, long seasonNumber, long episodeNumber); People GetPeopleBySlug(string slug); Genre GetGenreBySlug(string slug); Studio GetStudioBySlug(string slug); diff --git a/Kyoo/InternalAPI/LibraryManager/LibraryManager.cs b/Kyoo/InternalAPI/LibraryManager/LibraryManager.cs index 3c6b6b9a..ea7fdd92 100644 --- a/Kyoo/InternalAPI/LibraryManager/LibraryManager.cs +++ b/Kyoo/InternalAPI/LibraryManager/LibraryManager.cs @@ -1,9 +1,9 @@ using Kyoo.Models; +using Kyoo.Models.Watch; using Microsoft.Extensions.Configuration; using System.Collections.Generic; using System.Data.SQLite; using System.Diagnostics; -using System.IO; namespace Kyoo.InternalAPI { @@ -17,7 +17,7 @@ namespace Kyoo.InternalAPI string databasePath = configuration.GetValue("databasePath"); Debug.WriteLine("&Library Manager init, databasePath: " + databasePath); - if (!File.Exists(databasePath)) + if (!System.IO.File.Exists(databasePath)) { Debug.WriteLine("&Database doesn't exist, creating one."); @@ -303,6 +303,43 @@ namespace Kyoo.InternalAPI } } + public WatchItem GetWatchItem(string showSlug, long seasonNumber, long episodeNumber) + { + string query = "SELECT episodes.id, shows.title as showTitle, seasonNumber, episodeNumber, episodes.title, releaseDate, episodes.path FROM episodes JOIN shows ON shows.id = episodes.showID WHERE shows.slug = $showSlug AND episodes.seasonNumber = $seasonNumber AND episodes.episodeNumber = $episodeNumber;"; + + using (SQLiteCommand cmd = new SQLiteCommand(query, sqlConnection)) + { + cmd.Parameters.AddWithValue("$showSlug", showSlug); + cmd.Parameters.AddWithValue("$seasonNumber", seasonNumber); + cmd.Parameters.AddWithValue("$episodeNumber", episodeNumber); + SQLiteDataReader reader = cmd.ExecuteReader(); + + if (reader.Read()) + return WatchItem.FromReader(reader).SetStreams(this); + else + return null; + } + } + + 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 aec6c39e..37f6728b 100644 --- a/Kyoo/InternalAPI/Transcoder/ITranscoder.cs +++ b/Kyoo/InternalAPI/Transcoder/ITranscoder.cs @@ -3,5 +3,7 @@ namespace Kyoo.InternalAPI public interface ITranscoder { void GetVideo(string Path); + + dynamic ScanVideo(string Path); } } diff --git a/Kyoo/InternalAPI/Transcoder/Transcoder.cs b/Kyoo/InternalAPI/Transcoder/Transcoder.cs index 7373371b..79d73909 100644 --- a/Kyoo/InternalAPI/Transcoder/Transcoder.cs +++ b/Kyoo/InternalAPI/Transcoder/Transcoder.cs @@ -17,5 +17,10 @@ namespace Kyoo.InternalAPI { Debug.WriteLine("&Getting video..."); } + + public dynamic ScanVideo(string path) + { + return TranscoderAPI.ScanVideo(path); + } } } diff --git a/Kyoo/InternalAPI/Transcoder/TranscoderAPI.cs b/Kyoo/InternalAPI/Transcoder/TranscoderAPI.cs index 29d3b07e..7bd99d6b 100644 --- a/Kyoo/InternalAPI/Transcoder/TranscoderAPI.cs +++ b/Kyoo/InternalAPI/Transcoder/TranscoderAPI.cs @@ -4,7 +4,12 @@ namespace Kyoo.InternalAPI.TranscoderLink { public class TranscoderAPI { - [DllImport(@"C:\Projects\Kyoo\Debug\Kyoo.Transcoder.dll", CallingConvention = CallingConvention.Cdecl)] + private const string TranscoderPath = @"C:\Projects\Kyoo\Debug\Kyoo.Transcoder.dll"; + + [DllImport(TranscoderPath, CallingConvention = CallingConvention.Cdecl)] public extern static int Init(); + + [DllImport(TranscoderPath, CallingConvention = CallingConvention.Cdecl)] + public extern static dynamic ScanVideo(string path); } } diff --git a/Kyoo/Models/Episode.cs b/Kyoo/Models/Episode.cs index 06a8db2d..4b9dd3ad 100644 --- a/Kyoo/Models/Episode.cs +++ b/Kyoo/Models/Episode.cs @@ -16,21 +16,13 @@ namespace Kyoo.Models public string Overview; public DateTime? ReleaseDate; - public long Runtime; //This runtime variable should be in seconds (used by the video manager so we need precisions) + public long Runtime; //This runtime variable should be in minutes [JsonIgnore] public string ImgPrimary; public string ExternalIDs; public string Thumb; //Used in the API response only - public long RuntimeInMinutes - { - get - { - return Runtime / 60; - } - } - public Episode() { } diff --git a/Kyoo/Models/Stream.cs b/Kyoo/Models/Stream.cs new file mode 100644 index 00000000..0e1c3153 --- /dev/null +++ b/Kyoo/Models/Stream.cs @@ -0,0 +1,17 @@ +namespace Kyoo.Models.Watch +{ + public struct Stream + { + public string Title; + public string Language; + public bool IsDefault; + public bool IsForced; + public string Format; + } + + public struct VideoStream + { + public string Title; + public string Language; + } +} diff --git a/Kyoo/Models/WatchItem.cs b/Kyoo/Models/WatchItem.cs new file mode 100644 index 00000000..73af1397 --- /dev/null +++ b/Kyoo/Models/WatchItem.cs @@ -0,0 +1,63 @@ +using Kyoo.InternalAPI; +using Kyoo.Models.Watch; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; + +namespace Kyoo.Models +{ + public class WatchItem + { + [JsonIgnore] public readonly long episodeID; + + public string ShowTitle; + public long seasonNumber; + public long episodeNumber; + public string Title; + public DateTime? ReleaseDate; + [JsonIgnore] public string Path; + + [JsonIgnore] public VideoStream video; + public IEnumerable audios; + public IEnumerable subtitles; + + public WatchItem() { } + + public WatchItem(long episodeID, string showTitle, long seasonNumber, long episodeNumber, string title, DateTime? releaseDate, string path) + { + this.episodeID = episodeID; + ShowTitle = showTitle; + this.seasonNumber = seasonNumber; + this.episodeNumber = episodeNumber; + Title = title; + ReleaseDate = releaseDate; + Path = path; + } + + public WatchItem(long episodeID, string showTitle, long seasonNumber, long episodeNumber, string title, DateTime? releaseDate, string path, Stream[] audios, Stream[] subtitles) : this(episodeID, showTitle, seasonNumber, episodeNumber, title, releaseDate, path) + { + this.audios = audios; + this.subtitles = subtitles; + } + + public static WatchItem FromReader(System.Data.SQLite.SQLiteDataReader reader) + { + return new WatchItem((long)reader["id"], + reader["showTitle"] as string, + (long)reader["seasonNumber"], + (long)reader["episodeNumber"], + reader["title"] as string, + reader["releaseDate"] as DateTime?, + reader["path"] as string); + } + + public WatchItem SetStreams(ILibraryManager libraryManager) + { + (VideoStream video, IEnumerable audios, IEnumerable subtitles) streams = libraryManager.GetStreams(episodeID); + video = streams.video; + audios = streams.audios; + subtitles = streams.subtitles; + return this; + } + } +} diff --git a/Kyoo/Pages/Error.cshtml b/Kyoo/Pages/Error.cshtml deleted file mode 100644 index b1f3143a..00000000 --- a/Kyoo/Pages/Error.cshtml +++ /dev/null @@ -1,23 +0,0 @@ -@page -@model ErrorModel -@{ - ViewData["Title"] = "Error"; -} - -

Error.

-

An error occurred while processing your request.

- -@if (Model.ShowRequestId) -{ -

- Request ID: @Model.RequestId -

-} - -

Development Mode

-

- Swapping to Development environment will display more detailed information about the error that occurred. -

-

- Development environment should not be enabled in deployed applications, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the ASPNETCORE_ENVIRONMENT environment variable to Development, and restarting the application. -

diff --git a/Kyoo/Pages/Error.cshtml.cs b/Kyoo/Pages/Error.cshtml.cs deleted file mode 100644 index a75a4b79..00000000 --- a/Kyoo/Pages/Error.cshtml.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; - -namespace Kyoo.Pages -{ - [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] - public class ErrorModel : PageModel - { - public string RequestId { get; set; } - - public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); - - public void OnGet() - { - RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; - } - } -} diff --git a/Kyoo/Pages/_ViewImports.cshtml b/Kyoo/Pages/_ViewImports.cshtml deleted file mode 100644 index 6207fd4a..00000000 --- a/Kyoo/Pages/_ViewImports.cshtml +++ /dev/null @@ -1,3 +0,0 @@ -@using Kyoo -@namespace Kyoo.Pages -@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers