diff --git a/Kyoo.Transcoder/src/Stream.h b/Kyoo.Transcoder/src/Stream.h index 7a53aa89..a40fc1d4 100644 --- a/Kyoo.Transcoder/src/Stream.h +++ b/Kyoo.Transcoder/src/Stream.h @@ -11,11 +11,11 @@ extern "C" struct Stream { const char* title; const char* language; - const char* format; + const char* codec; bool isDefault; bool isForced; const char* path; - Stream(const char* title, const char* languageCode, const char* format, bool isDefault, bool isForced, const char* path) - : title(title), language(languageCode), format(format), isDefault(isDefault), isForced(isForced), path(path) { } + Stream(const char* title, const char* languageCode, const char* codec, bool isDefault, bool isForced, const char* path) + : title(title), language(languageCode), codec(codec), isDefault(isDefault), isForced(isForced), path(path) { } }; \ No newline at end of file diff --git a/Kyoo.Transcoder/src/Transcoder.cpp b/Kyoo.Transcoder/src/Transcoder.cpp index ff426763..d8fae579 100644 --- a/Kyoo.Transcoder/src/Transcoder.cpp +++ b/Kyoo.Transcoder/src/Transcoder.cpp @@ -56,14 +56,14 @@ Stream* ExtractSubtitles(const char* path, const char* outPath, int* streamCount //Get metadata for file name Stream stream("title", //title av_dict_get(inputStream->metadata, "language", NULL, 0)->value, //language - "format", //format - false, //isDefault - false, //isForced - "path"); //path + avcodec_get_name(inputCodecpar->codec_id), //format + inputStream->disposition & AV_DISPOSITION_DEFAULT, //isDefault + inputStream->disposition & AV_DISPOSITION_FORCED, //isForced + NULL); //The path is assigned afterward. subtitleStreams->push_back(stream); - std::cout << "Stream #" << i << "(" << stream.language << "), stream type: " << inputCodecpar->codec_type << " codec: " << inputCodecpar->codec_tag << std::endl; + std::cout << "Stream #" << i << "(" << stream.language << "), stream type: " << inputCodecpar->codec_type << " codec: " << stream.codec << std::endl; //Create output folder std::stringstream outStream; @@ -77,12 +77,23 @@ Stream* ExtractSubtitles(const char* path, const char* outPath, int* streamCount //Construct output file name outStream << fileName << "." << stream.language; - outStream << ".ass"; + + if (stream.isDefault) + outStream << ".default"; + if (stream.isForced) + outStream << ".forced"; + + if (stream.codec == "subrip") + outStream << ".srt"; + else if(stream.codec == "ass") + outStream << ".ass"; + + std::string outStr = outStream.str(); - const char* output = outStr.c_str(); + stream.path = outStr.c_str(); AVFormatContext* outputContext = NULL; - if (avformat_alloc_output_context2(&outputContext, NULL, NULL, output) < 0) + if (avformat_alloc_output_context2(&outputContext, NULL, NULL, stream.path) < 0) { std::cout << "Error: Couldn't create an output file." << std::endl; continue; @@ -129,13 +140,13 @@ Stream* ExtractSubtitles(const char* path, const char* outPath, int* streamCount // } //} - av_dump_format(outputContext, 0, output, true); + av_dump_format(outputContext, 0, stream.path, true); if (!(outputContext->flags & AVFMT_NOFILE)) { - if (avio_open(&outputContext->pb, output, AVIO_FLAG_WRITE) < 0) + if (avio_open(&outputContext->pb, stream.path, AVIO_FLAG_WRITE) < 0) { - std::cout << "Error: Couldn't open file at " << output << std::endl; + std::cout << "Error: Couldn't open file at " << stream.path << std::endl; goto end; } } @@ -144,7 +155,7 @@ Stream* ExtractSubtitles(const char* path, const char* outPath, int* streamCount if (avformat_write_header(outputContext, NULL) < 0) { - std::cout << "Error: Couldn't write headers to file at " << output << std::endl; + std::cout << "Error: Couldn't write headers to file at " << stream.path << std::endl; goto end; } diff --git a/Kyoo/Controllers/SubtitleController.cs b/Kyoo/Controllers/SubtitleController.cs index 85985d37..547aeecc 100644 --- a/Kyoo/Controllers/SubtitleController.cs +++ b/Kyoo/Controllers/SubtitleController.cs @@ -21,7 +21,7 @@ namespace Kyoo.Controllers [HttpGet("{showSlug}-s{seasonNumber}e{episodeNumber}-{languageTag}.{format?}")] public IActionResult GetSubtitle(string showSlug, long seasonNumber, long episodeNumber, string languageTag, string format) { - Stream subtitle = libraryManager.GetSubtitle(showSlug, seasonNumber, episodeNumber, languageTag); + Track subtitle = libraryManager.GetSubtitle(showSlug, seasonNumber, episodeNumber, languageTag); if (subtitle == null) return NotFound(); diff --git a/Kyoo/InternalAPI/Crawler/Crawler.cs b/Kyoo/InternalAPI/Crawler/Crawler.cs index 724e87d3..49bdd64b 100644 --- a/Kyoo/InternalAPI/Crawler/Crawler.cs +++ b/Kyoo/InternalAPI/Crawler/Crawler.cs @@ -8,7 +8,6 @@ using System.Linq; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; -using Stream = Kyoo.Models.Watch.Stream; namespace Kyoo.InternalAPI { @@ -145,8 +144,15 @@ namespace Kyoo.InternalAPI episode.SeasonID = seasonID; long episodeID = libraryManager.RegisterEpisode(episode); - Stream[] streams = transcoder.ExtractSubtitles(episode.Path); - //libraryManager.RegisterStreams(episodeID, streams); + if (episode.Path.EndsWith(".mkv")) + { + Track[] tracks = transcoder.ExtractSubtitles(episode.Path); + foreach (Track track in tracks) + { + track.episodeID = episodeID; + libraryManager.RegisterTrack(track); + } + } } } diff --git a/Kyoo/InternalAPI/LibraryManager/ILibraryManager.cs b/Kyoo/InternalAPI/LibraryManager/ILibraryManager.cs index 8d5d821e..ec748ece 100644 --- a/Kyoo/InternalAPI/LibraryManager/ILibraryManager.cs +++ b/Kyoo/InternalAPI/LibraryManager/ILibraryManager.cs @@ -17,8 +17,8 @@ namespace Kyoo.InternalAPI int GetSeasonCount(string showSlug, long seasonNumber); //Internal HTML read - (List audios, List subtitles) GetStreams(long episodeID); - Stream GetSubtitle(string showSlug, long seasonNumber, long episodeNumber, string languageTag); + (List audios, List subtitles) GetStreams(long episodeID); + Track GetSubtitle(string showSlug, long seasonNumber, long episodeNumber, string languageTag); //Public read IEnumerable GetLibraries(); @@ -42,6 +42,7 @@ namespace Kyoo.InternalAPI long RegisterShow(Show show); long RegisterSeason(Season season); long RegisterEpisode(Episode episode); + void RegisterTrack(Track track); long GetOrCreateGenre(Genre genre); long GetOrCreateStudio(Studio studio); diff --git a/Kyoo/InternalAPI/LibraryManager/LibraryManager.cs b/Kyoo/InternalAPI/LibraryManager/LibraryManager.cs index d0660715..fa5b790e 100644 --- a/Kyoo/InternalAPI/LibraryManager/LibraryManager.cs +++ b/Kyoo/InternalAPI/LibraryManager/LibraryManager.cs @@ -70,11 +70,11 @@ namespace Kyoo.InternalAPI FOREIGN KEY(showID) REFERENCES shows(id), FOREIGN KEY(seasonID) REFERENCES seasons(id) ); - CREATE TABLE streams( + CREATE TABLE tracks( id INTEGER PRIMARY KEY UNIQUE, episodeID INTEGER, streamType TEXT, - tile TEXT, + title TEXT, language TEXT, codec TEXT, isDefault BOOLEAN, @@ -189,35 +189,35 @@ namespace Kyoo.InternalAPI } - public (List audios, List subtitles) GetStreams(long episodeID) + public (List audios, List subtitles) GetStreams(long episodeID) { - string query = "SELECT * FROM streams WHERE episodeID = $episodeID;"; + string query = "SELECT * FROM tracks 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(); + List audios = new List(); + List subtitles = new List(); while (reader.Read()) { - Stream stream = Stream.FromReader(reader); + Track stream = Track.FromReader(reader); - //if (stream.type == StreamType.Audio) - // audios.Add(stream); - //else if (stream.type == StreamType.Subtitle) - // subtitles.Add(stream); + 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) + public Track 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 AND episodes.seasonNumber = $seasonNumber AND episodes.episodeNumber = $episodeNumber AND streams.language = $languageTag;"; + string query = "SELECT tracks.* FROM tracks JOIN episodes ON tracks.episodeID = episodes.id JOIN shows ON episodes.showID = shows.id WHERE shows.showSlug = $showSlug AND episodes.seasonNumber = $seasonNumber AND episodes.episodeNumber = $episodeNumber AND tracks.language = $languageTag;"; using (SQLiteCommand cmd = new SQLiteCommand(query, sqlConnection)) { @@ -228,7 +228,7 @@ namespace Kyoo.InternalAPI SQLiteDataReader reader = cmd.ExecuteReader(); if (reader.Read()) - return Stream.FromReader(reader); + return Track.FromReader(reader); return null; } @@ -685,6 +685,24 @@ namespace Kyoo.InternalAPI } } + public void RegisterTrack(Track track) + { + string query = "INSERT INTO tracks (episodeID, streamType, title, language, codec, isDefault, isForced, isExternal, path) VALUES($episodeID, $streamType, $title, $language, $codec, $isDefault, $isForced, $isExternal, $path);"; + using (SQLiteCommand cmd = new SQLiteCommand(query, sqlConnection)) + { + cmd.Parameters.AddWithValue("$episodeID", track.episodeID); + cmd.Parameters.AddWithValue("$streamType", track.type); + cmd.Parameters.AddWithValue("$title", track.Title); + cmd.Parameters.AddWithValue("$language", track.Language); + cmd.Parameters.AddWithValue("$codec", track.Codec); + cmd.Parameters.AddWithValue("$isDefault", track.IsDefault); + cmd.Parameters.AddWithValue("$isForced", track.IsForced); + cmd.Parameters.AddWithValue("$isExternal", track.IsDefault); + cmd.Parameters.AddWithValue("$path", track.Path); + cmd.ExecuteNonQuery(); + } + } + public void RegisterShowPeople(long showID, List people) { string query = "INSERT INTO people (slug, name, imgPrimary, externalIDs) VALUES($slug, $name, $imgPrimary, $externalIDs);"; diff --git a/Kyoo/InternalAPI/Transcoder/ITranscoder.cs b/Kyoo/InternalAPI/Transcoder/ITranscoder.cs index 7cabd66d..53bc83ec 100644 --- a/Kyoo/InternalAPI/Transcoder/ITranscoder.cs +++ b/Kyoo/InternalAPI/Transcoder/ITranscoder.cs @@ -1,3 +1,4 @@ +using Kyoo.Models; using Kyoo.Models.Watch; namespace Kyoo.InternalAPI @@ -11,6 +12,6 @@ namespace Kyoo.InternalAPI string Transcode(string path); //Extract all subtitles of a video and save them in the subtitles sub-folder. - Stream[] ExtractSubtitles(string path); + Track[] ExtractSubtitles(string path); } } diff --git a/Kyoo/InternalAPI/Transcoder/Transcoder.cs b/Kyoo/InternalAPI/Transcoder/Transcoder.cs index 94258ede..74e12b95 100644 --- a/Kyoo/InternalAPI/Transcoder/Transcoder.cs +++ b/Kyoo/InternalAPI/Transcoder/Transcoder.cs @@ -1,8 +1,8 @@ using Kyoo.InternalAPI.TranscoderLink; +using Kyoo.Models; using Microsoft.Extensions.Configuration; using System.Diagnostics; using System.IO; -using Stream = Kyoo.Models.Watch.Stream; namespace Kyoo.InternalAPI { @@ -13,13 +13,13 @@ namespace Kyoo.InternalAPI Debug.WriteLine("&Api INIT: " + TranscoderAPI.Init()); } - public Stream[] ExtractSubtitles(string path) + public Track[] ExtractSubtitles(string path) { string output = Path.Combine(Path.GetDirectoryName(path), "Subtitles"); Directory.CreateDirectory(output); - TranscoderAPI.ExtractSubtitles(path, output, out Stream[] streams); + TranscoderAPI.ExtractSubtitles(path, output, out Track[] tracks); - return streams; + return tracks; } public void GetVideo(string path) diff --git a/Kyoo/InternalAPI/Transcoder/TranscoderAPI.cs b/Kyoo/InternalAPI/Transcoder/TranscoderAPI.cs index ada36745..939d7576 100644 --- a/Kyoo/InternalAPI/Transcoder/TranscoderAPI.cs +++ b/Kyoo/InternalAPI/Transcoder/TranscoderAPI.cs @@ -1,5 +1,6 @@ using System; using System.Runtime.InteropServices; +using Kyoo.Models; using Kyoo.Models.Watch; namespace Kyoo.InternalAPI.TranscoderLink @@ -14,23 +15,24 @@ namespace Kyoo.InternalAPI.TranscoderLink [DllImport(TranscoderPath, CallingConvention = CallingConvention.Cdecl)] private extern static IntPtr ExtractSubtitles(string path, string outPath, out int streams); - public static void ExtractSubtitles(string path, string outPath, out Stream[] streams) + public static void ExtractSubtitles(string path, string outPath, out Track[] tracks) { int size = Marshal.SizeOf(); IntPtr streamsPtr = ExtractSubtitles(path, outPath, out int length); if (length > 0) { - streams = new Stream[length]; + tracks = new Track[length]; for (int i = 0; i < length; i++) { - streams[i] = Marshal.PtrToStructure(streamsPtr); + tracks[i] = (Track)Marshal.PtrToStructure(streamsPtr); + tracks[i].type = StreamType.Subtitle; streamsPtr += size; } } else - streams = null; + tracks = null; } } } diff --git a/Kyoo/Models/Stream.cs b/Kyoo/Models/Stream.cs deleted file mode 100644 index a1cf78e7..00000000 --- a/Kyoo/Models/Stream.cs +++ /dev/null @@ -1,49 +0,0 @@ -using Newtonsoft.Json; -using System.Runtime.InteropServices; - -namespace Kyoo.Models.Watch -{ - public enum StreamType - { - Audio, Subtitle, Unknow - } - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] - public class Stream - { - public string Title; - public string Language; - public string Format; - [MarshalAs(UnmanagedType.I1)] public bool IsDefault; - [MarshalAs(UnmanagedType.I1)] public bool IsForced; - [JsonIgnore] public string Path; - - //[JsonIgnore] public StreamType type; - //[JsonIgnore] public bool IsExternal; - - //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(); - //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/Track.cs b/Kyoo/Models/Track.cs new file mode 100644 index 00000000..ea642cc0 --- /dev/null +++ b/Kyoo/Models/Track.cs @@ -0,0 +1,57 @@ +using Kyoo.Models.Watch; +using Newtonsoft.Json; +using System.Runtime.InteropServices; + +namespace Kyoo.Models +{ + namespace Watch + { + public enum StreamType + { + Audio, Subtitle, Unknow + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] + public class Stream + { + public string Title; + public string Language; + public string Codec; + [MarshalAs(UnmanagedType.I1)] public bool IsDefault; + [MarshalAs(UnmanagedType.I1)] public bool IsForced; + [JsonIgnore] public string Path; + } + } + + public class Track : Stream + { + [JsonIgnore] public readonly long id; + [JsonIgnore] public long episodeID; + [JsonIgnore] public StreamType type; + [JsonIgnore] public bool IsExternal; + + public Track(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; + Codec = format; + IsExternal = isExternal; + Path = path; + } + + public static Track FromReader(System.Data.SQLite.SQLiteDataReader reader) + { + return new Track(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); + } + } +} \ No newline at end of file diff --git a/Kyoo/Models/WatchItem.cs b/Kyoo/Models/WatchItem.cs index 6e6c7f88..8041f288 100644 --- a/Kyoo/Models/WatchItem.cs +++ b/Kyoo/Models/WatchItem.cs @@ -21,8 +21,8 @@ namespace Kyoo.Models public string previousEpisode; public Episode nextEpisode; - public IEnumerable audios; - public IEnumerable subtitles; + public IEnumerable audios; + public IEnumerable subtitles; public WatchItem() { } @@ -40,7 +40,7 @@ namespace Kyoo.Models Link = ShowSlug + "-s" + seasonNumber + "e" + episodeNumber; } - public WatchItem(long episodeID, string showTitle, string showSlug, long seasonNumber, long episodeNumber, string title, DateTime? releaseDate, string path, Stream[] audios, Stream[] subtitles) : this(episodeID, showTitle, showSlug, seasonNumber, episodeNumber, title, releaseDate, path) + public WatchItem(long episodeID, string showTitle, string showSlug, long seasonNumber, long episodeNumber, string title, DateTime? releaseDate, string path, Track[] audios, Track[] subtitles) : this(episodeID, showTitle, showSlug, seasonNumber, episodeNumber, title, releaseDate, path) { this.audios = audios; this.subtitles = subtitles; @@ -60,7 +60,7 @@ namespace Kyoo.Models public WatchItem SetStreams(ILibraryManager libraryManager) { - (IEnumerable audios, IEnumerable subtitles) streams = libraryManager.GetStreams(episodeID); + (IEnumerable audios, IEnumerable subtitles) streams = libraryManager.GetStreams(episodeID); audios = streams.audios; subtitles = streams.subtitles; return this;