Adding more documentation

This commit is contained in:
Zoe Roux 2021-06-02 00:35:10 +02:00
parent 2bc559424c
commit c7569691e2
14 changed files with 417 additions and 159 deletions

View File

@ -8,7 +8,8 @@ namespace Kyoo.Models.Attributes
/// An attribute to inform that the service will be injected automatically by a service provider.
/// </summary>
/// <remarks>
/// It should only be used on <see cref="ITask"/> and will be injected before calling <see cref="ITask.Run"/>
/// It should only be used on <see cref="ITask"/> and will be injected before calling <see cref="ITask.Run"/>.
/// It can also be used on <see cref="IPlugin"/> and it will be injected before calling <see cref="IPlugin.ConfigureAspNet"/>.
/// </remarks>
[AttributeUsage(AttributeTargets.Property)]
[MeansImplicitUse(ImplicitUseKindFlags.Assign)]

View File

@ -2,17 +2,41 @@ using System;
namespace Kyoo.Models.Attributes
{
/// <summary>
/// Remove an property from the serialization pipeline. It will simply be skipped.
/// </summary>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public class SerializeIgnoreAttribute : Attribute {}
/// <summary>
/// Remove a property from the deserialization pipeline. The user can't input value for this property.
/// </summary>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public class DeserializeIgnoreAttribute : Attribute {}
/// <summary>
/// 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.
/// </summary>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public class SerializeAsAttribute : Attribute
{
/// <summary>
/// The format string to use.
/// </summary>
public string Format { get; }
/// <summary>
/// Create a new <see cref="SerializeAsAttribute"/> with the selected format.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
/// <example>
/// The show's poster serialized uses this format string: <code>{HOST}/api/shows/{Slug}/poster</code>
/// </example>
/// <param name="format">The format to use</param>
public SerializeAsAttribute(string format)
{
Format = format;

View File

@ -5,6 +5,9 @@ using Kyoo.Models.Attributes;
namespace Kyoo.Models
{
/// <summary>
/// The type of item, ether a show, a movie or a collection.
/// </summary>
public enum ItemType
{
Show,
@ -12,16 +15,50 @@ namespace Kyoo.Models
Collection
}
/// <summary>
/// A type union between <see cref="Show"/> and <see cref="Collection"/>.
/// This is used to list content put inside a library.
/// </summary>
public class LibraryItem : IResource
{
/// <inheritdoc />
public int ID { get; set; }
/// <inheritdoc />
public string Slug { get; set; }
/// <summary>
/// The title of the show or collection.
/// </summary>
public string Title { get; set; }
/// <summary>
/// The summary of the show or collection.
/// </summary>
public string Overview { get; set; }
/// <summary>
/// Is this show airing, not aired yet or finished? This is only applicable for shows.
/// </summary>
public Status? Status { get; set; }
public string TrailerUrl { get; set; }
public int? StartYear { get; set; }
public int? EndYear { get; set; }
/// <summary>
/// The date this show or collection started airing. It can be null if this is unknown.
/// </summary>
public DateTime? StartAir { get; set; }
/// <summary>
/// The date this show or collection finished airing.
/// It must be after the <see cref="StartAir"/> but can be the same (example: for movies).
/// It can also be null if this is unknown.
/// </summary>
public DateTime? EndAir { get; set; }
/// <summary>
/// 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.
/// </summary>
[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,9 +111,8 @@ namespace Kyoo.Models
Title = x.Name,
Overview = x.Overview,
Status = Models.Status.Unknown,
TrailerUrl = null,
StartYear = null,
EndYear = null,
StartAir = null,
EndAir = null,
Poster = x.Poster,
Type = ItemType.Collection
};

View File

@ -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)
{

View File

@ -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; }

View File

@ -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
{
/// <summary>
/// A series or a movie.
/// </summary>
public class Show : IResource, IOnMerge
{
/// <inheritdoc />
public int ID { get; set; }
/// <inheritdoc />
public string Slug { get; set; }
/// <summary>
/// The title of this show.
/// </summary>
public string Title { get; set; }
/// <summary>
/// The list of alternative titles of this show.
/// </summary>
[EditableRelation] public string[] Aliases { get; set; }
/// <summary>
/// The path of the root directory of this show.
/// This can be any kind of path supported by <see cref="IFileManager"/>
/// </summary>
[SerializeIgnore] public string Path { get; set; }
/// <summary>
/// The summary of this show.
/// </summary>
public string Overview { get; set; }
/// <summary>
/// Is this show airing, not aired yet or finished?
/// </summary>
public Status? Status { get; set; }
/// <summary>
/// An URL to a trailer. This could be any path supported by the <see cref="IFileManager"/>.
/// </summary>
/// 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; }
public int? StartYear { get; set; }
public int? EndYear { get; set; }
/// <summary>
/// The date this show started airing. It can be null if this is unknown.
/// </summary>
public DateTime? StartAir { get; set; }
/// <summary>
/// The date this show finished airing.
/// It must be after the <see cref="StartAir"/> but can be the same (example: for movies).
/// It can also be null if this is unknown.
/// </summary>
public DateTime? EndAir { get; set; }
/// <summary>
/// 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.
/// </summary>
[SerializeAs("{HOST}/api/shows/{Slug}/poster")] public string Poster { get; set; }
/// <summary>
/// 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.
/// </summary>
[SerializeAs("{HOST}/api/shows/{Slug}/logo")] public string Logo { get; set; }
/// <summary>
/// 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.
/// </summary>
[SerializeAs("{HOST}/api/shows/{Slug}/backdrop")] public string Backdrop { get; set; }
/// <summary>
/// True if this show represent a movie, false otherwise.
/// </summary>
public bool IsMovie { get; set; }
[EditableRelation] [LoadableRelation] public virtual ICollection<MetadataID> ExternalIDs { get; set; }
/// <summary>
/// The link to metadata providers that this show has. See <see cref="MetadataID"/> for more information.
/// </summary>
[EditableRelation] [LoadableRelation] public ICollection<MetadataID> ExternalIDs { get; set; }
/// <summary>
/// The ID of the Studio that made this show. This value is only set when the <see cref="Studio"/> has been loaded.
/// </summary>
[SerializeIgnore] public int? StudioID { get; set; }
[LoadableRelation(nameof(StudioID))] [EditableRelation] public virtual Studio Studio { get; set; }
[LoadableRelation] [EditableRelation] public virtual ICollection<Genre> Genres { get; set; }
[LoadableRelation] [EditableRelation] public virtual ICollection<PeopleRole> People { get; set; }
[LoadableRelation] public virtual ICollection<Season> Seasons { get; set; }
[LoadableRelation] public virtual ICollection<Episode> Episodes { get; set; }
[LoadableRelation] public virtual ICollection<Library> Libraries { get; set; }
[LoadableRelation] public virtual ICollection<Collection> Collections { get; set; }
/// <summary>
/// The Studio that made this show. This must be explicitly loaded via a call to <see cref="ILibraryManager.Load"/>.
/// </summary>
[LoadableRelation(nameof(StudioID))] [EditableRelation] public Studio Studio { get; set; }
/// <summary>
/// The list of genres (themes) this show has.
/// </summary>
[LoadableRelation] [EditableRelation] public ICollection<Genre> Genres { get; set; }
/// <summary>
/// The list of people that made this show.
/// </summary>
[LoadableRelation] [EditableRelation] public ICollection<PeopleRole> People { get; set; }
/// <summary>
/// The different seasons in this show. If this is a movie, this list is always null or empty.
/// </summary>
[LoadableRelation] public ICollection<Season> Seasons { get; set; }
/// <summary>
/// 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.
/// </summary>
[LoadableRelation] public ICollection<Episode> Episodes { get; set; }
/// <summary>
/// The list of libraries that contains this show.
/// </summary>
[LoadableRelation] public ICollection<Library> Libraries { get; set; }
/// <summary>
/// The list of collections that contains this show.
/// </summary>
[LoadableRelation] public ICollection<Collection> Collections { get; set; }
#if ENABLE_INTERNAL_LINKS
[SerializeIgnore] public virtual ICollection<Link<Library, Show>> LibraryLinks { get; set; }
[SerializeIgnore] public virtual ICollection<Link<Collection, Show>> CollectionLinks { get; set; }
[SerializeIgnore] public virtual ICollection<Link<Show, Genre>> GenreLinks { get; set; }
/// <summary>
/// The internal link between this show and libraries in the <see cref="Libraries"/> list.
/// </summary>
[Link] public ICollection<Link<Library, Show>> LibraryLinks { get; set; }
/// <summary>
/// The internal link between this show and collections in the <see cref="Collections"/> list.
/// </summary>
[Link] public ICollection<Link<Collection, Show>> CollectionLinks { get; set; }
/// <summary>
/// The internal link between this show and genres in the <see cref="Genres"/> list.
/// </summary>
[Link] public ICollection<Link<Show, Genre>> GenreLinks { get; set; }
#endif
/// <summary>
/// Retrieve the internal provider's ID of a show using it's provider slug.
/// </summary>
/// <remarks>This method will never return anything if the <see cref="ExternalIDs"/> are not loaded.</remarks>
/// <param name="provider">The slug of the provider</param>
/// <returns>The <see cref="MetadataID.DataID"/> field of the asked provider.</returns>
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)
/// <inheritdoc />
public void OnMerge(object merged)
{
if (ExternalIDs != null)
foreach (MetadataID id in ExternalIDs)
@ -64,5 +179,8 @@ namespace Kyoo.Models
}
}
/// <summary>
/// The enum containing show's status.
/// </summary>
public enum Status { Finished, Airing, Planned, Unknown }
}

View File

@ -3,31 +3,40 @@ using Kyoo.Models.Attributes;
namespace Kyoo.Models
{
/// <summary>
/// A studio that make shows.
/// </summary>
public class Studio : IResource
{
/// <inheritdoc />
public int ID { get; set; }
/// <inheritdoc />
public string Slug { get; set; }
/// <summary>
/// The name of this studio.
/// </summary>
public string Name { get; set; }
[LoadableRelation] public virtual ICollection<Show> Shows { get; set; }
/// <summary>
/// The list of shows that are made by this studio.
/// </summary>
[LoadableRelation] public ICollection<Show> Shows { get; set; }
/// <summary>
/// Create a new, empty, <see cref="Studio"/>.
/// </summary>
public Studio() { }
/// <summary>
/// Create a new <see cref="Studio"/> with a specific name, the slug is calculated automatically.
/// </summary>
/// <param name="name">The name of the studio.</param>
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");
}
}
}

View File

@ -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
{
/// <summary>
/// The list of available stream types.
/// Attachments are only used temporarily by the transcoder but are not stored in a database.
/// </summary>
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
/// <summary>
/// A video, audio or subtitle track for an episode.
/// </summary>
public class Track : IResource
{
/// <inheritdoc />
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;
}
}
/// <inheritdoc />
public string Slug
{
get
@ -113,33 +48,89 @@ namespace Kyoo.Models
}
}
/// <summary>
/// The title of the stream.
/// </summary>
public string Title { get; set; }
/// <summary>
/// The language of this stream (as a ISO-639-2 language code)
/// </summary>
public string Language { get; set; }
/// <summary>
/// The codec of this stream.
/// </summary>
public string Codec { get; set; }
/// <summary>
/// Is this stream the default one of it's type?
/// </summary>
public bool IsDefault { get; set; }
/// <summary>
/// Is this stream tagged as forced?
/// </summary>
public bool IsForced { get; set; }
/// <summary>
/// Is this track extern to the episode's file?
/// </summary>
public bool IsExternal { get; set; }
[LoadableRelation(nameof(EpisodeID))] public virtual Episode Episode { get; set; }
public Track() { }
/// <summary>
/// The path of this track.
/// </summary>
[SerializeIgnore] public string Path { 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)
/// <summary>
/// The type of this stream.
/// </summary>
[SerializeIgnore] public StreamType Type { get; set; }
/// <summary>
/// The ID of the episode that uses this track. This value is only set when the <see cref="Episode"/> has been loaded.
/// </summary>
[SerializeIgnore] public int EpisodeID { get; set; }
/// <summary>
/// The episode that uses this track.
/// </summary>
[LoadableRelation(nameof(EpisodeID))] public Episode Episode { get; set; }
/// <summary>
/// The index of this track on the episode.
/// </summary>
public int TrackIndex { get; set; }
/// <summary>
/// A user-friendly name for this track. It does not include the track type.
/// </summary>
public string DisplayName
{
IsExternal = isExternal;
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;
}
public Track(Stream stream)
: base(stream)
{
IsExternal = false;
}
//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",

View File

@ -20,6 +20,7 @@ namespace Kyoo
/// <typeparam name="T2">The type of items in the returned list</typeparam>
/// <returns>The list mapped.</returns>
/// <exception cref="ArgumentNullException">The list or the mapper can't be null</exception>
[LinqTunnel]
public static IEnumerable<T2> Map<T, T2>([NotNull] this IEnumerable<T> self,
[NotNull] Func<T, int, T2> mapper)
{
@ -52,6 +53,7 @@ namespace Kyoo
/// <typeparam name="T2">The type of items in the returned list</typeparam>
/// <returns>The list mapped as an AsyncEnumerable</returns>
/// <exception cref="ArgumentNullException">The list or the mapper can't be null</exception>
[LinqTunnel]
public static IAsyncEnumerable<T2> MapAsync<T, T2>([NotNull] this IEnumerable<T> self,
[NotNull] Func<T, int, Task<T2>> mapper)
{
@ -84,6 +86,7 @@ namespace Kyoo
/// <typeparam name="T2">The type of items in the returned list</typeparam>
/// <returns>The list mapped as an AsyncEnumerable</returns>
/// <exception cref="ArgumentNullException">The list or the mapper can't be null</exception>
[LinqTunnel]
public static IAsyncEnumerable<T2> SelectAsync<T, T2>([NotNull] this IEnumerable<T> self,
[NotNull] Func<T, Task<T2>> mapper)
{
@ -110,6 +113,7 @@ namespace Kyoo
/// <typeparam name="T">The type of items in the async list and in the returned list.</typeparam>
/// <returns>A task that will return a simple list</returns>
/// <exception cref="ArgumentNullException">The list can't be null</exception>
[LinqTunnel]
public static Task<List<T>> ToListAsync<T>([NotNull] this IAsyncEnumerable<T> self)
{
if (self == null)
@ -134,6 +138,7 @@ namespace Kyoo
/// <typeparam name="T">The type of items inside the list</typeparam>
/// <exception cref="ArgumentNullException">The iterable and the action can't be null.</exception>
/// <returns>The iterator proxied, there is no dual iterations.</returns>
[LinqTunnel]
public static IEnumerable<T> IfEmpty<T>([NotNull] this IEnumerable<T> self, [NotNull] Action action)
{
if (self == null)
@ -236,6 +241,7 @@ namespace Kyoo
/// <param name="countPerList">The number of items in each chunk</param>
/// <typeparam name="T">The type of data in the initial list.</typeparam>
/// <returns>A list of chunks</returns>
[LinqTunnel]
public static IEnumerable<List<T>> BatchBy<T>(this List<T> list, int countPerList)
{
for (int i = 0; i < list.Count; i += countPerList)
@ -249,6 +255,7 @@ namespace Kyoo
/// <param name="countPerList">The number of items in each chunk</param>
/// <typeparam name="T">The type of data in the initial list.</typeparam>
/// <returns>A list of chunks</returns>
[LinqTunnel]
public static IEnumerable<T[]> BatchBy<T>(this IEnumerable<T> list, int countPerList)
{
T[] ret = new T[countPerList];

View File

@ -1,4 +1,5 @@
using System.Reflection;
using JetBrains.Annotations;
using Xunit;
using Xunit.Sdk;
@ -15,6 +16,7 @@ namespace Kyoo.Tests
/// <param name="expected">The value to check against</param>
/// <param name="value">The value to check</param>
/// <typeparam name="T">The type to check</typeparam>
[AssertionMethod]
public static void DeepEqual<T>(T expected, T value)
{
foreach (PropertyInfo property in typeof(T).GetProperties())
@ -24,6 +26,7 @@ namespace Kyoo.Tests
/// <summary>
/// Explicitly fail a test.
/// </summary>
[AssertionMethod]
public static void Fail()
{
throw new XunitException();
@ -33,6 +36,7 @@ namespace Kyoo.Tests
/// Explicitly fail a test.
/// </summary>
/// <param name="message">The message that will be seen in the test report</param>
[AssertionMethod]
public static void Fail(string message)
{
throw new XunitException(message);

View File

@ -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",

View File

@ -57,7 +57,7 @@ namespace Kyoo.Controllers
Stream stream = Marshal.PtrToStructure<Stream>(streamsPtr);
if (stream!.Type != StreamType.Unknown)
{
tracks[j] = new Track(stream);
tracks[j] = stream.ToTrack();
j++;
}
streamsPtr += size;

67
Kyoo/Models/Stream.cs Normal file
View File

@ -0,0 +1,67 @@
using System.Runtime.InteropServices;
using Kyoo.Models.Attributes;
namespace Kyoo.Models.Watch
{
/// <summary>
/// The unmanaged stream that the transcoder will return.
/// </summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public class Stream
{
/// <summary>
/// The title of the stream.
/// </summary>
public string Title { get; set; }
/// <summary>
/// The language of this stream (as a ISO-639-2 language code)
/// </summary>
public string Language { get; set; }
/// <summary>
/// The codec of this stream.
/// </summary>
public string Codec { get; set; }
/// <summary>
/// Is this stream the default one of it's type?
/// </summary>
[MarshalAs(UnmanagedType.I1)] public bool IsDefault;
/// <summary>
/// Is this stream tagged as forced?
/// </summary>
[MarshalAs(UnmanagedType.I1)] public bool IsForced;
/// <summary>
/// The path of this track.
/// </summary>
[SerializeIgnore] public string Path { get; set; }
/// <summary>
/// The type of this stream.
/// </summary>
[SerializeIgnore] public StreamType Type { get; set; }
/// <summary>
/// Create a track from this stream.
/// </summary>
/// <returns>A new track that represent this stream.</returns>
public Track ToTrack()
{
return new()
{
Title = Title,
Language = Language,
Codec = Codec,
IsDefault = IsDefault,
IsForced = IsForced,
Path = Path,
Type = Type,
IsExternal = false
};
}
}
}

View File

@ -292,14 +292,20 @@ namespace Kyoo.Tasks
catch (DuplicatedItemException)
{
old = await libraryManager.GetOrDefault<Show>(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}";
if (show.StartAir != null)
{
show.Slug += $"-{show.StartAir.Value.Year}";
await libraryManager.Create(show);
}
else
throw;
}
await ThumbnailsManager.Validate(show);
return show;
}