From c7569691e228fe620d7e121b35060c70d4492641 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Wed, 2 Jun 2021 00:35:10 +0200 Subject: [PATCH] Adding more documentation --- .../Models/Attributes/InjectedAttribute.cs | 3 +- .../Models/Attributes/SerializeAttribute.cs | 24 +++ Kyoo.Common/Models/LibraryItem.cs | 65 +++++-- Kyoo.Common/Models/Link.cs | 6 +- Kyoo.Common/Models/MetadataID.cs | 10 +- Kyoo.Common/Models/Resources/Show.cs | 154 +++++++++++++-- Kyoo.Common/Models/Resources/Studio.cs | 33 ++-- Kyoo.Common/Models/Resources/Track.cs | 183 +++++++++--------- Kyoo.Common/Utility/EnumerableExtensions.cs | 9 +- Kyoo.Tests/KAssert.cs | 4 + Kyoo.Tests/Library/TestSample.cs | 4 +- Kyoo/Controllers/Transcoder.cs | 2 +- Kyoo/Models/Stream.cs | 67 +++++++ Kyoo/Tasks/Crawler.cs | 12 +- 14 files changed, 417 insertions(+), 159 deletions(-) create mode 100644 Kyoo/Models/Stream.cs diff --git a/Kyoo.Common/Models/Attributes/InjectedAttribute.cs b/Kyoo.Common/Models/Attributes/InjectedAttribute.cs index 1e9a8ece..b036acdf 100644 --- a/Kyoo.Common/Models/Attributes/InjectedAttribute.cs +++ b/Kyoo.Common/Models/Attributes/InjectedAttribute.cs @@ -8,7 +8,8 @@ namespace Kyoo.Models.Attributes /// An attribute to inform that the service will be injected automatically by a service provider. /// /// - /// It should only be used on and will be injected before calling + /// It should only be used on and will be injected before calling . + /// It can also be used on and it will be injected before calling . /// [AttributeUsage(AttributeTargets.Property)] [MeansImplicitUse(ImplicitUseKindFlags.Assign)] diff --git a/Kyoo.Common/Models/Attributes/SerializeAttribute.cs b/Kyoo.Common/Models/Attributes/SerializeAttribute.cs index 3eafb90c..a7958c91 100644 --- a/Kyoo.Common/Models/Attributes/SerializeAttribute.cs +++ b/Kyoo.Common/Models/Attributes/SerializeAttribute.cs @@ -2,17 +2,41 @@ using System; namespace Kyoo.Models.Attributes { + /// + /// Remove an property from the serialization pipeline. It will simply be skipped. + /// [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] public class SerializeIgnoreAttribute : Attribute {} + /// + /// Remove a property from the deserialization pipeline. The user can't input value for this property. + /// [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] public class DeserializeIgnoreAttribute : Attribute {} + /// + /// Change the way the field is serialized. It allow one to use a string format like formatting instead of the default value. + /// This can be disabled for a request by setting the "internal" query string parameter to true. + /// [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] public class SerializeAsAttribute : Attribute { + /// + /// The format string to use. + /// public string Format { get; } + /// + /// Create a new with the selected format. + /// + /// + /// The format string can contains any property within {}. It will be replaced by the actual value of the property. + /// You can also use the special value {HOST} that will put the webhost address. + /// + /// + /// The show's poster serialized uses this format string: {HOST}/api/shows/{Slug}/poster + /// + /// The format to use public SerializeAsAttribute(string format) { Format = format; diff --git a/Kyoo.Common/Models/LibraryItem.cs b/Kyoo.Common/Models/LibraryItem.cs index 78f604f2..7d48dd75 100644 --- a/Kyoo.Common/Models/LibraryItem.cs +++ b/Kyoo.Common/Models/LibraryItem.cs @@ -5,6 +5,9 @@ using Kyoo.Models.Attributes; namespace Kyoo.Models { + /// + /// The type of item, ether a show, a movie or a collection. + /// public enum ItemType { Show, @@ -12,16 +15,50 @@ namespace Kyoo.Models Collection } + /// + /// A type union between and . + /// This is used to list content put inside a library. + /// public class LibraryItem : IResource { + /// public int ID { get; set; } + + /// public string Slug { get; set; } + + /// + /// The title of the show or collection. + /// public string Title { get; set; } + + /// + /// The summary of the show or collection. + /// public string Overview { get; set; } + + /// + /// Is this show airing, not aired yet or finished? This is only applicable for shows. + /// public Status? Status { get; set; } - public string TrailerUrl { get; set; } - public int? StartYear { get; set; } - public int? EndYear { get; set; } + + /// + /// The date this show or collection started airing. It can be null if this is unknown. + /// + public DateTime? StartAir { get; set; } + + /// + /// The date this show or collection finished airing. + /// It must be after the but can be the same (example: for movies). + /// It can also be null if this is unknown. + /// + public DateTime? EndAir { get; set; } + + /// + /// The path of this item's poster. + /// By default, the http path for this poster is returned from the public API. + /// This can be disabled using the internal query flag. + /// [SerializeAs("{HOST}/api/{_type}/{Slug}/poster")] public string Poster { get; set; } [UsedImplicitly] private string _type => Type == ItemType.Collection ? "collection" : "show"; public ItemType Type { get; set; } @@ -35,9 +72,8 @@ namespace Kyoo.Models Title = show.Title; Overview = show.Overview; Status = show.Status; - TrailerUrl = show.TrailerUrl; - StartYear = show.StartYear; - EndYear = show.EndYear; + StartAir = show.StartAir; + EndAir = show.EndAir; Poster = show.Poster; Type = show.IsMovie ? ItemType.Movie : ItemType.Show; } @@ -49,9 +85,8 @@ namespace Kyoo.Models Title = collection.Name; Overview = collection.Overview; Status = Models.Status.Unknown; - TrailerUrl = null; - StartYear = null; - EndYear = null; + StartAir = null; + EndAir = null; Poster = collection.Poster; Type = ItemType.Collection; } @@ -63,9 +98,8 @@ namespace Kyoo.Models Title = x.Title, Overview = x.Overview, Status = x.Status, - TrailerUrl = x.TrailerUrl, - StartYear = x.StartYear, - EndYear = x.EndYear, + StartAir = x.StartAir, + EndAir = x.EndAir, Poster= x.Poster, Type = x.IsMovie ? ItemType.Movie : ItemType.Show }; @@ -77,10 +111,9 @@ namespace Kyoo.Models Title = x.Name, Overview = x.Overview, Status = Models.Status.Unknown, - TrailerUrl = null, - StartYear = null, - EndYear = null, - Poster= x.Poster, + StartAir = null, + EndAir = null, + Poster = x.Poster, Type = ItemType.Collection }; } diff --git a/Kyoo.Common/Models/Link.cs b/Kyoo.Common/Models/Link.cs index 2df85f1f..41758b52 100644 --- a/Kyoo.Common/Models/Link.cs +++ b/Kyoo.Common/Models/Link.cs @@ -1,5 +1,4 @@ using System; -using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; namespace Kyoo.Models @@ -55,13 +54,12 @@ namespace Kyoo.Models where T1 : class, IResource where T2 : class, IResource { - public virtual T1 First { get; set; } - public virtual T2 Second { get; set; } + public T1 First { get; set; } + public T2 Second { get; set; } public Link() {} - [SuppressMessage("ReSharper", "VirtualMemberCallInConstructor")] public Link(T1 first, T2 second, bool privateItems = false) : base(first, second) { diff --git a/Kyoo.Common/Models/MetadataID.cs b/Kyoo.Common/Models/MetadataID.cs index d1752d50..400d65da 100644 --- a/Kyoo.Common/Models/MetadataID.cs +++ b/Kyoo.Common/Models/MetadataID.cs @@ -6,19 +6,19 @@ namespace Kyoo.Models { [SerializeIgnore] public int ID { get; set; } [SerializeIgnore] public int ProviderID { get; set; } - public virtual Provider Provider {get; set; } + public Provider Provider {get; set; } [SerializeIgnore] public int? ShowID { get; set; } - [SerializeIgnore] public virtual Show Show { get; set; } + [SerializeIgnore] public Show Show { get; set; } [SerializeIgnore] public int? EpisodeID { get; set; } - [SerializeIgnore] public virtual Episode Episode { get; set; } + [SerializeIgnore] public Episode Episode { get; set; } [SerializeIgnore] public int? SeasonID { get; set; } - [SerializeIgnore] public virtual Season Season { get; set; } + [SerializeIgnore] public Season Season { get; set; } [SerializeIgnore] public int? PeopleID { get; set; } - [SerializeIgnore] public virtual People People { get; set; } + [SerializeIgnore] public People People { get; set; } public string DataID { get; set; } public string Link { get; set; } diff --git a/Kyoo.Common/Models/Resources/Show.cs b/Kyoo.Common/Models/Resources/Show.cs index e7d14d79..7656401f 100644 --- a/Kyoo.Common/Models/Resources/Show.cs +++ b/Kyoo.Common/Models/Resources/Show.cs @@ -1,53 +1,168 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; +using Kyoo.Common.Models.Attributes; +using Kyoo.Controllers; using Kyoo.Models.Attributes; namespace Kyoo.Models { + /// + /// A series or a movie. + /// public class Show : IResource, IOnMerge { + /// public int ID { get; set; } + + /// public string Slug { get; set; } + + /// + /// The title of this show. + /// public string Title { get; set; } + + /// + /// The list of alternative titles of this show. + /// [EditableRelation] public string[] Aliases { get; set; } + + /// + /// The path of the root directory of this show. + /// This can be any kind of path supported by + /// [SerializeIgnore] public string Path { get; set; } + + /// + /// The summary of this show. + /// public string Overview { get; set; } + + /// + /// Is this show airing, not aired yet or finished? + /// public Status? Status { get; set; } + + /// + /// An URL to a trailer. This could be any path supported by the . + /// + /// TODO for now, this is set to a youtube url. It should be cached and converted to a local file. public string TrailerUrl { get; set; } + + /// + /// The date this show started airing. It can be null if this is unknown. + /// + public DateTime? StartAir { get; set; } + + /// + /// The date this show finished airing. + /// It must be after the but can be the same (example: for movies). + /// It can also be null if this is unknown. + /// + public DateTime? EndAir { get; set; } - public int? StartYear { get; set; } - public int? EndYear { get; set; } - + /// + /// The path of this show's poster. + /// By default, the http path for this poster is returned from the public API. + /// This can be disabled using the internal query flag. + /// [SerializeAs("{HOST}/api/shows/{Slug}/poster")] public string Poster { get; set; } + + /// + /// The path of this show's logo. + /// By default, the http path for this logo is returned from the public API. + /// This can be disabled using the internal query flag. + /// [SerializeAs("{HOST}/api/shows/{Slug}/logo")] public string Logo { get; set; } + + /// + /// The path of this show's backdrop. + /// By default, the http path for this backdrop is returned from the public API. + /// This can be disabled using the internal query flag. + /// [SerializeAs("{HOST}/api/shows/{Slug}/backdrop")] public string Backdrop { get; set; } + /// + /// True if this show represent a movie, false otherwise. + /// public bool IsMovie { get; set; } - [EditableRelation] [LoadableRelation] public virtual ICollection ExternalIDs { get; set; } - + /// + /// The link to metadata providers that this show has. See for more information. + /// + [EditableRelation] [LoadableRelation] public ICollection ExternalIDs { get; set; } + /// + /// The ID of the Studio that made this show. This value is only set when the has been loaded. + /// [SerializeIgnore] public int? StudioID { get; set; } - [LoadableRelation(nameof(StudioID))] [EditableRelation] public virtual Studio Studio { get; set; } - [LoadableRelation] [EditableRelation] public virtual ICollection Genres { get; set; } - [LoadableRelation] [EditableRelation] public virtual ICollection People { get; set; } - [LoadableRelation] public virtual ICollection Seasons { get; set; } - [LoadableRelation] public virtual ICollection Episodes { get; set; } - [LoadableRelation] public virtual ICollection Libraries { get; set; } - [LoadableRelation] public virtual ICollection Collections { get; set; } + /// + /// The Studio that made this show. This must be explicitly loaded via a call to . + /// + [LoadableRelation(nameof(StudioID))] [EditableRelation] public Studio Studio { get; set; } + + /// + /// The list of genres (themes) this show has. + /// + [LoadableRelation] [EditableRelation] public ICollection Genres { get; set; } + + /// + /// The list of people that made this show. + /// + [LoadableRelation] [EditableRelation] public ICollection People { get; set; } + + /// + /// The different seasons in this show. If this is a movie, this list is always null or empty. + /// + [LoadableRelation] public ICollection Seasons { get; set; } + + /// + /// The list of episodes in this show. If this is a movie, there will be a unique episode (with the seasonNumber and episodeNumber set to -1). + /// Having an episode is necessary to store metadata and tracks. + /// + [LoadableRelation] public ICollection Episodes { get; set; } + + /// + /// The list of libraries that contains this show. + /// + [LoadableRelation] public ICollection Libraries { get; set; } + + /// + /// The list of collections that contains this show. + /// + [LoadableRelation] public ICollection Collections { get; set; } #if ENABLE_INTERNAL_LINKS - [SerializeIgnore] public virtual ICollection> LibraryLinks { get; set; } - [SerializeIgnore] public virtual ICollection> CollectionLinks { get; set; } - [SerializeIgnore] public virtual ICollection> GenreLinks { get; set; } + /// + /// The internal link between this show and libraries in the list. + /// + [Link] public ICollection> LibraryLinks { get; set; } + + /// + /// The internal link between this show and collections in the list. + /// + [Link] public ICollection> CollectionLinks { get; set; } + + /// + /// The internal link between this show and genres in the list. + /// + [Link] public ICollection> GenreLinks { get; set; } #endif + /// + /// Retrieve the internal provider's ID of a show using it's provider slug. + /// + /// This method will never return anything if the are not loaded. + /// The slug of the provider + /// The field of the asked provider. public string GetID(string provider) { - return ExternalIDs?.FirstOrDefault(x => x.Provider.Name == provider)?.DataID; + return ExternalIDs?.FirstOrDefault(x => x.Provider.Slug == provider)?.DataID; } - public virtual void OnMerge(object merged) + /// + public void OnMerge(object merged) { if (ExternalIDs != null) foreach (MetadataID id in ExternalIDs) @@ -64,5 +179,8 @@ namespace Kyoo.Models } } + /// + /// The enum containing show's status. + /// public enum Status { Finished, Airing, Planned, Unknown } } diff --git a/Kyoo.Common/Models/Resources/Studio.cs b/Kyoo.Common/Models/Resources/Studio.cs index 9eea3a7b..ebc3c4c1 100644 --- a/Kyoo.Common/Models/Resources/Studio.cs +++ b/Kyoo.Common/Models/Resources/Studio.cs @@ -3,31 +3,40 @@ using Kyoo.Models.Attributes; namespace Kyoo.Models { + /// + /// A studio that make shows. + /// public class Studio : IResource { + /// public int ID { get; set; } + + /// public string Slug { get; set; } + + /// + /// The name of this studio. + /// public string Name { get; set; } - [LoadableRelation] public virtual ICollection Shows { get; set; } + /// + /// The list of shows that are made by this studio. + /// + [LoadableRelation] public ICollection Shows { get; set; } + /// + /// Create a new, empty, . + /// public Studio() { } + /// + /// Create a new with a specific name, the slug is calculated automatically. + /// + /// The name of the studio. public Studio(string name) { Slug = Utility.ToSlug(name); Name = name; } - - public Studio(string slug, string name) - { - Slug = slug; - Name = name; - } - - public static Studio Default() - { - return new Studio("unknown", "Unknown Studio"); - } } } diff --git a/Kyoo.Common/Models/Resources/Track.cs b/Kyoo.Common/Models/Resources/Track.cs index 92438e0a..290ecd3f 100644 --- a/Kyoo.Common/Models/Resources/Track.cs +++ b/Kyoo.Common/Models/Resources/Track.cs @@ -1,11 +1,13 @@ -using Kyoo.Models.Watch; -using System.Globalization; +using System.Globalization; using System.Linq; -using System.Runtime.InteropServices; using Kyoo.Models.Attributes; namespace Kyoo.Models { + /// + /// The list of available stream types. + /// Attachments are only used temporarily by the transcoder but are not stored in a database. + /// public enum StreamType { Unknown = 0, @@ -15,82 +17,15 @@ namespace Kyoo.Models Attachment = 4 } - namespace Watch - { - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] - public class Stream - { - public string Title { get; set; } - public string Language { get; set; } - public string Codec { get; set; } - [MarshalAs(UnmanagedType.I1)] public bool isDefault; - [MarshalAs(UnmanagedType.I1)] public bool isForced; - [SerializeIgnore] public string Path { get; set; } - [SerializeIgnore] public StreamType Type { get; set; } - - public Stream() {} - - public Stream(string title, string language, string codec, bool isDefault, bool isForced, string path, StreamType type) - { - Title = title; - Language = language; - Codec = codec; - this.isDefault = isDefault; - this.isForced = isForced; - Path = path; - Type = type; - } - - public Stream(Stream stream) - { - Title = stream.Title; - Language = stream.Language; - isDefault = stream.isDefault; - isForced = stream.isForced; - Codec = stream.Codec; - Path = stream.Path; - Type = stream.Type; - } - } - } - - public class Track : Stream, IResource + /// + /// A video, audio or subtitle track for an episode. + /// + public class Track : IResource { + /// public int ID { get; set; } - [SerializeIgnore] public int EpisodeID { get; set; } - public int TrackIndex { get; set; } - public bool IsDefault - { - get => isDefault; - set => isDefault = value; - } - public bool IsForced - { - get => isForced; - set => isForced = value; - } - - public string DisplayName - { - get - { - string language = GetLanguage(Language); - - if (language == null) - return $"Unknown (index: {TrackIndex})"; - CultureInfo info = CultureInfo.GetCultures(CultureTypes.NeutralCultures) - .FirstOrDefault(x => x.ThreeLetterISOLanguageName == language); - string name = info?.EnglishName ?? language; - if (IsForced) - name += " Forced"; - if (IsExternal) - name += " (External)"; - if (Title != null && Title.Length > 1) - name += " - " + Title; - return name; - } - } - + + /// public string Slug { get @@ -112,34 +47,90 @@ namespace Kyoo.Models return $"{Episode.Slug}.{type}{Language}{index}{(IsForced ? "-forced" : "")}{codec}"; } } - - public bool IsExternal { get; set; } - [LoadableRelation(nameof(EpisodeID))] public virtual Episode Episode { get; set; } - public Track() { } + /// + /// The title of the stream. + /// + public string Title { get; set; } + + /// + /// The language of this stream (as a ISO-639-2 language code) + /// + public string Language { get; set; } + + /// + /// The codec of this stream. + /// + public string Codec { get; set; } + + + /// + /// Is this stream the default one of it's type? + /// + public bool IsDefault { get; set; } + + /// + /// Is this stream tagged as forced? + /// + public bool IsForced { get; set; } + + /// + /// Is this track extern to the episode's file? + /// + public bool IsExternal { get; set; } + + /// + /// The path of this track. + /// + [SerializeIgnore] public string Path { get; set; } + + /// + /// The type of this stream. + /// + [SerializeIgnore] public StreamType Type { get; set; } + + /// + /// The ID of the episode that uses this track. This value is only set when the has been loaded. + /// + [SerializeIgnore] public int EpisodeID { get; set; } + /// + /// The episode that uses this track. + /// + [LoadableRelation(nameof(EpisodeID))] public Episode Episode { get; set; } - public Track(StreamType type, - string title, - string language, - bool isDefault, - bool isForced, - string codec, - bool isExternal, - string path) - : base(title, language, codec, isDefault, isForced, path, type) - { - IsExternal = isExternal; - } + /// + /// The index of this track on the episode. + /// + public int TrackIndex { get; set; } - public Track(Stream stream) - : base(stream) + /// + /// A user-friendly name for this track. It does not include the track type. + /// + public string DisplayName { - IsExternal = false; + get + { + string language = GetLanguage(Language); + + if (language == null) + return $"Unknown (index: {TrackIndex})"; + CultureInfo info = CultureInfo.GetCultures(CultureTypes.NeutralCultures) + .FirstOrDefault(x => x.ThreeLetterISOLanguageName == language); + string name = info?.EnglishName ?? language; + if (IsForced) + name += " Forced"; + if (IsExternal) + name += " (External)"; + if (Title is {Length: > 1}) + name += " - " + Title; + return name; + } } //Converting mkv track language to c# system language tag. private static string GetLanguage(string mkvLanguage) { + // TODO delete this and have a real way to get the language string from the ISO-639-2. return mkvLanguage switch { "fre" => "fra", diff --git a/Kyoo.Common/Utility/EnumerableExtensions.cs b/Kyoo.Common/Utility/EnumerableExtensions.cs index 459d5f1c..93f1b52e 100644 --- a/Kyoo.Common/Utility/EnumerableExtensions.cs +++ b/Kyoo.Common/Utility/EnumerableExtensions.cs @@ -20,7 +20,8 @@ namespace Kyoo /// The type of items in the returned list /// The list mapped. /// The list or the mapper can't be null - public static IEnumerable Map([NotNull] this IEnumerable self, + [LinqTunnel] + public static IEnumerable Map([NotNull] this IEnumerable self, [NotNull] Func mapper) { if (self == null) @@ -52,6 +53,7 @@ namespace Kyoo /// The type of items in the returned list /// The list mapped as an AsyncEnumerable /// The list or the mapper can't be null + [LinqTunnel] public static IAsyncEnumerable MapAsync([NotNull] this IEnumerable self, [NotNull] Func> mapper) { @@ -84,6 +86,7 @@ namespace Kyoo /// The type of items in the returned list /// The list mapped as an AsyncEnumerable /// The list or the mapper can't be null + [LinqTunnel] public static IAsyncEnumerable SelectAsync([NotNull] this IEnumerable self, [NotNull] Func> mapper) { @@ -110,6 +113,7 @@ namespace Kyoo /// The type of items in the async list and in the returned list. /// A task that will return a simple list /// The list can't be null + [LinqTunnel] public static Task> ToListAsync([NotNull] this IAsyncEnumerable self) { if (self == null) @@ -134,6 +138,7 @@ namespace Kyoo /// The type of items inside the list /// The iterable and the action can't be null. /// The iterator proxied, there is no dual iterations. + [LinqTunnel] public static IEnumerable IfEmpty([NotNull] this IEnumerable self, [NotNull] Action action) { if (self == null) @@ -236,6 +241,7 @@ namespace Kyoo /// The number of items in each chunk /// The type of data in the initial list. /// A list of chunks + [LinqTunnel] public static IEnumerable> BatchBy(this List list, int countPerList) { for (int i = 0; i < list.Count; i += countPerList) @@ -249,6 +255,7 @@ namespace Kyoo /// The number of items in each chunk /// The type of data in the initial list. /// A list of chunks + [LinqTunnel] public static IEnumerable BatchBy(this IEnumerable list, int countPerList) { T[] ret = new T[countPerList]; diff --git a/Kyoo.Tests/KAssert.cs b/Kyoo.Tests/KAssert.cs index 2c97ddce..7ac753dd 100644 --- a/Kyoo.Tests/KAssert.cs +++ b/Kyoo.Tests/KAssert.cs @@ -1,4 +1,5 @@ using System.Reflection; +using JetBrains.Annotations; using Xunit; using Xunit.Sdk; @@ -15,6 +16,7 @@ namespace Kyoo.Tests /// The value to check against /// The value to check /// The type to check + [AssertionMethod] public static void DeepEqual(T expected, T value) { foreach (PropertyInfo property in typeof(T).GetProperties()) @@ -24,6 +26,7 @@ namespace Kyoo.Tests /// /// Explicitly fail a test. /// + [AssertionMethod] public static void Fail() { throw new XunitException(); @@ -33,6 +36,7 @@ namespace Kyoo.Tests /// Explicitly fail a test. /// /// The message that will be seen in the test report + [AssertionMethod] public static void Fail(string message) { throw new XunitException(message); diff --git a/Kyoo.Tests/Library/TestSample.cs b/Kyoo.Tests/Library/TestSample.cs index 5642054b..16eed5d8 100644 --- a/Kyoo.Tests/Library/TestSample.cs +++ b/Kyoo.Tests/Library/TestSample.cs @@ -26,8 +26,8 @@ namespace Kyoo.Tests "school students, they had long ceased to think of each other as friends.", Status = Status.Finished, TrailerUrl = null, - StartYear = 2011, - EndYear = 2011, + StartAir = new DateTime(2011), + EndAir = new DateTime(2011), Poster = "poster", Logo = "logo", Backdrop = "backdrop", diff --git a/Kyoo/Controllers/Transcoder.cs b/Kyoo/Controllers/Transcoder.cs index 823aa9d2..8f6e006e 100644 --- a/Kyoo/Controllers/Transcoder.cs +++ b/Kyoo/Controllers/Transcoder.cs @@ -57,7 +57,7 @@ namespace Kyoo.Controllers Stream stream = Marshal.PtrToStructure(streamsPtr); if (stream!.Type != StreamType.Unknown) { - tracks[j] = new Track(stream); + tracks[j] = stream.ToTrack(); j++; } streamsPtr += size; diff --git a/Kyoo/Models/Stream.cs b/Kyoo/Models/Stream.cs new file mode 100644 index 00000000..3639a4a6 --- /dev/null +++ b/Kyoo/Models/Stream.cs @@ -0,0 +1,67 @@ +using System.Runtime.InteropServices; +using Kyoo.Models.Attributes; + +namespace Kyoo.Models.Watch +{ + /// + /// The unmanaged stream that the transcoder will return. + /// + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] + public class Stream + { + /// + /// The title of the stream. + /// + public string Title { get; set; } + + /// + /// The language of this stream (as a ISO-639-2 language code) + /// + public string Language { get; set; } + + /// + /// The codec of this stream. + /// + public string Codec { get; set; } + + /// + /// Is this stream the default one of it's type? + /// + [MarshalAs(UnmanagedType.I1)] public bool IsDefault; + + /// + /// Is this stream tagged as forced? + /// + [MarshalAs(UnmanagedType.I1)] public bool IsForced; + + /// + /// The path of this track. + /// + [SerializeIgnore] public string Path { get; set; } + + /// + /// The type of this stream. + /// + [SerializeIgnore] public StreamType Type { get; set; } + + + /// + /// Create a track from this stream. + /// + /// A new track that represent this stream. + public Track ToTrack() + { + return new() + { + Title = Title, + Language = Language, + Codec = Codec, + IsDefault = IsDefault, + IsForced = IsForced, + Path = Path, + Type = Type, + IsExternal = false + }; + } + } +} \ No newline at end of file diff --git a/Kyoo/Tasks/Crawler.cs b/Kyoo/Tasks/Crawler.cs index e6a6cebf..ebe3eaed 100644 --- a/Kyoo/Tasks/Crawler.cs +++ b/Kyoo/Tasks/Crawler.cs @@ -292,13 +292,19 @@ namespace Kyoo.Tasks catch (DuplicatedItemException) { old = await libraryManager.GetOrDefault(show.Slug); - if (old.Path == showPath) + if (old != null && old.Path == showPath) { await libraryManager.Load(old, x => x.ExternalIDs); return old; } - show.Slug += $"-{show.StartYear}"; - await libraryManager.Create(show); + + if (show.StartAir != null) + { + show.Slug += $"-{show.StartAir.Value.Year}"; + await libraryManager.Create(show); + } + else + throw; } await ThumbnailsManager.Validate(show); return show;