mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-24 02:02:36 -04:00
Delete dotnet file system abstraction
This commit is contained in:
parent
ae901b257b
commit
c69864d0f5
@ -29,13 +29,6 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<IsWindows Condition="$([MSBuild]::IsOSPlatform('Windows'))">true</IsWindows>
|
||||
<IsOSX Condition="$([MSBuild]::IsOSPlatform('OSX'))">true</IsOSX>
|
||||
<IsLinux Condition="$([MSBuild]::IsOSPlatform('Linux'))">true</IsLinux>
|
||||
|
||||
<!-- This is horrible but the only workarround I found to disable the transcoder copy on the dev dockerfile -->
|
||||
<SkipTranscoder Condition="$(MsBuildThisFileDirectory) == '/app/src/'">true</SkipTranscoder>
|
||||
|
||||
<CheckCodingStyle Condition="$(CheckCodingStyle) == ''">true</CheckCodingStyle>
|
||||
</PropertyGroup>
|
||||
|
||||
|
@ -1,139 +0,0 @@
|
||||
// Kyoo - A portable and vast media library solution.
|
||||
// Copyright (c) Kyoo.
|
||||
//
|
||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||
//
|
||||
// Kyoo is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// Kyoo is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using Kyoo.Abstractions.Models;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Kyoo.Abstractions.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// A service to abstract the file system to allow custom file systems
|
||||
/// (like distant file systems or external providers).
|
||||
/// </summary>
|
||||
public interface IFileSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Used for http queries returning a file. This should be used to return local files
|
||||
/// or proxy them from a distant server.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If no file exists at the given path or if the path is null, a NotFoundResult is returned
|
||||
/// to handle it gracefully.
|
||||
/// </remarks>
|
||||
/// <param name="path">The path of the file.</param>
|
||||
/// <param name="rangeSupport">
|
||||
/// Should the file be downloaded at once or is the client allowed to request only part of the file
|
||||
/// </param>
|
||||
/// <param name="type">
|
||||
/// You can manually specify the content type of your file.
|
||||
/// For example you can force a file to be returned as plain text using <c>text/plain</c>.
|
||||
/// If the type is not specified, it will be deduced automatically (from the extension or by sniffing the file).
|
||||
/// </param>
|
||||
/// <returns>An <see cref="IActionResult"/> representing the file returned.</returns>
|
||||
IActionResult FileResult([CanBeNull] string path, bool rangeSupport = false, string type = null);
|
||||
|
||||
/// <summary>
|
||||
/// Read a file present at <paramref name="path"/>. The reader can be used in an arbitrary context.
|
||||
/// To return files from an http endpoint, use <see cref="FileResult"/>.
|
||||
/// </summary>
|
||||
/// <param name="path">The path of the file</param>
|
||||
/// <exception cref="FileNotFoundException">If the file could not be found.</exception>
|
||||
/// <returns>A reader to read the file.</returns>
|
||||
Task<Stream> GetReader([NotNull] string path);
|
||||
|
||||
/// <summary>
|
||||
/// Read a file present at <paramref name="path"/>. The reader can be used in an arbitrary context.
|
||||
/// To return files from an http endpoint, use <see cref="FileResult"/>.
|
||||
/// </summary>
|
||||
/// <param name="path">The path of the file</param>
|
||||
/// <param name="mime">The mime type of the opened file.</param>
|
||||
/// <exception cref="FileNotFoundException">If the file could not be found.</exception>
|
||||
/// <returns>A reader to read the file.</returns>
|
||||
Task<Stream> GetReader([NotNull] string path, AsyncRef<string> mime);
|
||||
|
||||
/// <summary>
|
||||
/// Create a new file at <paramref name="path"></paramref>.
|
||||
/// </summary>
|
||||
/// <param name="path">The path of the new file.</param>
|
||||
/// <returns>A writer to write to the new file.</returns>
|
||||
Task<Stream> NewFile([NotNull] string path);
|
||||
|
||||
/// <summary>
|
||||
/// Create a new directory at the given path
|
||||
/// </summary>
|
||||
/// <param name="path">The path of the directory</param>
|
||||
/// <returns>The path of the newly created directory is returned.</returns>
|
||||
Task<string> CreateDirectory([NotNull] string path);
|
||||
|
||||
/// <summary>
|
||||
/// Combine multiple paths.
|
||||
/// </summary>
|
||||
/// <param name="paths">The paths to combine</param>
|
||||
/// <returns>The combined path.</returns>
|
||||
string Combine(params string[] paths);
|
||||
|
||||
/// <summary>
|
||||
/// List files in a directory.
|
||||
/// </summary>
|
||||
/// <param name="path">The path of the directory</param>
|
||||
/// <param name="options">Should the search be recursive or not.</param>
|
||||
/// <returns>A list of files's path.</returns>
|
||||
Task<ICollection<string>> ListFiles([NotNull] string path,
|
||||
SearchOption options = SearchOption.TopDirectoryOnly);
|
||||
|
||||
/// <summary>
|
||||
/// Check if a file exists at the given path.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to check</param>
|
||||
/// <returns>True if the path exists, false otherwise</returns>
|
||||
Task<bool> Exists([NotNull] string path);
|
||||
|
||||
/// <summary>
|
||||
/// Get the extra directory of a resource <typeparamref name="T"/>.
|
||||
/// This method is in this system to allow a filesystem to use a different metadata policy for one.
|
||||
/// It can be useful if the filesystem is readonly.
|
||||
/// </summary>
|
||||
/// <param name="resource">The resource to proceed</param>
|
||||
/// <typeparam name="T">The type of the resource.</typeparam>
|
||||
/// <returns>The extra directory of the resource.</returns>
|
||||
Task<string> GetExtraDirectory<T>([NotNull] T resource);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve tracks for a specific episode.
|
||||
/// Subtitles, chapters and fonts should also be extracted and cached when calling this method.
|
||||
/// </summary>
|
||||
/// <param name="episode">The episode to retrieve tracks for.</param>
|
||||
/// <param name="reExtract">Should the cache be invalidated and subtitles and others be re-extracted?</param>
|
||||
/// <returns>The list of tracks available for this episode.</returns>
|
||||
Task<ICollection<Track>> ExtractInfos([NotNull] Episode episode, bool reExtract);
|
||||
|
||||
/// <summary>
|
||||
/// Transmux the selected episode to hls.
|
||||
/// </summary>
|
||||
/// <param name="episode">The episode to transmux.</param>
|
||||
/// <returns>The master file (m3u8) of the transmuxed hls file.</returns>
|
||||
IActionResult Transmux([NotNull] Episode episode);
|
||||
|
||||
// Maybe add options for to select the codec.
|
||||
// IActionResult Transcode(Episode episode);
|
||||
}
|
||||
}
|
@ -70,11 +70,6 @@ namespace Kyoo.Abstractions.Controllers
|
||||
/// </summary>
|
||||
IEpisodeRepository EpisodeRepository { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The repository that handle tracks.
|
||||
/// </summary>
|
||||
ITrackRepository TrackRepository { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The repository that handle people.
|
||||
/// </summary>
|
||||
|
@ -330,11 +330,6 @@ namespace Kyoo.Abstractions.Controllers
|
||||
Task<Episode> GetAbsolute(string showSlug, int absoluteNumber);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A repository to handle tracks
|
||||
/// </summary>
|
||||
public interface ITrackRepository : IRepository<Track> { }
|
||||
|
||||
/// <summary>
|
||||
/// A repository to handle libraries.
|
||||
/// </summary>
|
||||
|
@ -17,9 +17,10 @@
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using Kyoo.Abstractions.Models;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Kyoo.Abstractions.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
@ -39,17 +40,17 @@ namespace Kyoo.Abstractions.Controllers
|
||||
/// </param>
|
||||
/// <typeparam name="T">The type of the item</typeparam>
|
||||
/// <returns><c>true</c> if an image has been downloaded, <c>false</c> otherwise.</returns>
|
||||
Task<bool> DownloadImages<T>([NotNull] T item, bool alwaysDownload = false)
|
||||
Task<bool> DownloadImages<T>(T item, bool alwaysDownload = false)
|
||||
where T : IThumbnails;
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve the local path of an image of the given item.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to retrieve the poster from.</param>
|
||||
/// <param name="imageID">The ID of the image. See <see cref="Images"/> for values.</param>
|
||||
/// <param name="imageId">The ID of the image. See <see cref="Images"/> for values.</param>
|
||||
/// <typeparam name="T">The type of the item</typeparam>
|
||||
/// <returns>The path of the image for the given resource or null if it does not exists.</returns>
|
||||
Task<string> GetImagePath<T>([NotNull] T item, int imageID)
|
||||
string? GetImagePath<T>(T item, int imageId)
|
||||
where T : IThumbnails;
|
||||
}
|
||||
}
|
||||
|
@ -1,63 +0,0 @@
|
||||
// Kyoo - A portable and vast media library solution.
|
||||
// Copyright (c) Kyoo.
|
||||
//
|
||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||
//
|
||||
// Kyoo is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// Kyoo is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using Kyoo.Abstractions.Models;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Kyoo.Abstractions.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// Transcoder responsible of handling low level video details.
|
||||
/// </summary>
|
||||
public interface ITranscoder
|
||||
{
|
||||
/// <summary>
|
||||
/// Retrieve tracks for a specific episode.
|
||||
/// Subtitles, chapters and fonts should also be extracted and cached when calling this method.
|
||||
/// </summary>
|
||||
/// <param name="episode">The episode to retrieve tracks for.</param>
|
||||
/// <param name="reExtract">Should the cache be invalidated and subtitles and others be re-extracted?</param>
|
||||
/// <returns>The list of tracks available for this episode.</returns>
|
||||
Task<ICollection<Track>> ExtractInfos(Episode episode, bool reExtract);
|
||||
|
||||
/// <summary>
|
||||
/// List fonts assosiated with this episode.
|
||||
/// </summary>
|
||||
/// <param name="episode">Th episode to list fonts for.</param>
|
||||
/// <returns>The list of attachements for this epiosode.</returns>
|
||||
Task<ICollection<Font>> ListFonts(Episode episode);
|
||||
|
||||
/// <summary>
|
||||
/// Get the specified font for this episode.
|
||||
/// </summary>
|
||||
/// <param name="episode">The episode to list fonts for.</param>
|
||||
/// <param name="slug">The slug of the specific font to retrive.</param>
|
||||
/// <returns>The <see cref="Font"/> with the given slug or null.</returns>
|
||||
[ItemCanBeNull] Task<Font> GetFont(Episode episode, string slug);
|
||||
|
||||
/// <summary>
|
||||
/// Transmux the selected episode to hls.
|
||||
/// </summary>
|
||||
/// <param name="episode">The episode to transmux.</param>
|
||||
/// <returns>The master file (m3u8) of the transmuxed hls file.</returns>
|
||||
IActionResult Transmux(Episode episode);
|
||||
}
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
// Kyoo - A portable and vast media library solution.
|
||||
// Copyright (c) Kyoo.
|
||||
//
|
||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||
//
|
||||
// Kyoo is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// Kyoo is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
|
||||
namespace Kyoo.Abstractions.Models.Attributes
|
||||
{
|
||||
/// <summary>
|
||||
/// An attribute to inform how a <see cref="IFileSystem"/> works.
|
||||
/// </summary>
|
||||
[MetadataAttribute]
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class FileSystemMetadataAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// The scheme(s) used to identify this path.
|
||||
/// It can be something like http, https, ftp, file and so on.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If multiples files with the same schemes exists, an exception will be thrown.
|
||||
/// </remarks>
|
||||
public string[] Scheme { get; }
|
||||
|
||||
/// <summary>
|
||||
/// <c>true</c> if the scheme should be removed from the path before calling
|
||||
/// methods of this <see cref="IFileSystem"/>, <c>false</c> otherwise.
|
||||
/// </summary>
|
||||
public bool StripScheme { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="FileSystemMetadataAttribute"/> using the specified schemes.
|
||||
/// </summary>
|
||||
/// <param name="schemes">The schemes to use.</param>
|
||||
public FileSystemMetadataAttribute(string[] schemes)
|
||||
{
|
||||
Scheme = schemes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="FileSystemMetadataAttribute"/> using a dictionary of metadata.
|
||||
/// </summary>
|
||||
/// <param name="metadata">
|
||||
/// The dictionary of metadata. This method expect the dictionary to contain a field
|
||||
/// per property in this attribute, with the same types as the properties of this attribute.
|
||||
/// </param>
|
||||
public FileSystemMetadataAttribute(IDictionary<string, object> metadata)
|
||||
{
|
||||
Scheme = (string[])metadata[nameof(Scheme)];
|
||||
StripScheme = (bool)metadata[nameof(StripScheme)];
|
||||
}
|
||||
}
|
||||
}
|
@ -1,94 +0,0 @@
|
||||
// Kyoo - A portable and vast media library solution.
|
||||
// Copyright (c) Kyoo.
|
||||
//
|
||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||
//
|
||||
// Kyoo is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// Kyoo is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
|
||||
namespace Kyoo.Abstractions.Models.Attributes
|
||||
{
|
||||
/// <summary>
|
||||
/// An attribute to inform how a <see cref="IFileSystem"/> works.
|
||||
/// </summary>
|
||||
[MetadataAttribute]
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class TaskMetadataAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// The slug of the task, used to start it.
|
||||
/// </summary>
|
||||
public string Slug { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the task that will be displayed to the user.
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A quick description of what this task will do.
|
||||
/// </summary>
|
||||
public string Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Should this task be automatically run at app startup?
|
||||
/// </summary>
|
||||
public bool RunOnStartup { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The priority of this task. Only used if <see cref="RunOnStartup"/> is true.
|
||||
/// It allow one to specify witch task will be started first as tasked are run on a Priority's descending order.
|
||||
/// </summary>
|
||||
public int Priority { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// <c>true</c> if this task should not be displayed to the user, <c>false</c> otherwise.
|
||||
/// </summary>
|
||||
public bool IsHidden { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="TaskMetadataAttribute"/> with the given slug, name and description.
|
||||
/// </summary>
|
||||
/// <param name="slug">The slug of the task, used to start it.</param>
|
||||
/// <param name="name">The name of the task that will be displayed to the user.</param>
|
||||
/// <param name="description">A quick description of what this task will do.</param>
|
||||
public TaskMetadataAttribute(string slug, string name, string description)
|
||||
{
|
||||
Slug = slug;
|
||||
Name = name;
|
||||
Description = description;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="TaskMetadataAttribute"/> using a dictionary of metadata.
|
||||
/// </summary>
|
||||
/// <param name="metadata">
|
||||
/// The dictionary of metadata. This method expect the dictionary to contain a field
|
||||
/// per property in this attribute, with the same types as the properties of this attribute.
|
||||
/// </param>
|
||||
public TaskMetadataAttribute(IDictionary<string, object> metadata)
|
||||
{
|
||||
Slug = (string)metadata[nameof(Slug)];
|
||||
Name = (string)metadata[nameof(Name)];
|
||||
Description = (string)metadata[nameof(Description)];
|
||||
RunOnStartup = (bool)metadata[nameof(RunOnStartup)];
|
||||
Priority = (int)metadata[nameof(Priority)];
|
||||
IsHidden = (bool)metadata[nameof(IsHidden)];
|
||||
}
|
||||
}
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
// Kyoo - A portable and vast media library solution.
|
||||
// Copyright (c) Kyoo.
|
||||
//
|
||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||
//
|
||||
// Kyoo is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// Kyoo is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
namespace Kyoo.Abstractions.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// A chapter to split an episode in multiple parts.
|
||||
/// </summary>
|
||||
public class Chapter
|
||||
{
|
||||
/// <summary>
|
||||
/// The start time of the chapter (in second from the start of the episode).
|
||||
/// </summary>
|
||||
public float StartTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The end time of the chapter (in second from the start of the episode).
|
||||
/// </summary>
|
||||
public float EndTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of this chapter. This should be a human-readable name that could be presented to the user.
|
||||
/// There should be well-known chapters name for commonly used chapters.
|
||||
/// For example, use "Opening" for the introduction-song and "Credits" for the end chapter with credits.
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="Chapter"/>.
|
||||
/// </summary>
|
||||
/// <param name="startTime">The start time of the chapter (in second)</param>
|
||||
/// <param name="endTime">The end time of the chapter (in second)</param>
|
||||
/// <param name="name">The name of this chapter</param>
|
||||
public Chapter(float startTime, float endTime, string name)
|
||||
{
|
||||
StartTime = startTime;
|
||||
EndTime = endTime;
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
// Kyoo - A portable and vast media library solution.
|
||||
// Copyright (c) Kyoo.
|
||||
//
|
||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||
//
|
||||
// Kyoo is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// Kyoo is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Kyoo.Abstractions.Models.Exceptions
|
||||
{
|
||||
/// <summary>
|
||||
/// An exception thrown when a part of the app has a fatal issue.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class HealthException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a new <see cref="HealthException"/> with a custom message.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to use.</param>
|
||||
public HealthException(string message)
|
||||
: base(message)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// The serialization constructor
|
||||
/// </summary>
|
||||
/// <param name="info">Serialization infos</param>
|
||||
/// <param name="context">The serialization context</param>
|
||||
protected HealthException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{ }
|
||||
}
|
||||
}
|
@ -1,72 +0,0 @@
|
||||
// Kyoo - A portable and vast media library solution.
|
||||
// Copyright (c) Kyoo.
|
||||
//
|
||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||
//
|
||||
// Kyoo is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// Kyoo is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using Kyoo.Abstractions.Models.Attributes;
|
||||
using Kyoo.Utils;
|
||||
using PathIO = System.IO.Path;
|
||||
|
||||
namespace Kyoo.Abstractions.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// A font of an <see cref="Episode"/>.
|
||||
/// </summary>
|
||||
public class Font : ILink
|
||||
{
|
||||
/// <summary>
|
||||
/// A human-readable identifier, used in the URL.
|
||||
/// </summary>
|
||||
public string Slug { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the font file (with the extension).
|
||||
/// </summary>
|
||||
public string File { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The format of this font (the extension).
|
||||
/// </summary>
|
||||
public string Format { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The path of the font.
|
||||
/// </summary>
|
||||
[SerializeIgnore] public string Path { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public object Link { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new empty <see cref="Font"/>.
|
||||
/// </summary>
|
||||
public Font() { }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="Font"/> from a path.
|
||||
/// </summary>
|
||||
/// <param name="path">The path of the font.</param>
|
||||
/// <param name="episodeSlug">The slug of the episode that contains this font.</param>
|
||||
public Font(string path, string episodeSlug)
|
||||
{
|
||||
Slug = Utility.ToSlug(PathIO.GetFileNameWithoutExtension(path));
|
||||
Path = path;
|
||||
File = PathIO.GetFileName(path);
|
||||
Format = PathIO.GetExtension(path).Replace(".", string.Empty);
|
||||
Link = $"/watch/{episodeSlug}/font/{Slug}.{Format}";
|
||||
}
|
||||
}
|
||||
}
|
@ -123,7 +123,7 @@ namespace Kyoo.Abstractions.Models
|
||||
public int? AbsoluteNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The path of the video file for this episode. Any format supported by a <see cref="IFileSystem"/> is allowed.
|
||||
/// The path of the video file for this episode.
|
||||
/// </summary>
|
||||
public string Path { get; set; }
|
||||
|
||||
@ -148,11 +148,6 @@ namespace Kyoo.Abstractions.Models
|
||||
/// <inheritdoc />
|
||||
[EditableRelation][LoadableRelation] public ICollection<MetadataID> ExternalIDs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of tracks this episode has. This lists video, audio and subtitles available.
|
||||
/// </summary>
|
||||
[EditableRelation][LoadableRelation] public ICollection<Track> Tracks { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the slug of an episode.
|
||||
/// </summary>
|
||||
|
@ -28,7 +28,6 @@ namespace Kyoo.Abstractions.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// The list of images mapped to a certain index.
|
||||
/// The string value should be a path supported by the <see cref="IFileSystem"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// An arbitrary index should not be used, instead use indexes from <see cref="Models.Images"/>
|
||||
|
@ -46,7 +46,6 @@ namespace Kyoo.Abstractions.Models
|
||||
|
||||
/// <summary>
|
||||
/// The path of the root directory of this show.
|
||||
/// This can be any kind of path supported by <see cref="IFileSystem"/>
|
||||
/// </summary>
|
||||
[SerializeIgnore] public string Path { get; set; }
|
||||
|
||||
@ -61,7 +60,7 @@ namespace Kyoo.Abstractions.Models
|
||||
public Status Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// An URL to a trailer. This could be any path supported by the <see cref="IFileSystem"/>.
|
||||
/// An URL to a trailer.
|
||||
/// </summary>
|
||||
/// TODO for now, this is set to a youtube url. It should be cached and converted to a local file.
|
||||
[Obsolete("Use Images instead of this, this is only kept for the API response.")]
|
||||
|
@ -1,229 +0,0 @@
|
||||
// Kyoo - A portable and vast media library solution.
|
||||
// Copyright (c) Kyoo.
|
||||
//
|
||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||
//
|
||||
// Kyoo is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// Kyoo is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using JetBrains.Annotations;
|
||||
using Kyoo.Abstractions.Models.Attributes;
|
||||
|
||||
namespace Kyoo.Abstractions.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
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of the stream is not known.
|
||||
/// </summary>
|
||||
Unknown = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The stream is a video.
|
||||
/// </summary>
|
||||
Video = 1,
|
||||
|
||||
/// <summary>
|
||||
/// The stream is an audio.
|
||||
/// </summary>
|
||||
Audio = 2,
|
||||
|
||||
/// <summary>
|
||||
/// The stream is a subtitle.
|
||||
/// </summary>
|
||||
Subtitle = 3,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A video, audio or subtitle track for an episode.
|
||||
/// </summary>
|
||||
public class Track : IResource, ILink
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public int ID { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[Computed]
|
||||
public string Slug
|
||||
{
|
||||
get
|
||||
{
|
||||
string type = Type.ToString().ToLowerInvariant();
|
||||
string index = TrackIndex != 0 ? $"-{TrackIndex}" : string.Empty;
|
||||
string episode = EpisodeSlug ?? Episode?.Slug ?? EpisodeID.ToString(CultureInfo.InvariantCulture);
|
||||
return $"{episode}.{Language ?? "und"}{index}{(IsForced ? ".forced" : string.Empty)}.{type}";
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
private set
|
||||
{
|
||||
if (value == null)
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
Match match = Regex.Match(value,
|
||||
@"(?<ep>[^\.]+)\.(?<lang>\w{0,3})(-(?<index>\d+))?(\.(?<forced>forced))?\.(?<type>\w+)(\.\w*)?");
|
||||
|
||||
if (!match.Success)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
"Invalid track slug. " +
|
||||
"Format: {episodeSlug}.{language}[-{index}][.forced].{type}[.{extension}]"
|
||||
);
|
||||
}
|
||||
|
||||
EpisodeSlug = match.Groups["ep"].Value;
|
||||
Language = match.Groups["lang"].Value;
|
||||
if (Language == "und")
|
||||
Language = null;
|
||||
TrackIndex = match.Groups["index"].Success ? int.Parse(match.Groups["index"].Value, CultureInfo.InvariantCulture) : 0;
|
||||
IsForced = match.Groups["forced"].Success;
|
||||
Type = Enum.Parse<StreamType>(match.Groups["type"].Value, true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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; }
|
||||
|
||||
/// <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>
|
||||
/// The ID of the episode that uses this track.
|
||||
/// </summary>
|
||||
[SerializeIgnore] public int EpisodeID { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The episode that uses this track.
|
||||
/// </summary>
|
||||
[LoadableRelation(nameof(EpisodeID))]
|
||||
public Episode Episode
|
||||
{
|
||||
get => _episode;
|
||||
set
|
||||
{
|
||||
_episode = value;
|
||||
if (_episode != null)
|
||||
EpisodeSlug = _episode.Slug;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The slug of the episode that contain this track. If this is not set, this track is ill-formed.
|
||||
/// </summary>
|
||||
[SerializeIgnore] public string EpisodeSlug { private get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The episode that uses this track.
|
||||
/// This is the baking field of <see cref="Episode"/>.
|
||||
/// </summary>
|
||||
[SerializeIgnore] private Episode _episode;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public object Link => Type == StreamType.Subtitle ? $"/subtitle/{Slug}" : null;
|
||||
|
||||
// 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",
|
||||
null => "und",
|
||||
_ => mkvLanguage
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Utility method to create a track slug from a incomplete slug (only add the type of the track).
|
||||
/// </summary>
|
||||
/// <param name="baseSlug">The slug to edit</param>
|
||||
/// <param name="type">The new type of this </param>
|
||||
/// <returns>The completed slug.</returns>
|
||||
public static string BuildSlug(string baseSlug, StreamType type)
|
||||
{
|
||||
return baseSlug.EndsWith($".{type}", StringComparison.InvariantCultureIgnoreCase)
|
||||
? baseSlug
|
||||
: $"{baseSlug}.{type.ToString().ToLowerInvariant()}";
|
||||
}
|
||||
}
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
// Kyoo - A portable and vast media library solution.
|
||||
// Copyright (c) Kyoo.
|
||||
//
|
||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||
//
|
||||
// Kyoo is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// Kyoo is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
namespace Kyoo.Abstractions.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// A class wrapping a value that will be set after the completion of the task it is related to.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This class replace the use of an out parameter on a task since tasks and out can't be combined.
|
||||
/// </remarks>
|
||||
/// <typeparam name="T">The type of the value</typeparam>
|
||||
public class AsyncRef<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// The value that will be set before the completion of the task.
|
||||
/// </summary>
|
||||
public T Value { get; set; }
|
||||
}
|
||||
}
|
@ -19,12 +19,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
using Kyoo.Abstractions.Models.Attributes;
|
||||
using PathIO = System.IO.Path;
|
||||
|
||||
namespace Kyoo.Abstractions.Models
|
||||
{
|
||||
@ -85,11 +85,6 @@ namespace Kyoo.Abstractions.Models
|
||||
/// </summary>
|
||||
public DateTime? ReleaseDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The path of the video file for this episode. Any format supported by a <see cref="IFileSystem"/> is allowed.
|
||||
/// </summary>
|
||||
[SerializeIgnore] public string Path { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The episode that come before this one if you follow usual watch orders.
|
||||
/// If this is the first episode or this is a movie, it will be null.
|
||||
@ -111,35 +106,9 @@ namespace Kyoo.Abstractions.Models
|
||||
public Dictionary<int, string> Images { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The container of the video file of this episode.
|
||||
/// Common containers are mp4, mkv, avi and so on.
|
||||
/// The transcoder's info for this item. This include subtitles, fonts, chapters...
|
||||
/// </summary>
|
||||
public string Container { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The video track. See <see cref="Track"/> for more information.
|
||||
/// </summary>
|
||||
public Track Video { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of audio tracks. See <see cref="Track"/> for more information.
|
||||
/// </summary>
|
||||
public ICollection<Track> Audios { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of subtitles tracks. See <see cref="Track"/> for more information.
|
||||
/// </summary>
|
||||
public ICollection<Track> Subtitles { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of fonts that can be used to draw the subtitles.
|
||||
/// </summary>
|
||||
public ICollection<Font> Fonts { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of chapters. See <see cref="Chapter"/> for more information.
|
||||
/// </summary>
|
||||
public ICollection<Chapter> Chapters { get; set; }
|
||||
public object Info { get; set; }
|
||||
|
||||
[SerializeIgnore]
|
||||
private string _Type => IsMovie ? "movie" : "episode";
|
||||
@ -158,13 +127,11 @@ namespace Kyoo.Abstractions.Models
|
||||
/// <param name="library">
|
||||
/// A library manager to retrieve the next and previous episode and load the show and tracks of the episode.
|
||||
/// </param>
|
||||
/// <param name="fs">A file system used to retrieve chapters informations.</param>
|
||||
/// <param name="transcoder">The transcoder used to list fonts.</param>
|
||||
/// <param name="client">A http client to reach the transcoder.</param>
|
||||
/// <returns>A new WatchItem representing the given episode.</returns>
|
||||
public static async Task<WatchItem> FromEpisode(Episode ep, ILibraryManager library, IFileSystem fs, ITranscoder transcoder)
|
||||
public static async Task<WatchItem> FromEpisode(Episode ep, ILibraryManager library, HttpClient client)
|
||||
{
|
||||
await library.Load(ep, x => x.Show);
|
||||
await library.Load(ep, x => x.Tracks);
|
||||
|
||||
return new WatchItem
|
||||
{
|
||||
@ -178,13 +145,7 @@ namespace Kyoo.Abstractions.Models
|
||||
Title = ep.Title,
|
||||
Overview = ep.Overview,
|
||||
ReleaseDate = ep.ReleaseDate,
|
||||
Path = ep.Path,
|
||||
Images = ep.Show.Images,
|
||||
Container = PathIO.GetExtension(ep.Path).Replace(".", string.Empty),
|
||||
Video = ep.Tracks.FirstOrDefault(x => x.Type == StreamType.Video),
|
||||
Audios = ep.Tracks.Where(x => x.Type == StreamType.Audio).ToArray(),
|
||||
Subtitles = ep.Tracks.Where(x => x.Type == StreamType.Subtitle).ToArray(),
|
||||
Fonts = await transcoder.ListFonts(ep),
|
||||
PreviousEpisode = ep.Show.IsMovie
|
||||
? null
|
||||
: (await library.GetAll<Episode>(
|
||||
@ -197,50 +158,16 @@ namespace Kyoo.Abstractions.Models
|
||||
where: x => x.ShowID == ep.ShowID,
|
||||
limit: new Pagination(1, ep.ID)
|
||||
)).FirstOrDefault(),
|
||||
Chapters = await _GetChapters(ep, fs),
|
||||
IsMovie = ep.Show.IsMovie
|
||||
IsMovie = ep.Show.IsMovie,
|
||||
Info = await _GetInfo(ep, client),
|
||||
};
|
||||
}
|
||||
|
||||
// TODO move this method in a controller to support abstraction.
|
||||
private static async Task<ICollection<Chapter>> _GetChapters(Episode episode, IFileSystem fs)
|
||||
private static async Task<object> _GetInfo(Episode ep, HttpClient client)
|
||||
{
|
||||
string path = fs.Combine(
|
||||
await fs.GetExtraDirectory(episode),
|
||||
"Chapters",
|
||||
PathIO.GetFileNameWithoutExtension(episode.Path) + ".txt"
|
||||
return await client.GetFromJsonAsync<object>(
|
||||
$"http://transcoder:7666/info/{(ep.Show.IsMovie ? "movie" : "episode")}/${ep.Slug}/info"
|
||||
);
|
||||
if (!await fs.Exists(path))
|
||||
return Array.Empty<Chapter>();
|
||||
try
|
||||
{
|
||||
using StreamReader sr = new(await fs.GetReader(path));
|
||||
string chapters = await sr.ReadToEndAsync();
|
||||
return chapters.Split('\n')
|
||||
.Select(x =>
|
||||
{
|
||||
string[] values = x.Split(' ');
|
||||
if (
|
||||
values.Length < 3
|
||||
|| !float.TryParse(values[0], out float start)
|
||||
|| !float.TryParse(values[1], out float end)
|
||||
)
|
||||
return null;
|
||||
return new Chapter(
|
||||
start,
|
||||
end,
|
||||
string.Join(' ', values.Skip(2))
|
||||
);
|
||||
})
|
||||
.Where(x => x != null)
|
||||
.ToArray();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await Console.Error.WriteLineAsync($"Invalid chapter file at {path}");
|
||||
Console.Error.WriteLine(ex.ToString());
|
||||
return Array.Empty<Chapter>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -1,168 +0,0 @@
|
||||
// Kyoo - A portable and vast media library solution.
|
||||
// Copyright (c) Kyoo.
|
||||
//
|
||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||
//
|
||||
// Kyoo is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// Kyoo is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
using Kyoo.Abstractions.Models;
|
||||
using Kyoo.Abstractions.Models.Attributes;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Kyoo.Core.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="IFileSystem"/> for http/https links.
|
||||
/// </summary>
|
||||
[FileSystemMetadata(new[] { "http", "https" })]
|
||||
public class HttpFileSystem : IFileSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// The http client factory used to create clients.
|
||||
/// </summary>
|
||||
private readonly IHttpClientFactory _clientFactory;
|
||||
|
||||
/// <summary>
|
||||
/// Create a <see cref="HttpFileSystem"/> using the given client factory.
|
||||
/// </summary>
|
||||
/// <param name="factory">The http client factory used to create clients.</param>
|
||||
public HttpFileSystem(IHttpClientFactory factory)
|
||||
{
|
||||
_clientFactory = factory;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IActionResult FileResult(string path, bool rangeSupport = false, string type = null)
|
||||
{
|
||||
if (path == null)
|
||||
return new NotFoundResult();
|
||||
return new HttpForwardResult(new Uri(path), rangeSupport, type);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<Stream> GetReader(string path)
|
||||
{
|
||||
HttpClient client = _clientFactory.CreateClient();
|
||||
return client.GetStreamAsync(path);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<Stream> GetReader(string path, AsyncRef<string> mime)
|
||||
{
|
||||
HttpClient client = _clientFactory.CreateClient();
|
||||
HttpResponseMessage response = await client.GetAsync(path);
|
||||
response.EnsureSuccessStatusCode();
|
||||
mime.Value = response.Content.Headers.ContentType?.MediaType;
|
||||
return await response.Content.ReadAsStreamAsync();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<Stream> NewFile(string path)
|
||||
{
|
||||
throw new NotSupportedException("An http filesystem is readonly, a new file can't be created.");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<string> CreateDirectory(string path)
|
||||
{
|
||||
throw new NotSupportedException("An http filesystem is readonly, a directory can't be created.");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Combine(params string[] paths)
|
||||
{
|
||||
return Path.Combine(paths);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<ICollection<string>> ListFiles(string path, SearchOption options = SearchOption.TopDirectoryOnly)
|
||||
{
|
||||
throw new NotSupportedException("Listing files is not supported on an http filesystem.");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<bool> Exists(string path)
|
||||
{
|
||||
throw new NotSupportedException("Checking if a file exists is not supported on an http filesystem.");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<string> GetExtraDirectory<T>(T resource)
|
||||
{
|
||||
throw new NotSupportedException("Extras can not be stored inside an http filesystem.");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<ICollection<Track>> ExtractInfos(Episode episode, bool reExtract)
|
||||
{
|
||||
throw new NotSupportedException("Extracting infos is not supported on an http filesystem.");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IActionResult Transmux(Episode episode)
|
||||
{
|
||||
throw new NotSupportedException("Transmuxing is not supported on an http filesystem.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An <see cref="IActionResult"/> to proxy an http request.
|
||||
/// </summary>
|
||||
// TODO remove this suppress message once the class has been implemented.
|
||||
[SuppressMessage("ReSharper", "NotAccessedField.Local", Justification = "Not Implemented Yet.")]
|
||||
public class HttpForwardResult : IActionResult
|
||||
{
|
||||
/// <summary>
|
||||
/// The path of the request to forward.
|
||||
/// </summary>
|
||||
private readonly Uri _path;
|
||||
|
||||
/// <summary>
|
||||
/// Should the proxied result support ranges requests?
|
||||
/// </summary>
|
||||
private readonly bool _rangeSupport;
|
||||
|
||||
/// <summary>
|
||||
/// If not null, override the content type of the resulting request.
|
||||
/// </summary>
|
||||
private readonly string _type;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="HttpForwardResult"/>.
|
||||
/// </summary>
|
||||
/// <param name="path">The path of the request to forward.</param>
|
||||
/// <param name="rangeSupport">Should the proxied result support ranges requests?</param>
|
||||
/// <param name="type">If not null, override the content type of the resulting request.</param>
|
||||
public HttpForwardResult(Uri path, bool rangeSupport, string type = null)
|
||||
{
|
||||
_path = path;
|
||||
_rangeSupport = rangeSupport;
|
||||
_type = type;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task ExecuteResultAsync(ActionContext context)
|
||||
{
|
||||
// TODO implement that, example: https://github.com/twitchax/AspNetCore.Proxy/blob/14dd0f212d7abb43ca1bf8c890d5efb95db66acb/src/Core/Extensions/Http.cs#L15
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,176 +0,0 @@
|
||||
// Kyoo - A portable and vast media library solution.
|
||||
// Copyright (c) Kyoo.
|
||||
//
|
||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||
//
|
||||
// Kyoo is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// Kyoo is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
using Kyoo.Abstractions.Models;
|
||||
using Kyoo.Abstractions.Models.Attributes;
|
||||
using Kyoo.Core.Models.Options;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.StaticFiles;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Kyoo.Core.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="IFileSystem"/> for the local filesystem (using System.IO).
|
||||
/// </summary>
|
||||
[FileSystemMetadata(new[] { "", "file" }, StripScheme = true)]
|
||||
public class LocalFileSystem : IFileSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// An extension provider to get content types from files extensions.
|
||||
/// </summary>
|
||||
private readonly IContentTypeProvider _provider;
|
||||
|
||||
/// <summary>
|
||||
/// The transcoder of local files.
|
||||
/// </summary>
|
||||
private readonly ITranscoder _transcoder;
|
||||
|
||||
/// <summary>
|
||||
/// Options to check if the metadata should be kept in the show directory or in a kyoo's directory.
|
||||
/// </summary>
|
||||
private readonly IOptionsMonitor<BasicOptions> _options;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="LocalFileSystem"/> with the specified options.
|
||||
/// </summary>
|
||||
/// <param name="options">The options to use.</param>
|
||||
/// <param name="provider">An extension provider to get content types from files extensions.</param>
|
||||
/// <param name="transcoder">The transcoder of local files.</param>
|
||||
public LocalFileSystem(IOptionsMonitor<BasicOptions> options,
|
||||
IContentTypeProvider provider,
|
||||
ITranscoder transcoder)
|
||||
{
|
||||
_options = options;
|
||||
_provider = provider;
|
||||
_transcoder = transcoder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the content type of a file using it's extension.
|
||||
/// </summary>
|
||||
/// <param name="path">The path of the file</param>
|
||||
/// <exception cref="NotImplementedException">The extension of the file is not known.</exception>
|
||||
/// <returns>The content type of the file</returns>
|
||||
private string _GetContentType(string path)
|
||||
{
|
||||
if (_provider.TryGetContentType(path, out string contentType))
|
||||
return contentType;
|
||||
throw new NotImplementedException($"Can't get the content type of the file at: {path}");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IActionResult FileResult(string path, bool rangeSupport = false, string type = null)
|
||||
{
|
||||
if (path == null)
|
||||
return new NotFoundResult();
|
||||
if (!File.Exists(path))
|
||||
return new NotFoundResult();
|
||||
return new PhysicalFileResult(Path.GetFullPath(path), type ?? _GetContentType(path))
|
||||
{
|
||||
EnableRangeProcessing = rangeSupport
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<Stream> GetReader(string path)
|
||||
{
|
||||
if (path == null)
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
return Task.FromResult<Stream>(File.OpenRead(path));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<Stream> GetReader(string path, AsyncRef<string> mime)
|
||||
{
|
||||
if (path == null)
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
_provider.TryGetContentType(path, out string mimeValue);
|
||||
mime.Value = mimeValue;
|
||||
return Task.FromResult<Stream>(File.OpenRead(path));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<Stream> NewFile(string path)
|
||||
{
|
||||
if (path == null)
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
return Task.FromResult<Stream>(File.Create(path));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<string> CreateDirectory(string path)
|
||||
{
|
||||
if (path == null)
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
Directory.CreateDirectory(path);
|
||||
return Task.FromResult(path);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Combine(params string[] paths)
|
||||
{
|
||||
return Path.Combine(paths);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<ICollection<string>> ListFiles(string path, SearchOption options = SearchOption.TopDirectoryOnly)
|
||||
{
|
||||
if (path == null)
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
string[] ret = Directory.Exists(path)
|
||||
? Directory.GetFiles(path, "*", options)
|
||||
: Array.Empty<string>();
|
||||
return Task.FromResult<ICollection<string>>(ret);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<bool> Exists(string path)
|
||||
{
|
||||
return Task.FromResult(File.Exists(path) || Directory.Exists(path));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<string> GetExtraDirectory<T>(T resource)
|
||||
{
|
||||
string path = resource switch
|
||||
{
|
||||
IResource res => Combine(_options.CurrentValue.MetadataPath, typeof(T).Name.ToLower(), res.Slug),
|
||||
_ => Combine(_options.CurrentValue.MetadataPath, typeof(T).Name.ToLower())
|
||||
};
|
||||
return CreateDirectory(path);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<ICollection<Track>> ExtractInfos(Episode episode, bool reExtract)
|
||||
{
|
||||
return _transcoder.ExtractInfos(episode, reExtract);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IActionResult Transmux(Episode episode)
|
||||
{
|
||||
return _transcoder.Transmux(episode);
|
||||
}
|
||||
}
|
||||
}
|
@ -57,9 +57,6 @@ namespace Kyoo.Core.Controllers
|
||||
/// <inheritdoc />
|
||||
public IEpisodeRepository EpisodeRepository { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public ITrackRepository TrackRepository { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IPeopleRepository PeopleRepository { get; }
|
||||
|
||||
@ -89,7 +86,6 @@ namespace Kyoo.Core.Controllers
|
||||
ShowRepository = GetRepository<Show>() as IShowRepository;
|
||||
SeasonRepository = GetRepository<Season>() as ISeasonRepository;
|
||||
EpisodeRepository = GetRepository<Episode>() as IEpisodeRepository;
|
||||
TrackRepository = GetRepository<Track>() as ITrackRepository;
|
||||
PeopleRepository = GetRepository<People>() as IPeopleRepository;
|
||||
StudioRepository = GetRepository<Studio>() as IStudioRepository;
|
||||
GenreRepository = GetRepository<Genre>() as IGenreRepository;
|
||||
@ -354,11 +350,6 @@ namespace Kyoo.Core.Controllers
|
||||
(x, y) => x.ExternalIDs = y,
|
||||
(x, y) => { x.ResourceID = y.ID; }),
|
||||
|
||||
(Episode e, nameof(Episode.Tracks)) => _SetRelation(e,
|
||||
TrackRepository.GetAll(x => x.Episode.ID == obj.ID),
|
||||
(x, y) => x.Tracks = y,
|
||||
(x, y) => { x.Episode = y; x.EpisodeID = y.ID; }),
|
||||
|
||||
(Episode e, nameof(Episode.Show)) => ShowRepository
|
||||
.GetOrDefault(x => x.Episodes.Any(y => y.ID == obj.ID))
|
||||
.Then(x =>
|
||||
@ -376,15 +367,6 @@ namespace Kyoo.Core.Controllers
|
||||
}),
|
||||
|
||||
|
||||
(Track t, nameof(Track.Episode)) => EpisodeRepository
|
||||
.GetOrDefault(x => x.Tracks.Any(y => y.ID == obj.ID))
|
||||
.Then(x =>
|
||||
{
|
||||
t.Episode = x;
|
||||
t.EpisodeID = x?.ID ?? 0;
|
||||
}),
|
||||
|
||||
|
||||
(Genre g, nameof(Genre.Shows)) => ShowRepository
|
||||
.GetAll(x => x.Genres.Any(y => y.ID == obj.ID))
|
||||
.Then(x => g.Shows = x),
|
||||
|
@ -44,11 +44,6 @@ namespace Kyoo.Core.Controllers
|
||||
/// </summary>
|
||||
private readonly IProviderRepository _providers;
|
||||
|
||||
/// <summary>
|
||||
/// A track repository to handle creation and deletion of tracks related to the current episode.
|
||||
/// </summary>
|
||||
private readonly Lazy<ITrackRepository> _tracks;
|
||||
|
||||
/// <inheritdoc />
|
||||
// Use absolute numbers by default and fallback to season/episodes if it does not exists.
|
||||
protected override Sort<Episode> DefaultSort => new Sort<Episode>.Conglomerate(
|
||||
@ -63,16 +58,13 @@ namespace Kyoo.Core.Controllers
|
||||
/// <param name="database">The database handle to use.</param>
|
||||
/// <param name="shows">A show repository</param>
|
||||
/// <param name="providers">A provider repository</param>
|
||||
/// <param name="tracks">A track repository</param>
|
||||
public EpisodeRepository(DatabaseContext database,
|
||||
IShowRepository shows,
|
||||
IProviderRepository providers,
|
||||
Lazy<ITrackRepository> tracks)
|
||||
IProviderRepository providers)
|
||||
: base(database)
|
||||
{
|
||||
_database = database;
|
||||
_providers = providers;
|
||||
_tracks = tracks;
|
||||
|
||||
// Edit episode slugs when the show's slug changes.
|
||||
shows.OnEdited += (show) =>
|
||||
@ -162,7 +154,7 @@ namespace Kyoo.Core.Controllers
|
||||
? Get(obj.ShowID, obj.SeasonNumber.Value, obj.EpisodeNumber.Value)
|
||||
: GetAbsolute(obj.ShowID, obj.AbsoluteNumber.Value));
|
||||
OnResourceCreated(obj);
|
||||
return await _ValidateTracks(obj);
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@ -170,13 +162,6 @@ namespace Kyoo.Core.Controllers
|
||||
{
|
||||
await Validate(changed);
|
||||
|
||||
if (changed.Tracks != null || resetOld)
|
||||
{
|
||||
await _tracks.Value.DeleteAll(x => x.EpisodeID == resource.ID);
|
||||
resource.Tracks = changed.Tracks;
|
||||
await _ValidateTracks(resource);
|
||||
}
|
||||
|
||||
if (changed.ExternalIDs != null || resetOld)
|
||||
{
|
||||
await Database.Entry(resource).Collection(x => x.ExternalIDs).LoadAsync();
|
||||
@ -184,25 +169,6 @@ namespace Kyoo.Core.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set track's index and ensure that every tracks is well-formed.
|
||||
/// </summary>
|
||||
/// <param name="resource">The resource to fix.</param>
|
||||
/// <returns>The <paramref name="resource"/> parameter is returned.</returns>
|
||||
private async Task<Episode> _ValidateTracks(Episode resource)
|
||||
{
|
||||
if (resource.Tracks == null)
|
||||
return resource;
|
||||
|
||||
resource.Tracks = await resource.Tracks.SelectAsync(x =>
|
||||
{
|
||||
x.Episode = resource;
|
||||
return _tracks.Value.Create(x);
|
||||
}).ToListAsync();
|
||||
_database.Tracks.AttachRange(resource.Tracks);
|
||||
return resource;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task Validate(Episode resource)
|
||||
{
|
||||
@ -236,7 +202,6 @@ namespace Kyoo.Core.Controllers
|
||||
throw new ArgumentNullException(nameof(obj));
|
||||
|
||||
_database.Entry(obj).State = EntityState.Deleted;
|
||||
await obj.Tracks.ForEachAsync(x => _tracks.Value.Delete(x));
|
||||
obj.ExternalIDs.ForEach(x => _database.Entry(x).State = EntityState.Deleted);
|
||||
await _database.SaveChangesAsync();
|
||||
await base.Delete(obj);
|
||||
|
@ -1,112 +0,0 @@
|
||||
// Kyoo - A portable and vast media library solution.
|
||||
// Copyright (c) Kyoo.
|
||||
//
|
||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||
//
|
||||
// Kyoo is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// Kyoo is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
using Kyoo.Abstractions.Models;
|
||||
using Kyoo.Postgresql;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Kyoo.Core.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// A local repository to handle tracks.
|
||||
/// </summary>
|
||||
public class TrackRepository : LocalRepository<Track>, ITrackRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// The database handle
|
||||
/// </summary>
|
||||
private readonly DatabaseContext _database;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Sort<Track> DefaultSort => new Sort<Track>.By(x => x.TrackIndex);
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="TrackRepository"/>.
|
||||
/// </summary>
|
||||
/// <param name="database">The database handle</param>
|
||||
/// <param name="episodes">The episode repository</param>
|
||||
public TrackRepository(DatabaseContext database, IEpisodeRepository episodes)
|
||||
: base(database)
|
||||
{
|
||||
_database = database;
|
||||
|
||||
// Edit tracks slugs when the episodes's slug changes.
|
||||
episodes.OnEdited += (ep) =>
|
||||
{
|
||||
List<Track> tracks = _database.Tracks.AsTracking().Where(x => x.EpisodeID == ep.ID).ToList();
|
||||
foreach (Track track in tracks)
|
||||
{
|
||||
track.EpisodeSlug = ep.Slug;
|
||||
_database.SaveChanges();
|
||||
OnResourceEdited(track);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<ICollection<Track>> Search(string query)
|
||||
{
|
||||
throw new InvalidOperationException("Tracks do not support the search method.");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task Validate(Track resource)
|
||||
{
|
||||
await base.Validate(resource);
|
||||
if (resource.EpisodeID <= 0)
|
||||
{
|
||||
resource.EpisodeID = resource.Episode?.ID ?? 0;
|
||||
if (resource.EpisodeID <= 0)
|
||||
{
|
||||
throw new ArgumentException("Can't store a track not related to any episode " +
|
||||
$"(episodeID: {resource.EpisodeID}).");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task<Track> Create(Track obj)
|
||||
{
|
||||
if (obj == null)
|
||||
throw new ArgumentNullException(nameof(obj));
|
||||
|
||||
await base.Create(obj);
|
||||
obj.EpisodeSlug = _database.Episodes.First(x => x.ID == obj.EpisodeID).Slug;
|
||||
_database.Entry(obj).State = EntityState.Added;
|
||||
await _database.SaveChangesAsync();
|
||||
OnResourceCreated(obj);
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task Delete(Track obj)
|
||||
{
|
||||
if (obj == null)
|
||||
throw new ArgumentNullException(nameof(obj));
|
||||
|
||||
_database.Entry(obj).State = EntityState.Deleted;
|
||||
await _database.SaveChangesAsync();
|
||||
await base.Delete(obj);
|
||||
}
|
||||
}
|
||||
}
|
@ -19,13 +19,15 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
using Kyoo.Abstractions.Models;
|
||||
using Microsoft.AspNetCore.StaticFiles;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Kyoo.Core.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
@ -33,30 +35,27 @@ namespace Kyoo.Core.Controllers
|
||||
/// </summary>
|
||||
public class ThumbnailsManager : IThumbnailsManager
|
||||
{
|
||||
/// <summary>
|
||||
/// The file manager used to download the image if the file is distant
|
||||
/// </summary>
|
||||
private readonly IFileSystem _files;
|
||||
|
||||
/// <summary>
|
||||
/// A logger to report errors.
|
||||
/// </summary>
|
||||
private readonly ILogger<ThumbnailsManager> _logger;
|
||||
|
||||
private readonly IHttpClientFactory _clientFactory;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="ThumbnailsManager"/>.
|
||||
/// </summary>
|
||||
/// <param name="files">The file manager to use.</param>
|
||||
/// <param name="clientFactory">Client factory</param>
|
||||
/// <param name="logger">A logger to report errors</param>
|
||||
public ThumbnailsManager(IFileSystem files,
|
||||
public ThumbnailsManager(IHttpClientFactory clientFactory,
|
||||
ILogger<ThumbnailsManager> logger)
|
||||
{
|
||||
_files = files;
|
||||
_clientFactory = clientFactory;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An helper function to download an image using a <see cref="LocalFileSystem"/>.
|
||||
/// An helper function to download an image.
|
||||
/// </summary>
|
||||
/// <param name="url">The distant url of the image</param>
|
||||
/// <param name="localPath">The local path of the image</param>
|
||||
@ -70,12 +69,17 @@ namespace Kyoo.Core.Controllers
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Downloading image {What}", what);
|
||||
AsyncRef<string> mime = new();
|
||||
await using Stream reader = await _files.GetReader(url, mime);
|
||||
|
||||
HttpClient client = _clientFactory.CreateClient();
|
||||
HttpResponseMessage response = await client.GetAsync(url);
|
||||
response.EnsureSuccessStatusCode();
|
||||
string mime = response.Content.Headers.ContentType?.MediaType!;
|
||||
await using Stream reader = await response.Content.ReadAsStreamAsync();
|
||||
|
||||
string extension = new FileExtensionContentTypeProvider()
|
||||
.Mappings.FirstOrDefault(x => x.Value == mime.Value)
|
||||
.Mappings.FirstOrDefault(x => x.Value == mime)
|
||||
.Key;
|
||||
await using Stream local = await _files.NewFile(localPath + extension);
|
||||
await using Stream local = File.Create(localPath + extension);
|
||||
await reader.CopyToAsync(local);
|
||||
return true;
|
||||
}
|
||||
@ -101,8 +105,8 @@ namespace Kyoo.Core.Controllers
|
||||
|
||||
foreach ((int id, string image) in item.Images.Where(x => x.Value != null))
|
||||
{
|
||||
string localPath = await _GetPrivateImagePath(item, id);
|
||||
if (alwaysDownload || !await _files.Exists(localPath))
|
||||
string localPath = _GetPrivateImagePath(item, id);
|
||||
if (alwaysDownload || !Path.Exists(localPath))
|
||||
ret |= await _DownloadImage(image, localPath, $"The image n {id} of {name}");
|
||||
}
|
||||
|
||||
@ -113,34 +117,41 @@ namespace Kyoo.Core.Controllers
|
||||
/// Retrieve the local path of an image of the given item <b>without an extension</b>.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to retrieve the poster from.</param>
|
||||
/// <param name="imageID">The ID of the image. See <see cref="Images"/> for values.</param>
|
||||
/// <param name="imageId">The ID of the image. See <see cref="Images"/> for values.</param>
|
||||
/// <typeparam name="T">The type of the item</typeparam>
|
||||
/// <returns>The path of the image for the given resource, <b>even if it does not exists</b></returns>
|
||||
private async Task<string> _GetPrivateImagePath<T>([NotNull] T item, int imageID)
|
||||
private static string _GetPrivateImagePath<T>(T item, int imageId)
|
||||
{
|
||||
if (item == null)
|
||||
throw new ArgumentNullException(nameof(item));
|
||||
|
||||
string directory = await _files.GetExtraDirectory(item);
|
||||
string imageName = imageID switch
|
||||
string directory = item switch
|
||||
{
|
||||
IResource res => Path.Combine("/metadata", typeof(T).Name.ToLowerInvariant(), res.Slug),
|
||||
_ => Path.Combine("/metadata", typeof(T).Name.ToLowerInvariant())
|
||||
};
|
||||
Directory.CreateDirectory(directory);
|
||||
string imageName = imageId switch
|
||||
{
|
||||
Images.Poster => "poster",
|
||||
Images.Logo => "logo",
|
||||
Images.Thumbnail => "thumbnail",
|
||||
Images.Trailer => "trailer",
|
||||
_ => $"{imageID}"
|
||||
_ => $"{imageId}"
|
||||
};
|
||||
return _files.Combine(directory, imageName);
|
||||
return Path.Combine(directory, imageName);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<string> GetImagePath<T>(T item, int imageID)
|
||||
public string? GetImagePath<T>(T item, int imageId)
|
||||
where T : IThumbnails
|
||||
{
|
||||
string basePath = await _GetPrivateImagePath(item, imageID);
|
||||
string directory = Path.GetDirectoryName(basePath);
|
||||
string basePath = _GetPrivateImagePath(item, imageId);
|
||||
string directory = Path.GetDirectoryName(basePath)!;
|
||||
string baseFile = Path.GetFileName(basePath);
|
||||
return (await _files.ListFiles(directory!))
|
||||
if (!Directory.Exists(directory))
|
||||
return null;
|
||||
return Directory.GetFiles(directory, "*", SearchOption.TopDirectoryOnly)
|
||||
.FirstOrDefault(x => Path.GetFileNameWithoutExtension(x) == baseFile);
|
||||
}
|
||||
}
|
||||
|
@ -1,342 +0,0 @@
|
||||
// Kyoo - A portable and vast media library solution.
|
||||
// Copyright (c) Kyoo.
|
||||
//
|
||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||
//
|
||||
// Kyoo is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// Kyoo is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
using Kyoo.Abstractions.Models;
|
||||
using Kyoo.Abstractions.Models.Exceptions;
|
||||
using Kyoo.Core.Models.Options;
|
||||
using Kyoo.Core.Models.Watch;
|
||||
using Kyoo.Utils;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Kyoo.Core.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// The transcoder used by the <see cref="LocalFileSystem"/>.
|
||||
/// </summary>
|
||||
public class Transcoder : ITranscoder
|
||||
{
|
||||
#pragma warning disable IDE1006
|
||||
/// <summary>
|
||||
/// The class that interact with the transcoder written in C.
|
||||
/// </summary>
|
||||
private static class TranscoderAPI
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the library. For windows '.dll' should be appended, on linux or macos it should be prefixed
|
||||
/// by 'lib' and '.so' or '.dylib' should be appended.
|
||||
/// </summary>
|
||||
private const string TranscoderPath = "transcoder";
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the C library, setup the logger and return the size of a <see cref="FTrack"/>.
|
||||
/// </summary>
|
||||
/// <returns>The size of a <see cref="FTrack"/></returns>
|
||||
[DllImport(TranscoderPath, CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern int init();
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the C library, setup the logger and return the size of a <see cref="FTrack"/>.
|
||||
/// </summary>
|
||||
/// <returns>The size of a <see cref="FTrack"/></returns>
|
||||
public static int Init() => init();
|
||||
|
||||
/// <summary>
|
||||
/// Transmux the file at the specified path. The path must be a local one with '/' as a separator.
|
||||
/// </summary>
|
||||
/// <param name="path">The path of a local file with '/' as a separators.</param>
|
||||
/// <param name="outPath">The path of the hls output file.</param>
|
||||
/// <param name="playableDuration">
|
||||
/// The number of seconds currently playable. This is incremented as the file gets transmuxed.
|
||||
/// </param>
|
||||
/// <returns><c>0</c> on success, non 0 on failure.</returns>
|
||||
[DllImport(TranscoderPath, CallingConvention = CallingConvention.Cdecl,
|
||||
CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true)]
|
||||
private static extern int transmux(string path, string outPath, out float playableDuration);
|
||||
|
||||
/// <summary>
|
||||
/// Transmux the file at the specified path. The path must be a local one.
|
||||
/// </summary>
|
||||
/// <param name="path">The path of a local file.</param>
|
||||
/// <param name="outPath">The path of the hls output file.</param>
|
||||
/// <param name="playableDuration">
|
||||
/// The number of seconds currently playable. This is incremented as the file gets transmuxed.
|
||||
/// </param>
|
||||
/// <returns><c>0</c> on success, non 0 on failure.</returns>
|
||||
public static int Transmux(string path, string outPath, out float playableDuration)
|
||||
{
|
||||
path = path.Replace('\\', '/');
|
||||
outPath = outPath.Replace('\\', '/');
|
||||
return transmux(path, outPath, out playableDuration);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve tracks from a video file and extract subtitles, fonts and chapters to an external file.
|
||||
/// </summary>
|
||||
/// <param name="path">
|
||||
/// The path of the video file to analyse. This must be a local path with '/' as a separator.
|
||||
/// </param>
|
||||
/// <param name="outPath">The directory that will be used to store extracted files.</param>
|
||||
/// <param name="length">The size of the returned array.</param>
|
||||
/// <param name="trackCount">The number of tracks in the returned array.</param>
|
||||
/// <param name="reExtract">Should the cache be invalidated and information re-extracted or not?</param>
|
||||
/// <returns>A pointer to an array of <see cref="FTrack"/></returns>
|
||||
[DllImport(TranscoderPath, CallingConvention = CallingConvention.Cdecl,
|
||||
CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true)]
|
||||
private static extern IntPtr extract_infos(string path,
|
||||
string outPath,
|
||||
out uint length,
|
||||
out uint trackCount,
|
||||
bool reExtract);
|
||||
|
||||
/// <summary>
|
||||
/// An helper method to free an array of <see cref="FTrack"/>.
|
||||
/// </summary>
|
||||
/// <param name="streams">A pointer to the first element of the array</param>
|
||||
/// <param name="count">The number of items in the array.</param>
|
||||
[DllImport(TranscoderPath, CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern void free_streams(IntPtr streams, uint count);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve tracks from a video file and extract subtitles, fonts and chapters to an external file.
|
||||
/// </summary>
|
||||
/// <param name="path">The path of the video file to analyse. This must be a local path.</param>
|
||||
/// <param name="outPath">The directory that will be used to store extracted files.</param>
|
||||
/// <param name="reExtract">Should the cache be invalidated and information re-extracted or not?</param>
|
||||
/// <returns>An array of <see cref="Track"/>.</returns>
|
||||
public static Track[] ExtractInfos(string path, string outPath, bool reExtract)
|
||||
{
|
||||
path = path.Replace('\\', '/');
|
||||
outPath = outPath.Replace('\\', '/');
|
||||
|
||||
int size = Marshal.SizeOf<FTrack>();
|
||||
IntPtr ptr = extract_infos(path, outPath, out uint arrayLength, out uint trackCount, reExtract);
|
||||
IntPtr streamsPtr = ptr;
|
||||
Track[] tracks;
|
||||
|
||||
if (trackCount > 0 && ptr != IntPtr.Zero)
|
||||
{
|
||||
tracks = new Track[trackCount];
|
||||
|
||||
int j = 0;
|
||||
for (int i = 0; i < arrayLength; i++, streamsPtr += size)
|
||||
{
|
||||
FTrack stream = Marshal.PtrToStructure<FTrack>(streamsPtr);
|
||||
if (stream!.Type == FTrackType.Unknown || stream.Type == FTrackType.Attachment)
|
||||
continue;
|
||||
tracks[j] = stream.ToTrack();
|
||||
j++;
|
||||
}
|
||||
Array.Resize(ref tracks, j);
|
||||
}
|
||||
else
|
||||
tracks = Array.Empty<Track>();
|
||||
|
||||
if (ptr != IntPtr.Zero)
|
||||
free_streams(ptr, trackCount);
|
||||
return tracks;
|
||||
}
|
||||
}
|
||||
#pragma warning restore IDE1006
|
||||
|
||||
/// <summary>
|
||||
/// The file system used to retrieve the extra directory of shows to know where to extract information.
|
||||
/// </summary>
|
||||
private readonly IFileSystem _files;
|
||||
|
||||
/// <summary>
|
||||
/// Options to know where to cache transmuxed/transcoded episodes.
|
||||
/// </summary>
|
||||
private readonly IOptions<BasicOptions> _options;
|
||||
|
||||
/// <summary>
|
||||
/// The logger to use. This is also used by the wrapped C library.
|
||||
/// </summary>
|
||||
private readonly ILogger<Transcoder> _logger;
|
||||
|
||||
/// <summary>
|
||||
/// <see langword="true"/> if the C library has been checked, <see langword="false"/> otherwise.
|
||||
/// </summary>
|
||||
private bool _initialized;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="Transcoder"/>.
|
||||
/// </summary>
|
||||
/// <param name="files">
|
||||
/// The file system used to retrieve the extra directory of shows to know where to extract information.
|
||||
/// </param>
|
||||
/// <param name="options">Options to know where to cache transmuxed/transcoded episodes.</param>
|
||||
/// <param name="logger">The logger to use. This is also used by the wrapped C library.</param>
|
||||
public Transcoder(IFileSystem files, IOptions<BasicOptions> options, ILogger<Transcoder> logger)
|
||||
{
|
||||
_files = files;
|
||||
_options = options;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the C library can be used or if there is an issue with it.
|
||||
/// </summary>
|
||||
/// <param name="fastStop">Should the healthcheck be abborted if the transcoder was already initialized?</param>
|
||||
/// <exception cref="HealthException">If the transcoder is corrupted, this exception in thrown.</exception>
|
||||
public void CheckHealth(bool fastStop = false)
|
||||
{
|
||||
if (fastStop && _initialized)
|
||||
return;
|
||||
if (TranscoderAPI.Init() != Marshal.SizeOf<FTrack>())
|
||||
{
|
||||
_logger.LogCritical("The transcoder library could not be initialized correctly");
|
||||
throw new HealthException("The transcoder library is corrupted or invalid.");
|
||||
}
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<ICollection<Track>> ExtractInfos(Episode episode, bool reExtract)
|
||||
{
|
||||
CheckHealth(true);
|
||||
|
||||
string dir = await _files.GetExtraDirectory(episode);
|
||||
if (dir == null)
|
||||
throw new ArgumentException("Invalid path.");
|
||||
return await Task.Factory.StartNew(
|
||||
() => TranscoderAPI.ExtractInfos(episode.Path, dir, reExtract),
|
||||
TaskCreationOptions.LongRunning
|
||||
);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<ICollection<Font>> ListFonts(Episode episode)
|
||||
{
|
||||
string path = _files.Combine(await _files.GetExtraDirectory(episode), "Attachments");
|
||||
return (await _files.ListFiles(path))
|
||||
.Select(x => new Font(x, episode.Slug))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<Font> GetFont(Episode episode, string slug)
|
||||
{
|
||||
string path = _files.Combine(await _files.GetExtraDirectory(episode), "Attachments");
|
||||
string font = (await _files.ListFiles(path))
|
||||
.FirstOrDefault(x => Utility.ToSlug(Path.GetFileNameWithoutExtension(x)) == slug);
|
||||
if (font == null)
|
||||
return null;
|
||||
return new Font(font, episode.Slug);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IActionResult Transmux(Episode episode)
|
||||
{
|
||||
CheckHealth(true);
|
||||
|
||||
string folder = Path.Combine(_options.Value.TransmuxPath, episode.Slug);
|
||||
string manifest = Path.GetFullPath(Path.Combine(folder, episode.Slug + ".m3u8"));
|
||||
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(folder);
|
||||
if (File.Exists(manifest))
|
||||
return new PhysicalFileResult(manifest, "application/x-mpegurl");
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
_logger.LogCritical("Access to the path {Manifest} is denied. " +
|
||||
"Please change your transmux path in the config", manifest);
|
||||
return new StatusCodeResult(500);
|
||||
}
|
||||
|
||||
return new TransmuxResult(episode.Path, manifest, _logger);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An action result that runs the transcoder and return the created manifest file after a few seconds of
|
||||
/// the video has been proceeded. If the transcoder fails, it returns a 500 error code.
|
||||
/// </summary>
|
||||
private class TransmuxResult : IActionResult
|
||||
{
|
||||
/// <summary>
|
||||
/// The path of the episode to transmux. It must be a local one.
|
||||
/// </summary>
|
||||
private readonly string _path;
|
||||
|
||||
/// <summary>
|
||||
/// The path of the manifest file to create. It must be a local one.
|
||||
/// </summary>
|
||||
private readonly string _manifest;
|
||||
|
||||
/// <summary>
|
||||
/// The logger to use in case of issue.
|
||||
/// </summary>
|
||||
private readonly ILogger _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="TransmuxResult"/>.
|
||||
/// </summary>
|
||||
/// <param name="path">The path of the episode to transmux. It must be a local one.</param>
|
||||
/// <param name="manifest">The path of the manifest file to create. It must be a local one.</param>
|
||||
/// <param name="logger">The logger to use in case of issue.</param>
|
||||
public TransmuxResult(string path, string manifest, ILogger logger)
|
||||
{
|
||||
_path = path;
|
||||
_manifest = Path.GetFullPath(manifest);
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
// We use threads so tasks are not always awaited.
|
||||
#pragma warning disable 4014
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task ExecuteResultAsync(ActionContext context)
|
||||
{
|
||||
float playableDuration = 0;
|
||||
bool transmuxFailed = false;
|
||||
|
||||
Task.Factory.StartNew(() =>
|
||||
{
|
||||
transmuxFailed = TranscoderAPI.Transmux(_path, _manifest, out playableDuration) != 0;
|
||||
}, TaskCreationOptions.LongRunning);
|
||||
|
||||
while (playableDuration < 10 || (!File.Exists(_manifest) && !transmuxFailed))
|
||||
await Task.Delay(10);
|
||||
|
||||
if (!transmuxFailed)
|
||||
{
|
||||
new PhysicalFileResult(_manifest, "application/x-mpegurl")
|
||||
.ExecuteResultAsync(context);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogCritical("The transmuxing failed on the C library");
|
||||
new StatusCodeResult(500)
|
||||
.ExecuteResultAsync(context);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning restore 4014
|
||||
}
|
||||
}
|
||||
}
|
@ -27,7 +27,6 @@ using Kyoo.Core.Controllers;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.StaticFiles;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Newtonsoft.Json;
|
||||
@ -46,10 +45,6 @@ namespace Kyoo.Core
|
||||
/// <inheritdoc />
|
||||
public void Configure(ContainerBuilder builder)
|
||||
{
|
||||
builder.RegisterType<LocalFileSystem>().As<IFileSystem>().SingleInstance();
|
||||
builder.RegisterType<HttpFileSystem>().As<IFileSystem>().SingleInstance();
|
||||
|
||||
builder.RegisterType<Transcoder>().As<ITranscoder>().SingleInstance();
|
||||
builder.RegisterType<ThumbnailsManager>().As<IThumbnailsManager>().InstancePerLifetimeScope();
|
||||
builder.RegisterType<LibraryManager>().As<ILibraryManager>().InstancePerLifetimeScope();
|
||||
|
||||
@ -59,22 +54,11 @@ namespace Kyoo.Core
|
||||
builder.RegisterRepository<IShowRepository, ShowRepository>();
|
||||
builder.RegisterRepository<ISeasonRepository, SeasonRepository>();
|
||||
builder.RegisterRepository<IEpisodeRepository, EpisodeRepository>();
|
||||
builder.RegisterRepository<ITrackRepository, TrackRepository>();
|
||||
builder.RegisterRepository<IPeopleRepository, PeopleRepository>();
|
||||
builder.RegisterRepository<IStudioRepository, StudioRepository>();
|
||||
builder.RegisterRepository<IGenreRepository, GenreRepository>();
|
||||
builder.RegisterRepository<IProviderRepository, ProviderRepository>();
|
||||
builder.RegisterRepository<IUserRepository, UserRepository>();
|
||||
|
||||
builder.RegisterType<FileExtensionContentTypeProvider>().As<IContentTypeProvider>().SingleInstance()
|
||||
.OnActivating(x =>
|
||||
{
|
||||
x.Instance.Mappings[".data"] = "application/octet-stream";
|
||||
x.Instance.Mappings[".mkv"] = "video/x-matroska";
|
||||
x.Instance.Mappings[".ass"] = "text/x-ssa";
|
||||
x.Instance.Mappings[".srt"] = "application/x-subrip";
|
||||
x.Instance.Mappings[".m3u8"] = "application/x-mpegurl";
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -2,13 +2,6 @@
|
||||
<PropertyGroup>
|
||||
<AssemblyName>Kyoo.Core</AssemblyName>
|
||||
<RootNamespace>Kyoo.Core</RootNamespace>
|
||||
<TranscoderRoot>../Kyoo.Transcoder/</TranscoderRoot>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TranscoderBinary Condition="$(IsWindows) == true">transcoder.dll</TranscoderBinary>
|
||||
<TranscoderBinary Condition="$(IsOSX) == true">libtranscoder.dylib</TranscoderBinary>
|
||||
<TranscoderBinary Condition="$(IsLinux) == true">libtranscoder.so</TranscoderBinary>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -22,21 +15,4 @@
|
||||
<ProjectReference Include="../Kyoo.Abstractions/Kyoo.Abstractions.csproj" />
|
||||
<ProjectReference Include="../Kyoo.Postgresql/Kyoo.Postgresql.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="BuildTranscoder" BeforeTargets="BeforeBuild" Condition="'$(SkipTranscoder)' != 'true' And !Exists('$(TranscoderRoot)/build/$(TranscoderBinary)')">
|
||||
<Exec WorkingDirectory="$(TranscoderRoot)" Condition="'$(IsWindows)' != 'true'" Command="mkdir -p build %26%26 cd build %26%26 cmake .. %26%26 make -j" ContinueOnError="true">
|
||||
<Output TaskParameter="ExitCode" PropertyName="ErrorCode" />
|
||||
</Exec>
|
||||
<Exec WorkingDirectory="$(TranscoderRoot)" Condition="'$(IsWindows)' == 'true'" Command="(if not exist build mkdir build) %26%26 cd build %26%26 cmake .. -G "NMake Makefiles" %26%26 nmake" ContinueOnError="true">
|
||||
<Output TaskParameter="ExitCode" PropertyName="ErrorCode" />
|
||||
</Exec>
|
||||
<Error Condition="'$(ErrorCode)' != '0'" Text="An environement capable of building the transcoder was not found. Appropriate tools are not installed, not available in the $PATH or not correctly configured. To fix this you can ether:
 - Fix your tools
 - Skip the transcoder via the '-p:SkipTranscoder=true'
 - Download an already built transcoder and put it in ./Kyoo.Transcoder/build" />
|
||||
</Target>
|
||||
|
||||
<ItemGroup Condition="'$(SkipTranscoder)' != 'true'">
|
||||
<None Include="$(TranscoderRoot)/build/$(TranscoderBinary)">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<Visible>false</Visible>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -1,118 +0,0 @@
|
||||
// Kyoo - A portable and vast media library solution.
|
||||
// Copyright (c) Kyoo.
|
||||
//
|
||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||
//
|
||||
// Kyoo is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// Kyoo is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
using Kyoo.Abstractions.Models;
|
||||
|
||||
namespace Kyoo.Core.Models.Watch
|
||||
{
|
||||
/// <summary>
|
||||
/// The list of available stream types.
|
||||
/// Attachments are only used temporarily by the transcoder but are not stored in a database.
|
||||
/// This is another <see cref="StreamType"/> enum used internally to communicate with ffmpeg.
|
||||
/// </summary>
|
||||
public enum FTrackType
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of the stream is not known.
|
||||
/// </summary>
|
||||
Unknown = StreamType.Unknown,
|
||||
|
||||
/// <summary>
|
||||
/// The stream is a video.
|
||||
/// </summary>
|
||||
Video = StreamType.Video,
|
||||
|
||||
/// <summary>
|
||||
/// The stream is an audio.
|
||||
/// </summary>
|
||||
Audio = StreamType.Audio,
|
||||
|
||||
/// <summary>
|
||||
/// The stream is a subtitle.
|
||||
/// </summary>
|
||||
Subtitle = StreamType.Subtitle,
|
||||
|
||||
/// <summary>
|
||||
/// The stream is an attachment (a font, an image or something else).
|
||||
/// Only fonts are handled by kyoo but they are not saved to the database.
|
||||
/// </summary>
|
||||
Attachment
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The unmanaged stream that the transcoder will return.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
|
||||
public struct FTrack
|
||||
{
|
||||
/// <summary>
|
||||
/// The title of the stream.
|
||||
/// </summary>
|
||||
public string Title;
|
||||
|
||||
/// <summary>
|
||||
/// The language of this stream (as a ISO-639-2 language code)
|
||||
/// </summary>
|
||||
public string Language;
|
||||
|
||||
/// <summary>
|
||||
/// The codec of this stream.
|
||||
/// </summary>
|
||||
public string Codec;
|
||||
|
||||
/// <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>
|
||||
public string Path;
|
||||
|
||||
/// <summary>
|
||||
/// The type of this stream.
|
||||
/// </summary>
|
||||
public FTrackType Type;
|
||||
|
||||
/// <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 < FTrackType.Attachment ? (StreamType)Type : StreamType.Unknown,
|
||||
IsExternal = false
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
// Kyoo - A portable and vast media library solution.
|
||||
// Copyright (c) Kyoo.
|
||||
//
|
||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||
//
|
||||
// Kyoo is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// Kyoo is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
namespace Kyoo.Core.Models.Options
|
||||
{
|
||||
/// <summary>
|
||||
/// The typed list of basic/global options for Kyoo
|
||||
/// </summary>
|
||||
public class BasicOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// The path of this list of options
|
||||
/// </summary>
|
||||
public const string Path = "Basics";
|
||||
|
||||
/// <summary>
|
||||
/// The temporary folder to cache transmuxed file.
|
||||
/// </summary>
|
||||
public string TransmuxPath { get; set; } = "cached/transmux";
|
||||
|
||||
/// <summary>
|
||||
/// The temporary folder to cache transcoded file.
|
||||
/// </summary>
|
||||
public string TranscodePath { get; set; } = "cached/transcode";
|
||||
|
||||
/// <summary>
|
||||
/// The path where metadata is stored.
|
||||
/// </summary>
|
||||
public string MetadataPath { get; set; } = "metadata/";
|
||||
}
|
||||
}
|
@ -16,6 +16,8 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
using Kyoo.Abstractions.Models;
|
||||
@ -23,6 +25,7 @@ using Kyoo.Abstractions.Models.Permissions;
|
||||
using Kyoo.Abstractions.Models.Utils;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.StaticFiles;
|
||||
using static Kyoo.Abstractions.Models.Utils.Constants;
|
||||
|
||||
namespace Kyoo.Core.Api
|
||||
@ -37,11 +40,6 @@ namespace Kyoo.Core.Api
|
||||
public class CrudThumbsApi<T> : CrudApi<T>
|
||||
where T : class, IResource, IThumbnails
|
||||
{
|
||||
/// <summary>
|
||||
/// The file manager used to send images.
|
||||
/// </summary>
|
||||
private readonly IFileSystem _files;
|
||||
|
||||
/// <summary>
|
||||
/// The thumbnail manager used to retrieve images paths.
|
||||
/// </summary>
|
||||
@ -53,17 +51,28 @@ namespace Kyoo.Core.Api
|
||||
/// <param name="repository">
|
||||
/// The repository to use as a baking store for the type <typeparamref name="T"/>.
|
||||
/// </param>
|
||||
/// <param name="files">The file manager used to send images.</param>
|
||||
/// <param name="thumbs">The thumbnail manager used to retrieve images paths.</param>
|
||||
public CrudThumbsApi(IRepository<T> repository,
|
||||
IFileSystem files,
|
||||
IThumbnailsManager thumbs)
|
||||
: base(repository)
|
||||
{
|
||||
_files = files;
|
||||
_thumbs = thumbs;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the content type of a file using it's extension.
|
||||
/// </summary>
|
||||
/// <param name="path">The path of the file</param>
|
||||
/// <exception cref="NotImplementedException">The extension of the file is not known.</exception>
|
||||
/// <returns>The content type of the file</returns>
|
||||
private static string _GetContentType(string path)
|
||||
{
|
||||
FileExtensionContentTypeProvider provider = new();
|
||||
if (provider.TryGetContentType(path, out string contentType))
|
||||
return contentType;
|
||||
throw new NotImplementedException($"Can't get the content type of the file at: {path}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get image
|
||||
/// </summary>
|
||||
@ -80,11 +89,7 @@ namespace Kyoo.Core.Api
|
||||
/// <param name="image">The number of the image to retrieve.</param>
|
||||
/// <returns>The image asked.</returns>
|
||||
/// <response code="404">No item exist with the specific identifier or the image does not exists on kyoo.</response>
|
||||
[HttpGet("{identifier:id}/image-{image:int}")]
|
||||
[PartialPermission(Kind.Read)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> GetImage(Identifier identifier, int image)
|
||||
private async Task<IActionResult> _GetImage(Identifier identifier, int image)
|
||||
{
|
||||
T resource = await identifier.Match(
|
||||
id => Repository.GetOrDefault(id),
|
||||
@ -92,8 +97,10 @@ namespace Kyoo.Core.Api
|
||||
);
|
||||
if (resource == null)
|
||||
return NotFound();
|
||||
string path = await _thumbs.GetImagePath(resource, image);
|
||||
return _files.FileResult(path);
|
||||
string path = _thumbs.GetImagePath(resource, image);
|
||||
if (path == null || !System.IO.File.Exists(path))
|
||||
return NotFound();
|
||||
return PhysicalFile(Path.GetFullPath(path), _GetContentType(path), true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -113,7 +120,7 @@ namespace Kyoo.Core.Api
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public Task<IActionResult> GetPoster(Identifier identifier)
|
||||
{
|
||||
return GetImage(identifier, Images.Poster);
|
||||
return _GetImage(identifier, Images.Poster);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -133,7 +140,7 @@ namespace Kyoo.Core.Api
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public Task<IActionResult> GetLogo(Identifier identifier)
|
||||
{
|
||||
return GetImage(identifier, Images.Logo);
|
||||
return _GetImage(identifier, Images.Logo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -151,7 +158,7 @@ namespace Kyoo.Core.Api
|
||||
[HttpGet("{identifier:id}/thumbnail", Order = AlternativeRoute)]
|
||||
public Task<IActionResult> GetBackdrop(Identifier identifier)
|
||||
{
|
||||
return GetImage(identifier, Images.Thumbnail);
|
||||
return _GetImage(identifier, Images.Thumbnail);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
@ -16,7 +16,6 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using Kyoo.Core.Models.Options;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
@ -1,55 +0,0 @@
|
||||
// Kyoo - A portable and vast media library solution.
|
||||
// Copyright (c) Kyoo.
|
||||
//
|
||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||
//
|
||||
// Kyoo is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// Kyoo is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
using Kyoo.Abstractions.Models;
|
||||
using Kyoo.Abstractions.Models.Attributes;
|
||||
using Kyoo.Abstractions.Models.Permissions;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using static Kyoo.Abstractions.Models.Utils.Constants;
|
||||
|
||||
namespace Kyoo.Core.Api
|
||||
{
|
||||
/// <summary>
|
||||
/// Information about one or multiple <see cref="Provider"/>.
|
||||
/// Providers are links to external websites or database.
|
||||
/// They are mostly linked to plugins that provide metadata from those websites.
|
||||
/// </summary>
|
||||
[Route("providers")]
|
||||
[Route("provider", Order = AlternativeRoute)]
|
||||
[ApiController]
|
||||
[ResourceView]
|
||||
[PartialPermission(nameof(Provider))]
|
||||
[ApiDefinition("Providers", Group = MetadataGroup)]
|
||||
public class ProviderApi : CrudThumbsApi<Provider>
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a new <see cref="ProviderApi"/>.
|
||||
/// </summary>
|
||||
/// <param name="libraryManager">
|
||||
/// The library manager used to modify or retrieve information about the data store.
|
||||
/// </param>
|
||||
/// <param name="files">The file manager used to send images and fonts.</param>
|
||||
/// <param name="thumbnails">The thumbnail manager used to retrieve images paths.</param>
|
||||
public ProviderApi(ILibraryManager libraryManager,
|
||||
IFileSystem files,
|
||||
IThumbnailsManager thumbnails)
|
||||
: base(libraryManager.ProviderRepository, files, thumbnails)
|
||||
{ }
|
||||
}
|
||||
}
|
@ -53,12 +53,10 @@ namespace Kyoo.Core.Api
|
||||
/// <param name="libraryManager">
|
||||
/// The library manager used to modify or retrieve information about the data store.
|
||||
/// </param>
|
||||
/// <param name="files">The file manager used to send images and fonts.</param>
|
||||
/// <param name="thumbs">The thumbnail manager used to retrieve images paths.</param>
|
||||
public StaffApi(ILibraryManager libraryManager,
|
||||
IFileSystem files,
|
||||
IThumbnailsManager thumbs)
|
||||
: base(libraryManager.PeopleRepository, files, thumbs)
|
||||
: base(libraryManager.PeopleRepository, thumbs)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
}
|
||||
|
@ -51,12 +51,10 @@ namespace Kyoo.Core.Api
|
||||
/// <param name="libraryManager">
|
||||
/// The library manager used to modify or retrieve information about the data store.
|
||||
/// </param>
|
||||
/// <param name="files">The file manager used to send images.</param>
|
||||
/// <param name="thumbs">The thumbnail manager used to retrieve images paths.</param>
|
||||
public CollectionApi(ILibraryManager libraryManager,
|
||||
IFileSystem files,
|
||||
IThumbnailsManager thumbs)
|
||||
: base(libraryManager.CollectionRepository, files, thumbs)
|
||||
: base(libraryManager.CollectionRepository, thumbs)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
}
|
||||
|
@ -16,15 +16,12 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
using Kyoo.Abstractions.Models;
|
||||
using Kyoo.Abstractions.Models.Attributes;
|
||||
using Kyoo.Abstractions.Models.Permissions;
|
||||
using Kyoo.Abstractions.Models.Utils;
|
||||
using Kyoo.Utils;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using static Kyoo.Abstractions.Models.Utils.Constants;
|
||||
@ -47,43 +44,18 @@ namespace Kyoo.Core.Api
|
||||
/// </summary>
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
|
||||
/// <summary>
|
||||
/// The transcoder used to retrive fonts.
|
||||
/// </summary>
|
||||
private readonly ITranscoder _transcoder;
|
||||
|
||||
/// <summary>
|
||||
/// The file system used to send fonts.
|
||||
/// </summary>
|
||||
private readonly IFileSystem _files;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="EpisodeApi"/>.
|
||||
/// </summary>
|
||||
/// <param name="libraryManager">
|
||||
/// The library manager used to modify or retrieve information in the data store.
|
||||
/// </param>
|
||||
/// <param name="transcoder">The transcoder used to retrive fonts</param>
|
||||
/// <param name="files">The file manager used to send images.</param>
|
||||
/// <param name="thumbnails">The thumbnail manager used to retrieve images paths.</param>
|
||||
public EpisodeApi(ILibraryManager libraryManager,
|
||||
ITranscoder transcoder,
|
||||
IFileSystem files,
|
||||
IThumbnailsManager thumbnails)
|
||||
: base(libraryManager.EpisodeRepository, files, thumbnails)
|
||||
: base(libraryManager.EpisodeRepository, thumbnails)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
_transcoder = transcoder;
|
||||
_files = files;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<ActionResult<Episode>> Create([FromBody] Episode resource)
|
||||
{
|
||||
// TODO: Remove this method and use a websocket API to do that.
|
||||
resource.Tracks = await _transcoder.ExtractInfos(resource, false);
|
||||
ActionResult<Episode> ret = await base.Create(resource);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -132,43 +104,5 @@ namespace Kyoo.Core.Api
|
||||
? NotFound()
|
||||
: NoContent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get tracks
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// List the tracks (video, audio and subtitles) available for this episode.
|
||||
/// This endpoint provide the list of raw tracks, without transcode on it. To get a schema easier to watch
|
||||
/// on a player, see the [/watch endpoint](#/watch).
|
||||
/// </remarks>
|
||||
/// <param name="identifier">The ID or slug of the <see cref="Episode"/>.</param>
|
||||
/// <param name="sortBy">A key to sort tracks by.</param>
|
||||
/// <param name="where">An optional list of filters.</param>
|
||||
/// <param name="pagination">The number of tracks to return.</param>
|
||||
/// <returns>A page of tracks.</returns>
|
||||
/// <response code="400">The filters or the sort parameters are invalid.</response>
|
||||
/// <response code="404">No episode with the given ID or slug could be found.</response>
|
||||
/// TODO fix the /watch endpoint link (when operations ID are specified).
|
||||
[HttpGet("{identifier:id}/tracks")]
|
||||
[HttpGet("{identifier:id}/track", Order = AlternativeRoute)]
|
||||
[PartialPermission(Kind.Read)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<Page<Track>>> GetEpisode(Identifier identifier,
|
||||
[FromQuery] string sortBy,
|
||||
[FromQuery] Dictionary<string, string> where,
|
||||
[FromQuery] Pagination pagination)
|
||||
{
|
||||
ICollection<Track> resources = await _libraryManager.GetAll(
|
||||
ApiHelper.ParseWhere(where, identifier.Matcher<Track>(x => x.EpisodeID, x => x.Episode.Slug)),
|
||||
Sort<Track>.From(sortBy),
|
||||
pagination
|
||||
);
|
||||
|
||||
if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame<Episode>()) == null)
|
||||
return NotFound();
|
||||
return Page(resources, pagination.Limit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -51,12 +51,10 @@ namespace Kyoo.Core.Api
|
||||
/// <param name="libraryManager">
|
||||
/// The library manager used to modify or retrieve information in the data store.
|
||||
/// </param>
|
||||
/// <param name="files">The file manager used to send images.</param>
|
||||
/// <param name="thumbs">The thumbnail manager used to retrieve images paths.</param>
|
||||
public SeasonApi(ILibraryManager libraryManager,
|
||||
IFileSystem files,
|
||||
IThumbnailsManager thumbs)
|
||||
: base(libraryManager.SeasonRepository, files, thumbs)
|
||||
: base(libraryManager.SeasonRepository, thumbs)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
}
|
||||
|
@ -48,7 +48,6 @@ namespace Kyoo.Core.Api
|
||||
/// The library manager used to modify or retrieve information in the data store.
|
||||
/// </summary>
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly ITranscoder _transcoder;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="ShowApi"/>.
|
||||
@ -56,17 +55,12 @@ namespace Kyoo.Core.Api
|
||||
/// <param name="libraryManager">
|
||||
/// The library manager used to modify or retrieve information about the data store.
|
||||
/// </param>
|
||||
/// <param name="files">The file manager used to send images and fonts.</param>
|
||||
/// <param name="thumbs">The thumbnail manager used to retrieve images paths.</param>
|
||||
/// <param name="transcoder">TODO: Remove this.</param>
|
||||
public ShowApi(ILibraryManager libraryManager,
|
||||
IFileSystem files,
|
||||
IThumbnailsManager thumbs,
|
||||
ITranscoder transcoder)
|
||||
: base(libraryManager.ShowRepository, files, thumbs)
|
||||
IThumbnailsManager thumbs)
|
||||
: base(libraryManager.ShowRepository, thumbs)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
_transcoder = transcoder;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@ -82,7 +76,6 @@ namespace Kyoo.Core.Api
|
||||
Path = ret.Value.Path
|
||||
};
|
||||
|
||||
episode.Tracks = await _transcoder.ExtractInfos(episode, false);
|
||||
await _libraryManager.Create(episode);
|
||||
}
|
||||
return ret;
|
||||
|
@ -1,205 +0,0 @@
|
||||
// Kyoo - A portable and vast media library solution.
|
||||
// Copyright (c) Kyoo.
|
||||
//
|
||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||
//
|
||||
// Kyoo is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// Kyoo is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
using Kyoo.Abstractions.Models;
|
||||
using Kyoo.Abstractions.Models.Attributes;
|
||||
using Kyoo.Abstractions.Models.Permissions;
|
||||
using Kyoo.Abstractions.Models.Utils;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using static Kyoo.Abstractions.Models.Utils.Constants;
|
||||
|
||||
namespace Kyoo.Core.Api
|
||||
{
|
||||
/// <summary>
|
||||
/// An endpoint to retrieve subtitles for a specific episode.
|
||||
/// </summary>
|
||||
[Route("subtitles")]
|
||||
[Route("subtitle", Order = AlternativeRoute)]
|
||||
[PartialPermission("subtitle")]
|
||||
[ApiController]
|
||||
[ApiDefinition("Subtitles", Group = WatchGroup)]
|
||||
public class SubtitleApi : ControllerBase
|
||||
{
|
||||
/// <summary>
|
||||
/// The library manager used to modify or retrieve information about the data store.
|
||||
/// </summary>
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
|
||||
/// <summary>
|
||||
/// The file manager used to send subtitles files.
|
||||
/// </summary>
|
||||
private readonly IFileSystem _files;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="SubtitleApi"/>.
|
||||
/// </summary>
|
||||
/// <param name="libraryManager">The library manager used to interact with the data store.</param>
|
||||
/// <param name="files">The file manager used to send subtitle files.</param>
|
||||
public SubtitleApi(ILibraryManager libraryManager, IFileSystem files)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
_files = files;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get subtitle
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Get the subtitle file with the given identifier.
|
||||
/// The extension is optional and can be used to ask Kyoo to convert the subtitle file on the fly.
|
||||
/// </remarks>
|
||||
/// <param name="identifier">
|
||||
/// The ID or slug of the subtitle (the same as the corresponding <see cref="Track"/>).
|
||||
/// </param>
|
||||
/// <param name="extension">An optional extension for the subtitle file.</param>
|
||||
/// <returns>The subtitle file</returns>
|
||||
/// <response code="404">No subtitle exist with the given ID or slug.</response>
|
||||
[HttpGet("{identifier:int}", Order = AlternativeRoute)]
|
||||
[HttpGet("{identifier:id}.{extension}")]
|
||||
[PartialPermission(Kind.Read)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[SuppressMessage("ReSharper", "RouteTemplates.ParameterTypeAndConstraintsMismatch",
|
||||
Justification = "An indentifier can be constructed with an int.")]
|
||||
public async Task<IActionResult> GetSubtitle(Identifier identifier, string extension)
|
||||
{
|
||||
Track subtitle = await identifier.Match(
|
||||
id => _libraryManager.GetOrDefault<Track>(id),
|
||||
slug =>
|
||||
{
|
||||
if (slug.Count(x => x == '.') == 3)
|
||||
{
|
||||
int idx = slug.LastIndexOf('.');
|
||||
extension = slug[(idx + 1)..];
|
||||
slug = slug[..idx];
|
||||
}
|
||||
return _libraryManager.GetOrDefault<Track>(Track.BuildSlug(slug, StreamType.Subtitle));
|
||||
});
|
||||
|
||||
if (subtitle == null)
|
||||
return NotFound();
|
||||
if (subtitle.Codec == "subrip" && extension == "vtt")
|
||||
return new ConvertSubripToVtt(subtitle.Path, _files);
|
||||
return _files.FileResult(subtitle.Path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An action result that convert a subrip subtitle to vtt.
|
||||
/// </summary>
|
||||
private class ConvertSubripToVtt : IActionResult
|
||||
{
|
||||
/// <summary>
|
||||
/// The path of the file to convert. It can be any path supported by a <see cref="IFileSystem"/>.
|
||||
/// </summary>
|
||||
private readonly string _path;
|
||||
|
||||
/// <summary>
|
||||
/// The file system used to manipulate the given file.
|
||||
/// </summary>
|
||||
private readonly IFileSystem _files;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="ConvertSubripToVtt"/>.
|
||||
/// </summary>
|
||||
/// <param name="subtitlePath">
|
||||
/// The path of the subtitle file. It can be any path supported by the given <paramref name="files"/>.
|
||||
/// </param>
|
||||
/// <param name="files">
|
||||
/// The file system used to interact with the file at the given <paramref name="subtitlePath"/>.
|
||||
/// </param>
|
||||
public ConvertSubripToVtt(string subtitlePath, IFileSystem files)
|
||||
{
|
||||
_path = subtitlePath;
|
||||
_files = files;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task ExecuteResultAsync(ActionContext context)
|
||||
{
|
||||
List<string> lines = new();
|
||||
|
||||
context.HttpContext.Response.StatusCode = 200;
|
||||
context.HttpContext.Response.Headers.Add("Content-Type", "text/vtt");
|
||||
|
||||
await using (StreamWriter writer = new(context.HttpContext.Response.Body))
|
||||
{
|
||||
await writer.WriteLineAsync("WEBVTT");
|
||||
await writer.WriteLineAsync(string.Empty);
|
||||
await writer.WriteLineAsync(string.Empty);
|
||||
|
||||
using StreamReader reader = new(await _files.GetReader(_path));
|
||||
string line;
|
||||
while ((line = await reader.ReadLineAsync()) != null)
|
||||
{
|
||||
if (line == string.Empty)
|
||||
{
|
||||
lines.Add(string.Empty);
|
||||
IEnumerable<string> processedBlock = _ConvertBlock(lines);
|
||||
foreach (string t in processedBlock)
|
||||
await writer.WriteLineAsync(t);
|
||||
lines.Clear();
|
||||
}
|
||||
else
|
||||
lines.Add(line);
|
||||
}
|
||||
}
|
||||
|
||||
await context.HttpContext.Response.Body.FlushAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a block from subrip to vtt.
|
||||
/// </summary>
|
||||
/// <param name="lines">All the lines in the block.</param>
|
||||
/// <returns>The given block, converted to vtt.</returns>
|
||||
private static IList<string> _ConvertBlock(IList<string> lines)
|
||||
{
|
||||
if (lines.Count < 3)
|
||||
return lines;
|
||||
lines[1] = lines[1].Replace(',', '.');
|
||||
if (lines[2].Length > 5)
|
||||
{
|
||||
lines[1] += lines[2].Substring(0, 6) switch
|
||||
{
|
||||
"{\\an1}" => " line:93% position:15%",
|
||||
"{\\an2}" => " line:93%",
|
||||
"{\\an3}" => " line:93% position:85%",
|
||||
"{\\an4}" => " line:50% position:15%",
|
||||
"{\\an5}" => " line:50%",
|
||||
"{\\an6}" => " line:50% position:85%",
|
||||
"{\\an7}" => " line:7% position:15%",
|
||||
"{\\an8}" => " line:7%",
|
||||
"{\\an9}" => " line:7% position:85%",
|
||||
_ => " line:93%"
|
||||
};
|
||||
}
|
||||
|
||||
if (lines[2].StartsWith("{\\an"))
|
||||
lines[2] = lines[2][6..];
|
||||
return lines;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,78 +0,0 @@
|
||||
// Kyoo - A portable and vast media library solution.
|
||||
// Copyright (c) Kyoo.
|
||||
//
|
||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||
//
|
||||
// Kyoo is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// Kyoo is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
using Kyoo.Abstractions.Models;
|
||||
using Kyoo.Abstractions.Models.Attributes;
|
||||
using Kyoo.Abstractions.Models.Permissions;
|
||||
using Kyoo.Abstractions.Models.Utils;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using static Kyoo.Abstractions.Models.Utils.Constants;
|
||||
|
||||
namespace Kyoo.Core.Api
|
||||
{
|
||||
/// <summary>
|
||||
/// Information about one or multiple <see cref="Track"/>.
|
||||
/// A track contain metadata about a video, an audio or a subtitles.
|
||||
/// </summary>
|
||||
[Route("tracks")]
|
||||
[Route("track", Order = AlternativeRoute)]
|
||||
[ApiController]
|
||||
[ResourceView]
|
||||
[PartialPermission(nameof(Track))]
|
||||
[ApiDefinition("Tracks", Group = WatchGroup)]
|
||||
public class TrackApi : CrudApi<Track>
|
||||
{
|
||||
/// <summary>
|
||||
/// The library manager used to modify or retrieve information in the data store.
|
||||
/// </summary>
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="TrackApi"/>.
|
||||
/// </summary>
|
||||
/// <param name="libraryManager">
|
||||
/// The library manager used to modify or retrieve information in the data store.
|
||||
/// </param>
|
||||
public TrackApi(ILibraryManager libraryManager)
|
||||
: base(libraryManager.TrackRepository)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get track's episode
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Get the episode that uses this track.
|
||||
/// </remarks>
|
||||
/// <param name="identifier">The ID or slug of the <see cref="Track"/>.</param>
|
||||
/// <returns>The episode that uses this track.</returns>
|
||||
/// <response code="404">No track with the given ID or slug could be found.</response>
|
||||
[HttpGet("{identifier:id}/episode")]
|
||||
[PartialPermission(Kind.Read)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<Episode>> GetEpisode(Identifier identifier)
|
||||
{
|
||||
return await _libraryManager.Get(identifier.IsContainedIn<Episode, Track>(x => x.Tracks));
|
||||
}
|
||||
}
|
||||
}
|
@ -16,6 +16,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
using Kyoo.Abstractions.Models;
|
||||
@ -46,14 +47,9 @@ namespace Kyoo.Core.Api
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
|
||||
/// <summary>
|
||||
/// A file system used to retrieve chapters informations.
|
||||
/// The http client to reach transcoder.
|
||||
/// </summary>
|
||||
private readonly IFileSystem _files;
|
||||
|
||||
/// <summary>
|
||||
/// The transcoder used to list fonts.
|
||||
/// </summary>
|
||||
private readonly ITranscoder _transcoder;
|
||||
private readonly HttpClient _client;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="WatchApi"/>.
|
||||
@ -61,13 +57,11 @@ namespace Kyoo.Core.Api
|
||||
/// <param name="libraryManager">
|
||||
/// The library manager used to modify or retrieve information in the data store.
|
||||
/// </param>
|
||||
/// <param name="fs">A file system used to retrieve chapters informations.</param>
|
||||
/// <param name="transcoder">The transcoder used to list fonts.</param>
|
||||
public WatchApi(ILibraryManager libraryManager, IFileSystem fs, ITranscoder transcoder)
|
||||
/// <param name="client">The http client to reach transcoder.</param>
|
||||
public WatchApi(ILibraryManager libraryManager, HttpClient client)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
_files = fs;
|
||||
_transcoder = transcoder;
|
||||
_client = client;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -91,38 +85,7 @@ namespace Kyoo.Core.Api
|
||||
);
|
||||
if (item == null)
|
||||
return NotFound();
|
||||
return await WatchItem.FromEpisode(item, _libraryManager, _files, _transcoder);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get font
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Get a font file that is used in subtitles of this episode.
|
||||
/// </remarks>
|
||||
/// <param name="identifier">The ID or slug of the <see cref="Episode"/>.</param>
|
||||
/// <param name="slug">The slug of the font to retrieve.</param>
|
||||
/// <returns>A page of collections.</returns>
|
||||
/// <response code="404">No show with the given ID/slug could be found or the font does not exist.</response>
|
||||
[HttpGet("{identifier:id}/fonts/{slug}")]
|
||||
[HttpGet("{identifier:id}/font/{slug}", Order = AlternativeRoute)]
|
||||
[PartialPermission(Kind.Read)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> GetFont(Identifier identifier, string slug)
|
||||
{
|
||||
Episode episode = await identifier.Match(
|
||||
id => _libraryManager.GetOrDefault<Episode>(id),
|
||||
slug => _libraryManager.GetOrDefault<Episode>(slug)
|
||||
);
|
||||
if (episode == null)
|
||||
return NotFound();
|
||||
if (slug.Contains('.'))
|
||||
slug = slug[..slug.LastIndexOf('.')];
|
||||
Font font = await _transcoder.GetFont(episode, slug);
|
||||
if (font == null)
|
||||
return NotFound();
|
||||
return _files.FileResult(font.Path);
|
||||
return await WatchItem.FromEpisode(item, _libraryManager, _client);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,192 +0,0 @@
|
||||
// Kyoo - A portable and vast media library solution.
|
||||
// Copyright (c) Kyoo.
|
||||
//
|
||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||
//
|
||||
// Kyoo is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// Kyoo is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Autofac.Features.Metadata;
|
||||
using JetBrains.Annotations;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
using Kyoo.Abstractions.Models;
|
||||
using Kyoo.Abstractions.Models.Attributes;
|
||||
using Kyoo.Core.Models.Options;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Kyoo.Host.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// A composite that merge every <see cref="IFileSystem"/> available
|
||||
/// using <see cref="FileSystemMetadataAttribute"/>.
|
||||
/// </summary>
|
||||
public class FileSystemComposite : IFileSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// The list of <see cref="IFileSystem"/> mapped to their metadata.
|
||||
/// </summary>
|
||||
private readonly ICollection<Meta<Func<IFileSystem>, FileSystemMetadataAttribute>> _fileSystems;
|
||||
|
||||
/// <summary>
|
||||
/// Options to check if the metadata should be kept in the show directory or in a kyoo's directory.
|
||||
/// </summary>
|
||||
private readonly IOptionsMonitor<BasicOptions> _options;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="FileSystemComposite"/> from a list of <see cref="IFileSystem"/> mapped to their
|
||||
/// metadata.
|
||||
/// </summary>
|
||||
/// <param name="fileSystems">The list of filesystem mapped to their metadata.</param>
|
||||
/// <param name="options">The options to use.</param>
|
||||
public FileSystemComposite(ICollection<Meta<Func<IFileSystem>, FileSystemMetadataAttribute>> fileSystems,
|
||||
IOptionsMonitor<BasicOptions> options)
|
||||
{
|
||||
_fileSystems = fileSystems;
|
||||
_options = options;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve the file system that should be used for a given path.
|
||||
/// </summary>
|
||||
/// <param name="path">
|
||||
/// The path that was requested.
|
||||
/// </param>
|
||||
/// <param name="usablePath">
|
||||
/// The path that the returned file system wants
|
||||
/// (respecting <see cref="FileSystemMetadataAttribute.StripScheme"/>).
|
||||
/// </param>
|
||||
/// <exception cref="ArgumentException">No file system was registered for the given path.</exception>
|
||||
/// <returns>The file system that should be used for a given path</returns>
|
||||
[NotNull]
|
||||
private IFileSystem _GetFileSystemForPath([NotNull] string path, [NotNull] out string usablePath)
|
||||
{
|
||||
Regex schemeMatcher = new(@"(.+)://(.*)", RegexOptions.Compiled);
|
||||
Match match = schemeMatcher.Match(path);
|
||||
|
||||
if (!match.Success)
|
||||
{
|
||||
usablePath = path;
|
||||
Meta<Func<IFileSystem>, FileSystemMetadataAttribute> defaultFs = _fileSystems
|
||||
.SingleOrDefault(x => x.Metadata.Scheme.Contains(string.Empty));
|
||||
if (defaultFs == null)
|
||||
throw new ArgumentException($"No file system registered for the default scheme.");
|
||||
return defaultFs.Value.Invoke();
|
||||
}
|
||||
string scheme = match.Groups[1].Value;
|
||||
Meta<Func<IFileSystem>, FileSystemMetadataAttribute> ret = _fileSystems
|
||||
.SingleOrDefault(x => x.Metadata.Scheme.Contains(scheme));
|
||||
if (ret == null)
|
||||
throw new ArgumentException($"No file system registered for the scheme: {scheme}.");
|
||||
usablePath = ret.Metadata.StripScheme ? match.Groups[2].Value : path;
|
||||
return ret.Value.Invoke();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IActionResult FileResult(string path, bool rangeSupport = false, string type = null)
|
||||
{
|
||||
if (path == null)
|
||||
return new NotFoundResult();
|
||||
return _GetFileSystemForPath(path, out string relativePath)
|
||||
.FileResult(relativePath, rangeSupport, type);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<Stream> GetReader(string path)
|
||||
{
|
||||
if (path == null)
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
return _GetFileSystemForPath(path, out string relativePath)
|
||||
.GetReader(relativePath);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<Stream> GetReader(string path, AsyncRef<string> mime)
|
||||
{
|
||||
if (path == null)
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
return _GetFileSystemForPath(path, out string relativePath)
|
||||
.GetReader(relativePath, mime);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<Stream> NewFile(string path)
|
||||
{
|
||||
if (path == null)
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
return _GetFileSystemForPath(path, out string relativePath)
|
||||
.NewFile(relativePath);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<string> CreateDirectory(string path)
|
||||
{
|
||||
if (path == null)
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
return _GetFileSystemForPath(path, out string relativePath)
|
||||
.CreateDirectory(relativePath);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Combine(params string[] paths)
|
||||
{
|
||||
return _GetFileSystemForPath(paths[0], out string relativePath)
|
||||
.Combine(paths[1..].Prepend(relativePath).ToArray());
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<ICollection<string>> ListFiles(string path, SearchOption options = SearchOption.TopDirectoryOnly)
|
||||
{
|
||||
if (path == null)
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
return _GetFileSystemForPath(path, out string relativePath)
|
||||
.ListFiles(relativePath, options);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<bool> Exists(string path)
|
||||
{
|
||||
if (path == null)
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
return _GetFileSystemForPath(path, out string relativePath)
|
||||
.Exists(relativePath);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<string> GetExtraDirectory<T>(T resource)
|
||||
{
|
||||
IFileSystem fs = _GetFileSystemForPath(_options.CurrentValue.MetadataPath, out string path);
|
||||
return fs.GetExtraDirectory(resource);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<ICollection<Track>> ExtractInfos(Episode episode, bool reExtract)
|
||||
{
|
||||
IFileSystem fs = _GetFileSystemForPath(episode.Path, out string _);
|
||||
return fs.ExtractInfos(episode, reExtract);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IActionResult Transmux(Episode episode)
|
||||
{
|
||||
IFileSystem fs = _GetFileSystemForPath(episode.Path, out string _);
|
||||
return fs.Transmux(episode);
|
||||
}
|
||||
}
|
||||
}
|
@ -20,10 +20,8 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
using Kyoo.Core.Models.Options;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Kyoo.Host.Controllers
|
||||
{
|
||||
@ -38,11 +36,6 @@ namespace Kyoo.Host.Controllers
|
||||
/// </summary>
|
||||
private readonly IServiceProvider _provider;
|
||||
|
||||
/// <summary>
|
||||
/// The configuration to get the plugin's directory.
|
||||
/// </summary>
|
||||
private readonly IOptions<BasicOptions> _options;
|
||||
|
||||
/// <summary>
|
||||
/// The logger used by this class.
|
||||
/// </summary>
|
||||
@ -57,14 +50,11 @@ namespace Kyoo.Host.Controllers
|
||||
/// Create a new <see cref="PluginManager"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="provider">A service container to allow initialization of plugins</param>
|
||||
/// <param name="options">The configuration instance, to get the plugin's directory path.</param>
|
||||
/// <param name="logger">The logger used by this class.</param>
|
||||
public PluginManager(IServiceProvider provider,
|
||||
IOptions<BasicOptions> options,
|
||||
ILogger<PluginManager> logger)
|
||||
{
|
||||
_provider = provider;
|
||||
_options = options;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
|
@ -20,11 +20,7 @@ using System.Collections.Generic;
|
||||
using Autofac;
|
||||
using Autofac.Extras.AttributeMetadata;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
using Kyoo.Core.Models.Options;
|
||||
using Kyoo.Host.Controllers;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Serilog;
|
||||
|
||||
namespace Kyoo.Host
|
||||
@ -42,20 +38,13 @@ namespace Kyoo.Host
|
||||
/// </summary>
|
||||
private readonly IPluginManager _plugins;
|
||||
|
||||
/// <summary>
|
||||
/// The configuration used to register options.
|
||||
/// </summary>
|
||||
private readonly IConfiguration _configuration;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="HostModule"/>.
|
||||
/// </summary>
|
||||
/// <param name="plugins">The plugin manager that loaded all plugins.</param>
|
||||
/// <param name="configuration"> The configuration used to register options.</param>
|
||||
public HostModule(IPluginManager plugins, IConfiguration configuration)
|
||||
public HostModule(IPluginManager plugins)
|
||||
{
|
||||
_plugins = plugins;
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@ -63,7 +52,6 @@ namespace Kyoo.Host
|
||||
{
|
||||
builder.RegisterModule<AttributedMetadataModule>();
|
||||
builder.RegisterInstance(_plugins).As<IPluginManager>().ExternallyOwned();
|
||||
builder.RegisterComposite<FileSystemComposite, IFileSystem>().InstancePerLifetimeScope();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -24,7 +24,6 @@ using Autofac;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
using Kyoo.Authentication;
|
||||
using Kyoo.Core;
|
||||
using Kyoo.Core.Models.Options;
|
||||
using Kyoo.Host.Controllers;
|
||||
using Kyoo.Postgresql;
|
||||
using Kyoo.Swagger;
|
||||
@ -35,7 +34,6 @@ using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Kyoo.Host
|
||||
{
|
||||
@ -49,11 +47,6 @@ namespace Kyoo.Host
|
||||
/// </summary>
|
||||
private readonly IPluginManager _plugins;
|
||||
|
||||
/// <summary>
|
||||
/// The configuration used to register <see cref="IOptions{TOptions}"/> and so on for plugin's specified types.
|
||||
/// </summary>
|
||||
private readonly IConfiguration _configuration;
|
||||
|
||||
/// <summary>
|
||||
/// The plugin that adds controllers and tasks specific to this host.
|
||||
/// </summary>
|
||||
@ -63,14 +56,10 @@ namespace Kyoo.Host
|
||||
/// Created from the DI container, those services are needed to load information and instantiate plugins.s
|
||||
/// </summary>
|
||||
/// <param name="plugins">The plugin manager to use to load new plugins and configure the host.</param>
|
||||
/// <param name="configuration">
|
||||
/// The configuration used to register <see cref="IOptions{TOptions}"/> and so on for plugin's specified types.
|
||||
/// </param>
|
||||
public PluginsStartup(IPluginManager plugins, IConfiguration configuration)
|
||||
public PluginsStartup(IPluginManager plugins)
|
||||
{
|
||||
_plugins = plugins;
|
||||
_configuration = configuration;
|
||||
_hostModule = new HostModule(_plugins, configuration);
|
||||
_hostModule = new HostModule(_plugins);
|
||||
_plugins.LoadPlugins(
|
||||
typeof(CoreModule),
|
||||
typeof(AuthenticationModule),
|
||||
@ -94,10 +83,9 @@ namespace Kyoo.Host
|
||||
HostServiceProvider hostProvider = new(host.HostingEnvironment, host.Configuration, logger);
|
||||
PluginManager plugins = new(
|
||||
hostProvider,
|
||||
Options.Create(host.Configuration.GetSection(BasicOptions.Path).Get<BasicOptions>()),
|
||||
logger.CreateLogger<PluginManager>()
|
||||
);
|
||||
return new PluginsStartup(plugins, host.Configuration);
|
||||
return new PluginsStartup(plugins);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -65,11 +65,6 @@ namespace Kyoo.Postgresql
|
||||
/// </summary>
|
||||
public DbSet<Episode> Episodes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// All tracks of Kyoo. See <see cref="Track"/>.
|
||||
/// </summary>
|
||||
public DbSet<Track> Tracks { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// All genres of Kyoo. See <see cref="Genres"/>.
|
||||
/// </summary>
|
||||
@ -268,10 +263,6 @@ namespace Kyoo.Postgresql
|
||||
.HasMany(x => x.Episodes)
|
||||
.WithOne(x => x.Season)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
modelBuilder.Entity<Episode>()
|
||||
.HasMany(x => x.Tracks)
|
||||
.WithOne(x => x.Episode)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
modelBuilder.Entity<Show>()
|
||||
.HasOne(x => x.Studio)
|
||||
@ -307,7 +298,6 @@ namespace Kyoo.Postgresql
|
||||
modelBuilder.Entity<Show>().Property(x => x.Slug).IsRequired();
|
||||
modelBuilder.Entity<Season>().Property(x => x.Slug).IsRequired();
|
||||
modelBuilder.Entity<Episode>().Property(x => x.Slug).IsRequired();
|
||||
modelBuilder.Entity<Track>().Property(x => x.Slug).IsRequired();
|
||||
modelBuilder.Entity<Studio>().Property(x => x.Slug).IsRequired();
|
||||
modelBuilder.Entity<User>().Property(x => x.Slug).IsRequired();
|
||||
|
||||
@ -344,12 +334,6 @@ namespace Kyoo.Postgresql
|
||||
modelBuilder.Entity<Episode>()
|
||||
.HasIndex(x => x.Slug)
|
||||
.IsUnique();
|
||||
modelBuilder.Entity<Track>()
|
||||
.HasIndex(x => new { x.EpisodeID, x.Type, x.Language, x.TrackIndex, x.IsForced })
|
||||
.IsUnique();
|
||||
modelBuilder.Entity<Track>()
|
||||
.HasIndex(x => x.Slug)
|
||||
.IsUnique();
|
||||
modelBuilder.Entity<User>()
|
||||
.HasIndex(x => x.Slug)
|
||||
.IsUnique();
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -19,6 +19,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Kyoo.Abstractions.Models;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
@ -27,6 +28,8 @@ namespace Kyoo.Postgresql.Migrations
|
||||
/// <summary>
|
||||
/// The initial migration that build most of the database.
|
||||
/// </summary>
|
||||
[DbContext(typeof(PostgresContext))]
|
||||
[Migration("20210801171613_Initial")]
|
||||
public partial class Initial : Migration
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
@ -577,7 +580,7 @@ namespace Kyoo.Postgresql.Migrations
|
||||
is_forced = table.Column<bool>(type: "boolean", nullable: false),
|
||||
is_external = table.Column<bool>(type: "boolean", nullable: false),
|
||||
path = table.Column<string>(type: "text", nullable: true),
|
||||
type = table.Column<StreamType>(type: "stream_type", nullable: false),
|
||||
type = table.Column<object>(type: "stream_type", nullable: false),
|
||||
episode_id = table.Column<int>(type: "integer", nullable: false),
|
||||
track_index = table.Column<int>(type: "integer", nullable: false)
|
||||
},
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -16,6 +16,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace Kyoo.Postgresql.Migrations
|
||||
@ -23,6 +24,8 @@ namespace Kyoo.Postgresql.Migrations
|
||||
/// <summary>
|
||||
/// A migration that adds postgres triggers to update slugs.
|
||||
/// </summary>
|
||||
[DbContext(typeof(PostgresContext))]
|
||||
[Migration("20210801171641_Triggers")]
|
||||
public partial class Triggers : Migration
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -16,6 +16,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace Kyoo.Postgresql.Migrations
|
||||
@ -23,6 +24,8 @@ namespace Kyoo.Postgresql.Migrations
|
||||
/// <summary>
|
||||
/// Remove triggers
|
||||
/// </summary>
|
||||
[DbContext(typeof(PostgresContext))]
|
||||
[Migration("20230724144449_RemoveTriggers")]
|
||||
public partial class RemoveTriggers : Migration
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
|
@ -1,4 +1,4 @@
|
||||
// <auto-generated />
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Kyoo.Abstractions.Models;
|
||||
@ -13,8 +13,6 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
namespace Kyoo.Postgresql.Migrations
|
||||
{
|
||||
[DbContext(typeof(PostgresContext))]
|
||||
[Migration("20230726100747_Timestamp")]
|
||||
partial class Timestamp
|
||||
{
|
||||
/// <inheritdoc />
|
||||
@ -545,7 +543,7 @@ namespace Kyoo.Postgresql.Migrations
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("track_index");
|
||||
|
||||
b.Property<StreamType>("Type")
|
||||
b.Property<object>("Type")
|
||||
.HasColumnType("stream_type")
|
||||
.HasColumnName("type");
|
||||
|
||||
|
@ -17,6 +17,7 @@
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
@ -24,6 +25,8 @@ using Microsoft.EntityFrameworkCore.Migrations;
|
||||
namespace Kyoo.Postgresql.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
[DbContext(typeof(PostgresContext))]
|
||||
[Migration("20230726100747_Timestamp")]
|
||||
public partial class Timestamp : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
|
@ -542,7 +542,7 @@ namespace Kyoo.Postgresql.Migrations
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("track_index");
|
||||
|
||||
b.Property<StreamType>("Type")
|
||||
b.Property<object>("Type")
|
||||
.HasColumnType("stream_type")
|
||||
.HasColumnName("type");
|
||||
|
||||
|
@ -54,7 +54,6 @@ namespace Kyoo.Postgresql
|
||||
{
|
||||
NpgsqlConnection.GlobalTypeMapper.MapEnum<Status>();
|
||||
NpgsqlConnection.GlobalTypeMapper.MapEnum<ItemType>();
|
||||
NpgsqlConnection.GlobalTypeMapper.MapEnum<StreamType>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -108,7 +107,6 @@ namespace Kyoo.Postgresql
|
||||
{
|
||||
modelBuilder.HasPostgresEnum<Status>();
|
||||
modelBuilder.HasPostgresEnum<ItemType>();
|
||||
modelBuilder.HasPostgresEnum<StreamType>();
|
||||
|
||||
modelBuilder.Entity<LibraryItem>()
|
||||
.ToView("library_items")
|
||||
|
@ -48,8 +48,7 @@ namespace Kyoo.Tests.Database
|
||||
SeasonRepository season = new(_NewContext(), show, provider);
|
||||
LibraryItemRepository libraryItem = new(_NewContext(),
|
||||
new Lazy<ILibraryRepository>(() => LibraryManager.LibraryRepository));
|
||||
EpisodeRepository episode = new(_NewContext(), show, provider, new Lazy<ITrackRepository>(() => LibraryManager.TrackRepository));
|
||||
TrackRepository track = new(_NewContext(), episode);
|
||||
EpisodeRepository episode = new(_NewContext(), show, provider);
|
||||
UserRepository user = new(_NewContext());
|
||||
|
||||
LibraryManager = new LibraryManager(new IBaseRepository[] {
|
||||
@ -60,7 +59,6 @@ namespace Kyoo.Tests.Database
|
||||
show,
|
||||
season,
|
||||
episode,
|
||||
track,
|
||||
people,
|
||||
studio,
|
||||
genre,
|
||||
|
@ -1,74 +0,0 @@
|
||||
// Kyoo - A portable and vast media library solution.
|
||||
// Copyright (c) Kyoo.
|
||||
//
|
||||
// See AUTHORS.md and LICENSE file in the project root for full license information.
|
||||
//
|
||||
// Kyoo is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// Kyoo is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using Kyoo.Abstractions.Controllers;
|
||||
using Kyoo.Abstractions.Models;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Kyoo.Tests.Database
|
||||
{
|
||||
namespace PostgreSQL
|
||||
{
|
||||
[Collection(nameof(Postgresql))]
|
||||
public class TrackTests : ATrackTests
|
||||
{
|
||||
public TrackTests(PostgresFixture postgres, ITestOutputHelper output)
|
||||
: base(new RepositoryActivator(output, postgres)) { }
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class ATrackTests : RepositoryTests<Track>
|
||||
{
|
||||
private readonly ITrackRepository _repository;
|
||||
|
||||
protected ATrackTests(RepositoryActivator repositories)
|
||||
: base(repositories)
|
||||
{
|
||||
_repository = repositories.LibraryManager.TrackRepository;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SlugEditTest()
|
||||
{
|
||||
await Repositories.LibraryManager.ShowRepository.Edit(new Show
|
||||
{
|
||||
ID = 1,
|
||||
Slug = "new-slug"
|
||||
}, false);
|
||||
Track track = await _repository.Get(1);
|
||||
Assert.Equal("new-slug-s1e1.eng-1.subtitle", track.Slug);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UndefinedLanguageSlugTest()
|
||||
{
|
||||
await _repository.Create(new Track
|
||||
{
|
||||
ID = 5,
|
||||
TrackIndex = 0,
|
||||
Type = StreamType.Video,
|
||||
Language = null,
|
||||
EpisodeID = TestSample.Get<Episode>().ID
|
||||
});
|
||||
Track track = await _repository.Get(5);
|
||||
Assert.Equal("anohana-s1e1.und.video", track.Slug);
|
||||
}
|
||||
}
|
||||
}
|
@ -239,29 +239,6 @@ namespace Kyoo.Tests
|
||||
ReleaseDate = new DateTime(2020, 06, 05).ToUniversalTime()
|
||||
}
|
||||
},
|
||||
{
|
||||
typeof(Track),
|
||||
() =>
|
||||
{
|
||||
Track ret = new()
|
||||
{
|
||||
ID = 1,
|
||||
EpisodeID = 1,
|
||||
Codec = "subrip",
|
||||
Language = "eng",
|
||||
Path = "/path",
|
||||
Title = "Subtitle track",
|
||||
Episode = Get<Episode>(),
|
||||
Type = StreamType.Subtitle,
|
||||
IsDefault = true,
|
||||
IsExternal = false,
|
||||
IsForced = false,
|
||||
TrackIndex = 1
|
||||
};
|
||||
ret.Episode = null;
|
||||
return ret;
|
||||
}
|
||||
},
|
||||
{
|
||||
typeof(People),
|
||||
() => new People
|
||||
@ -359,12 +336,6 @@ namespace Kyoo.Tests
|
||||
episode.Season = season;
|
||||
context.Episodes.Add(episode);
|
||||
|
||||
Track track = Get<Track>();
|
||||
track.ID = 0;
|
||||
track.EpisodeID = 0;
|
||||
track.Episode = episode;
|
||||
context.Tracks.Add(track);
|
||||
|
||||
Studio studio = Get<Studio>();
|
||||
studio.ID = 0;
|
||||
studio.Shows = new List<Show> { show };
|
||||
|
@ -20,13 +20,17 @@
|
||||
|
||||
import { z } from "zod";
|
||||
import { zdate } from "../utils";
|
||||
import { ResourceP, ImagesP, imageFn } from "../traits";
|
||||
import { ImagesP, imageFn } from "../traits";
|
||||
import { EpisodeP } from "./episode";
|
||||
|
||||
/**
|
||||
* A video, audio or subtitle track for an episode.
|
||||
* A audio or subtitle track.
|
||||
*/
|
||||
export const TrackP = ResourceP.extend({
|
||||
export const TrackP = z.object({
|
||||
/**
|
||||
* The index of this track on the episode.
|
||||
*/
|
||||
index: z.number(),
|
||||
/**
|
||||
* The title of the stream.
|
||||
*/
|
||||
@ -47,44 +51,16 @@ export const TrackP = ResourceP.extend({
|
||||
* Is this stream tagged as forced?
|
||||
*/
|
||||
isForced: z.boolean(),
|
||||
/**
|
||||
* Is this track extern to the episode's file?
|
||||
*/
|
||||
isExternal: z.boolean(),
|
||||
/**
|
||||
* The index of this track on the episode.
|
||||
*/
|
||||
trackIndex: z.number(),
|
||||
/**
|
||||
* A user-friendly name for this track. It does not include the track type.
|
||||
*/
|
||||
displayName: z.string(),
|
||||
});
|
||||
export type Audio = z.infer<typeof TrackP>;
|
||||
|
||||
export const SubtitleP = TrackP.extend({
|
||||
/*
|
||||
* The url of this track (only if this is a subtitle)..
|
||||
*/
|
||||
link: z.string().transform(imageFn).nullable(),
|
||||
});
|
||||
export type Track = z.infer<typeof TrackP>;
|
||||
|
||||
export const FontP = z.object({
|
||||
/*
|
||||
* A human-readable identifier, used in the URL.
|
||||
*/
|
||||
slug: z.string(),
|
||||
/*
|
||||
* The name of the font file (with the extension).
|
||||
*/
|
||||
file: z.string(),
|
||||
/*
|
||||
* The format of this font (the extension).
|
||||
*/
|
||||
format: z.string(),
|
||||
/*
|
||||
* The url of the font.
|
||||
*/
|
||||
link: z.string().transform(imageFn),
|
||||
});
|
||||
export type Font = z.infer<typeof FontP>;
|
||||
export type Subtitle = z.infer<typeof SubtitleP>;
|
||||
|
||||
export const ChapterP = z.object({
|
||||
/**
|
||||
@ -128,31 +104,41 @@ const WatchMovieP = z.preprocess(
|
||||
* The release date of this episode. It can be null if unknown.
|
||||
*/
|
||||
releaseDate: zdate().nullable(),
|
||||
|
||||
/**
|
||||
* The container of the video file of this episode. Common containers are mp4, mkv, avi and so
|
||||
* on.
|
||||
* The transcoder's info for this item. This include subtitles, fonts, chapters...
|
||||
*/
|
||||
container: z.string(),
|
||||
/**
|
||||
* The video track. See Track for more information.
|
||||
*/
|
||||
video: TrackP,
|
||||
/**
|
||||
* The list of audio tracks. See Track for more information.
|
||||
*/
|
||||
audios: z.array(TrackP),
|
||||
/**
|
||||
* The list of subtitles tracks. See Track for more information.
|
||||
*/
|
||||
subtitles: z.array(TrackP),
|
||||
/**
|
||||
* The list of fonts that can be used to display subtitles.
|
||||
*/
|
||||
fonts: z.array(FontP),
|
||||
/**
|
||||
* The list of chapters. See Chapter for more information.
|
||||
*/
|
||||
chapters: z.array(ChapterP),
|
||||
info: z.object({
|
||||
/**
|
||||
* The sha1 of the video file.
|
||||
*/
|
||||
sha: z.string(),
|
||||
/**
|
||||
* The internal path of the video file.
|
||||
*/
|
||||
path: z.string(),
|
||||
/**
|
||||
* The container of the video file of this episode. Common containers are mp4, mkv, avi and so
|
||||
* on.
|
||||
*/
|
||||
container: z.string(),
|
||||
/**
|
||||
* The list of audio tracks.
|
||||
*/
|
||||
audios: z.array(TrackP),
|
||||
/**
|
||||
* The list of subtitles tracks.
|
||||
*/
|
||||
subtitles: z.array(SubtitleP),
|
||||
/**
|
||||
* The list of fonts that can be used to display subtitles.
|
||||
*/
|
||||
fonts: z.array(z.string().transform(imageFn)),
|
||||
/**
|
||||
* The list of chapters. See Chapter for more information.
|
||||
*/
|
||||
chapters: z.array(ChapterP),
|
||||
}),
|
||||
/**
|
||||
* The links to the videos of this watch item.
|
||||
*/
|
||||
|
@ -18,7 +18,7 @@
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Font, Track, WatchItem } from "@kyoo/models";
|
||||
import { Subtitle, WatchItem } from "@kyoo/models";
|
||||
import { IconButton, tooltip, Menu, ts } from "@kyoo/primitives";
|
||||
import { useAtom } from "jotai";
|
||||
import { Platform, View } from "react-native";
|
||||
@ -40,8 +40,8 @@ export const RightButtons = ({
|
||||
onMenuClose,
|
||||
...props
|
||||
}: {
|
||||
subtitles?: Track[];
|
||||
fonts?: Font[];
|
||||
subtitles?: Subtitle[];
|
||||
fonts?: string[];
|
||||
qualities?: WatchItem["link"];
|
||||
onMenuOpen: () => void;
|
||||
onMenuClose: () => void;
|
||||
@ -71,7 +71,7 @@ export const RightButtons = ({
|
||||
/>
|
||||
{subtitles.map((x) => (
|
||||
<Menu.Item
|
||||
key={x.id}
|
||||
key={x.index}
|
||||
label={x.displayName}
|
||||
selected={selectedSubtitle === x}
|
||||
onSelect={() => setSubtitle(x)}
|
||||
|
@ -51,9 +51,9 @@ const mapData = (
|
||||
href: data ? (data.isMovie ? `/movie/${data.slug}` : `/show/${data.showSlug}`) : "#",
|
||||
poster: data.poster,
|
||||
qualities: data.link,
|
||||
subtitles: data.subtitles,
|
||||
chapters: data.chapters,
|
||||
fonts: data.fonts,
|
||||
subtitles: data.info.subtitles,
|
||||
chapters: data.info.chapters,
|
||||
fonts: data.info.fonts,
|
||||
previousSlug,
|
||||
nextSlug,
|
||||
};
|
||||
@ -92,7 +92,7 @@ export const Player: QueryPage<{ slug: string }> = ({ slug }) => {
|
||||
const next =
|
||||
data && !data.isMovie && data.nextEpisode ? `/watch/${data.nextEpisode.slug}` : undefined;
|
||||
|
||||
useVideoKeyboard(data?.subtitles, data?.fonts, previous, next);
|
||||
useVideoKeyboard(data?.info.subtitles, data?.info.fonts, previous, next);
|
||||
|
||||
const [isFullscreen, setFullscreen] = useAtom(fullscreenAtom);
|
||||
const [isPlaying, setPlay] = useAtom(playAtom);
|
||||
@ -191,9 +191,9 @@ export const Player: QueryPage<{ slug: string }> = ({ slug }) => {
|
||||
>
|
||||
<Video
|
||||
links={data?.link}
|
||||
subtitles={data?.subtitles}
|
||||
subtitles={data?.info.subtitles}
|
||||
setError={setPlaybackError}
|
||||
fonts={data?.fonts}
|
||||
fonts={data?.info.fonts}
|
||||
onPointerDown={(e) => onPointerDown(e)}
|
||||
onEnd={() => {
|
||||
if (!data) return;
|
||||
|
@ -18,7 +18,7 @@
|
||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Font, getToken, Track } from "@kyoo/models";
|
||||
import { getToken, Subtitle } from "@kyoo/models";
|
||||
import {
|
||||
forwardRef,
|
||||
RefObject,
|
||||
@ -201,7 +201,7 @@ export default Video;
|
||||
|
||||
let htmlTrack: HTMLTrackElement | null;
|
||||
let subOcto: SubtitleOctopus | null;
|
||||
const useSubtitle = (player: RefObject<HTMLVideoElement>, value: Track | null, fonts?: Font[]) => {
|
||||
const useSubtitle = (player: RefObject<HTMLVideoElement>, value: Subtitle | null, fonts?: string[]) => {
|
||||
useEffect(() => {
|
||||
if (!player.current) return;
|
||||
|
||||
@ -228,7 +228,7 @@ const useSubtitle = (player: RefObject<HTMLVideoElement>, value: Track | null, f
|
||||
track.kind = "subtitles";
|
||||
track.label = value.displayName;
|
||||
if (value.language) track.srclang = value.language;
|
||||
track.src = value.link! + ".vtt";
|
||||
track.src = value.link;
|
||||
track.className = "subtitle_container";
|
||||
track.default = true;
|
||||
track.onload = () => {
|
||||
@ -243,12 +243,11 @@ const useSubtitle = (player: RefObject<HTMLVideoElement>, value: Track | null, f
|
||||
removeOctoSub();
|
||||
subOcto = new SubtitleOctopus({
|
||||
video: player.current,
|
||||
subUrl: value.link!,
|
||||
subUrl: value.link,
|
||||
workerUrl: "/_next/static/chunks/subtitles-octopus-worker.js",
|
||||
legacyWorkerUrl: "/_next/static/chunks/subtitles-octopus-worker-legacy.js",
|
||||
fallbackFont: "/default.woff2",
|
||||
fonts: fonts?.map((x) => x.link),
|
||||
// availableFonts: fonts ? Object.fromEntries(fonts.map((x) => [x.slug, x.link])) : undefined,
|
||||
fonts: fonts,
|
||||
// lazyFileLoading: true,
|
||||
renderMode: "wasm-blend",
|
||||
});
|
||||
|
@ -14,11 +14,13 @@ use crate::transcode::Quality;
|
||||
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct MediaInfo {
|
||||
/// The sha1 of the video file.
|
||||
pub sha: String,
|
||||
/// The internal path of this track.
|
||||
/// The internal path of the video file.
|
||||
pub path: String,
|
||||
/// The length of the media in seconds.
|
||||
pub length: f32,
|
||||
/// The container of the video file of this episode.
|
||||
pub container: String,
|
||||
pub video: Video,
|
||||
pub audios: Vec<Audio>,
|
||||
@ -54,9 +56,9 @@ pub struct Audio {
|
||||
/// The codec of this stream.
|
||||
pub codec: String,
|
||||
/// Is this stream the default one of it's type?
|
||||
pub default: bool,
|
||||
pub is_default: bool,
|
||||
/// Is this stream tagged as forced? (useful only for subtitles)
|
||||
pub forced: bool,
|
||||
pub is_forced: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, ToSchema)]
|
||||
@ -70,9 +72,9 @@ pub struct Subtitle {
|
||||
/// The codec of this stream.
|
||||
pub codec: String,
|
||||
/// Is this stream the default one of it's type?
|
||||
pub default: bool,
|
||||
pub is_default: bool,
|
||||
/// Is this stream tagged as forced? (useful only for subtitles)
|
||||
pub forced: bool,
|
||||
pub is_forced: bool,
|
||||
/// The link to access this subtitle.
|
||||
pub link: String,
|
||||
}
|
||||
@ -80,9 +82,9 @@ pub struct Subtitle {
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct Chapter {
|
||||
/// The start time of the chapter (in second from the start of the episode).
|
||||
pub start: f32,
|
||||
pub start_time: f32,
|
||||
/// The end time of the chapter (in second from the start of the episode).
|
||||
pub end: f32,
|
||||
pub end_time: f32,
|
||||
/// The name of this chapter. This should be a human-readable name that could be presented to the user.
|
||||
pub name: String, // TODO: add a type field for Opening, Credits...
|
||||
}
|
||||
@ -149,8 +151,8 @@ pub async fn identify(path: String) -> Result<MediaInfo, std::io::Error> {
|
||||
title: a["Title"].as_str().map(|x| x.to_string()),
|
||||
language: a["Language"].as_str().map(|x| x.to_string()),
|
||||
codec,
|
||||
default: a["Default"] == "Yes",
|
||||
forced: a["Forced"] == "No",
|
||||
is_default: a["Default"] == "Yes",
|
||||
is_forced: a["Forced"] == "No",
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
@ -193,8 +195,8 @@ pub async fn identify(path: String) -> Result<MediaInfo, std::io::Error> {
|
||||
language: a["Language"].as_str().map(|x| x.to_string()),
|
||||
// TODO: format is invalid. Channels count missing...
|
||||
codec: a["Format"].as_str().unwrap().to_string(),
|
||||
default: a["Default"] == "Yes",
|
||||
forced: a["Forced"] == "No",
|
||||
is_default: a["Default"] == "Yes",
|
||||
is_forced: a["Forced"] == "No",
|
||||
})
|
||||
.collect(),
|
||||
subtitles: subs,
|
||||
@ -209,8 +211,8 @@ pub async fn identify(path: String) -> Result<MediaInfo, std::io::Error> {
|
||||
.map(|x| {
|
||||
std::iter::zip(x["extra"].entries(), x["extra"].entries().skip(1))
|
||||
.map(|((start, name), (end, _))| Chapter {
|
||||
start: time_to_seconds(start),
|
||||
end: time_to_seconds(end),
|
||||
start_time: time_to_seconds(start),
|
||||
end_time: time_to_seconds(end),
|
||||
name: name.as_str().unwrap().to_string(),
|
||||
})
|
||||
.collect()
|
||||
|
Loading…
x
Reference in New Issue
Block a user