diff --git a/.editorconfig b/.editorconfig index 7b812979..db19b724 100644 --- a/.editorconfig +++ b/.editorconfig @@ -14,6 +14,12 @@ indent_style = space indent_size = 2 [*.cs] +csharp_prefer_braces = false +dotnet_diagnostic.IDE0130.severity = none +dotnet_diagnostic.IDE0058.severity = none +dotnet_diagnostic.IDE0046.severity = none +dotnet_diagnostic.CA1848.severity = none +dotnet_diagnostic.CA2007.severity = none # Sort using and Import directives with System.* appearing first dotnet_sort_system_directives_first = true csharp_using_directive_placement = outside_namespace:warning diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..6cfd0e87 --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +TVDB__APIKEY= +THEMOVIEDB__APIKEY= diff --git a/.gitignore b/.gitignore index 694e9d6b..42235b8d 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,9 @@ libtranscoder.so libtranscoder.dylib transcoder.dll +video +.env + ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. ## diff --git a/README.md b/README.md index 9c1b965a..983e9f05 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,6 @@ services: environment: - KYOO_DATADIR=/var/lib/kyoo - BASICS__PUBLICURL=https://demo.kyoo.moe - - BASICS__MetadataInShow=false - DATABASE__ENABLED=postgres - DATABASE__CONFIGURATIONS__POSTGRES__SERVER=postgres - DATABASE__CONFIGURATIONS__POSTGRES__USER ID=kyoo diff --git a/deployment/changelog.json b/deployment/changelog.json deleted file mode 100644 index 4e7c94ec..00000000 --- a/deployment/changelog.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "categories": [ - { - "title": "## Features", - "labels": ["enhancement"] - }, - { - "title": "## Fixes", - "labels": ["bug"] - }, - { - "title": "## Web App", - "labels": ["webapp"] - } - ], - "template": "${{CHANGELOG}}\n\n
\nOthers\n\n${{UNCATEGORIZED}}\n
", - "pr_template": "- ${{TITLE}} (PR: #${{NUMBER}})" -} diff --git a/deployment/kyoo.service b/deployment/kyoo.service index 5727b7c5..b9cdcf0c 100644 --- a/deployment/kyoo.service +++ b/deployment/kyoo.service @@ -1,6 +1,5 @@ [Unit] Description=Kyoo Media Server -Requires=postgresql.service After=network.target [Service] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..313392e8 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,35 @@ +version: "3.8" + +services: + kyoo: + build: . + restart: on-failure + environment: + - KYOO_DATADIR=/var/lib/kyoo + - BASICS__PUBLICURL=http://localhost:5000 + - DATABASE__ENABLED=postgres + - DATABASE__CONFIGURATIONS__POSTGRES__SERVER=postgres + - DATABASE__CONFIGURATIONS__POSTGRES__USER ID=kyoo + - DATABASE__CONFIGURATIONS__POSTGRES__PASSWORD=kyooPassword + - TVDB__APIKEY=${TVDB__APIKEY} + - THEMOVIEDB__APIKEY=${THEMOVIEDB__APIKEY} + ports: + - "5000:5000" + depends_on: + - postgres + volumes: + - kyoo:/var/lib/kyoo + - ./video:/video + postgres: + image: "postgres" + restart: on-failure + environment: + - POSTGRES_USER=kyoo + - POSTGRES_PASSWORD=kyooPassword + volumes: + - db:/var/lib/postgresql/data + +volumes: + kyoo: + db: + diff --git a/docs/start/setting_up.md b/docs/start/setting_up.md index 6a0b82ce..656bd460 100644 --- a/docs/start/setting_up.md +++ b/docs/start/setting_up.md @@ -27,22 +27,6 @@ We are going to take a look at the fields you might want to change to tailor Kyo - ```pluginsPath```: The directory where the plugins are stored - ```transmuxPath```: The directory where the transmux-ed video are stored (used as a cache) - ```transcodePath```: The directory where the transcoded video are stored (used as a cache) - - ```metadataInShow```: A boolean telling if the Movie/Show metadata (posters, extracted subtitles, Chapters) will be stored in the same directory as the video, in an ```Extra``` directory, or in the ```metadataPath```. - For example, if ```metadataInShow``` is true, your file tree wil look something like this: - - ```bash - /my-movies - | - -- My First Movie/ - | - -- My First Movie.mp4 - -- Extra/ - | - -- poster.jpe - -- etc... - ``` - - **Warning** Therefore, if your shows are not in individual folders, it is recommended to set ```metadataInShow``` to ```false```. If you don't, all the shows will share the same metadata we are sure you don't want that ;) - ```database``` - ```enabled```: Which database to use. Either ```sqlite``` (by default) or ```postgres```. SQLite is easier to use & manage if you don't have an SQL server on your machine. However, if you have a large amount of videos, we recommend using Postgres, which is more powerful to manage large databases @@ -70,7 +54,7 @@ We are going to take a look at the fields you might want to change to tailor Kyo - ```tvdb``` - ```apikey```: The API key that will be used to interact with the TVDB's API. See [there](https://thetvdb.com/api-information) to get one -- ```the-moviedb``` +- ```themoviedb``` - ```apikey```: The API key that will be used to interact with TMDB's API. See [there](https://developers.themoviedb.org/3/getting-started/introduction) to get one ## Using a Container diff --git a/front/projects/host/src/app/models/font.ts b/front/projects/host/src/app/models/font.ts new file mode 100644 index 00000000..9f2e93e5 --- /dev/null +++ b/front/projects/host/src/app/models/font.ts @@ -0,0 +1,5 @@ +export interface Font { + slug: string; + file: string; + format: string; +} diff --git a/front/projects/host/src/app/pages/player/player.component.ts b/front/projects/host/src/app/pages/player/player.component.ts index c53be96f..f41e3907 100644 --- a/front/projects/host/src/app/pages/player/player.component.ts +++ b/front/projects/host/src/app/pages/player/player.component.ts @@ -15,7 +15,7 @@ import { DomSanitizer, Title } from "@angular/platform-browser"; import { ActivatedRoute, Event, NavigationCancel, NavigationEnd, NavigationStart, Router } from "@angular/router"; import { OidcSecurityService } from "angular-auth-oidc-client"; import Hls from "hls.js"; -import { ShowService } from "../../services/api.service"; +import { EpisodeService, ShowService } from "../../services/api.service"; import { StartupService } from "../../services/startup.service"; import { getWhatIsSupported, @@ -24,6 +24,7 @@ import { } from "./playbackMethodDetector"; import { AppComponent } from "../../app.component"; import { Track, WatchItem } from "../../models/watch-item"; +import { Font } from "../../models/font"; import SubtitlesOctopus from "libass-wasm/dist/js/subtitles-octopus.js"; import MouseMoveEvent = JQuery.MouseMoveEvent; import TouchMoveEvent = JQuery.TouchMoveEvent; @@ -161,13 +162,12 @@ export class PlayerComponent implements OnInit, OnDestroy, AfterViewInit private hlsPlayer: Hls = new Hls(); private oidcSecurity: OidcSecurityService; constructor(private route: ActivatedRoute, - private sanitizer: DomSanitizer, private snackBar: MatSnackBar, private title: Title, private router: Router, private location: Location, private injector: Injector, - private shows: ShowService, + private episode: EpisodeService, private startup: StartupService) { } @@ -481,18 +481,13 @@ export class PlayerComponent implements OnInit, OnDestroy, AfterViewInit if (subtitle.codec === "ass") { - if (!this.subtitlesManager) - { - const fonts: { [key: string]: string } = await this.shows.getFonts(this.item.showSlug).toPromise(); - this.subtitlesManager = new SubtitlesOctopus({ - video: this.player, - subUrl: `subtitle/${subtitle.slug}`, - fonts: Object.values(fonts), - renderMode: "fast" - }); - } - else - this.subtitlesManager.setTrackByUrl(`subtitle/${subtitle.slug}`); + const fonts: Font[] = await this.episode.getFonts(this.item.slug).toPromise(); + this.subtitlesManager = new SubtitlesOctopus({ + video: this.player, + subUrl: `subtitle/${subtitle.slug}`, + fonts: fonts.map(x => `/api/episode/${this.item.slug}/font/${x.slug}.${x.format}`), + renderMode: "fast" + }); } else if (subtitle.codec === "subrip") { diff --git a/front/projects/host/src/app/services/api.service.ts b/front/projects/host/src/app/services/api.service.ts index 33d18b53..a15aa042 100644 --- a/front/projects/host/src/app/services/api.service.ts +++ b/front/projects/host/src/app/services/api.service.ts @@ -9,6 +9,7 @@ import { LibraryItem } from "../models/resources/library-item"; import { map } from "rxjs/operators"; import { Season } from "../models/resources/season"; import { Episode } from "../models/resources/episode"; +import { Font } from "../models/font"; import { People } from "../models/resources/people"; import { Show } from "../models/resources/show"; import { Studio } from "../models/resources/studio"; @@ -126,6 +127,11 @@ export class EpisodeService extends CrudApi return this.client.get(`/api/seasons/${show}-s${seasonNumber}/episodes${this.ArgsAsQuery(args)}`) .pipe(map(x => Object.assign(new Page(), x))); } + + getFonts(id: string | number): Observable + { + return this.client.get(`/api/episodes/${id}/fonts`); + } } @Injectable({ @@ -177,11 +183,6 @@ export class ShowService extends CrudApi return this.client.get>(`/api/collections/${collection}/shows${this.ArgsAsQuery(args)}`) .pipe(map(x => Object.assign(new Page(), x))); } - - getFonts(id: string | number): Observable<{[font: string]: string}> - { - return this.client.get(`/api/shows/${id}/fonts`); - } } @Injectable({ diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 823f045a..31fdbf88 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -28,10 +28,6 @@ true true - - - - true @@ -48,8 +44,7 @@ - true $(MSBuildThisFileDirectory)../Kyoo.ruleset - + diff --git a/src/Kyoo.Abstractions/Controllers/ITranscoder.cs b/src/Kyoo.Abstractions/Controllers/ITranscoder.cs new file mode 100644 index 00000000..b87a8d5d --- /dev/null +++ b/src/Kyoo.Abstractions/Controllers/ITranscoder.cs @@ -0,0 +1,63 @@ +// 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 . + +using System.Collections.Generic; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Kyoo.Abstractions.Models; +using Microsoft.AspNetCore.Mvc; + +namespace Kyoo.Abstractions.Controllers +{ + /// + /// Transcoder responsible of handling low level video details. + /// + public interface ITranscoder + { + /// + /// Retrieve tracks for a specific episode. + /// Subtitles, chapters and fonts should also be extracted and cached when calling this method. + /// + /// The episode to retrieve tracks for. + /// Should the cache be invalidated and subtitles and others be re-extracted? + /// The list of tracks available for this episode. + Task> ExtractInfos(Episode episode, bool reExtract); + + /// + /// List fonts assosiated with this episode. + /// + /// Th episode to list fonts for. + /// The list of attachements for this epiosode. + Task> ListFonts(Episode episode); + + /// + /// Get the specified font for this episode. + /// + /// The episode to list fonts for. + /// The slug of the specific font to retrive. + /// The with the given slug or null. + [ItemCanBeNull] Task GetFont(Episode episode, string slug); + + /// + /// Transmux the selected episode to hls. + /// + /// The episode to transmux. + /// The master file (m3u8) of the transmuxed hls file. + IActionResult Transmux(Episode episode); + } +} diff --git a/src/Kyoo.Abstractions/Models/Attributes/EditableRelationAttribute.cs b/src/Kyoo.Abstractions/Models/Attributes/EditableRelationAttribute.cs index 5b78b3f8..a94f401b 100644 --- a/src/Kyoo.Abstractions/Models/Attributes/EditableRelationAttribute.cs +++ b/src/Kyoo.Abstractions/Models/Attributes/EditableRelationAttribute.cs @@ -1,4 +1,4 @@ -// Kyoo - A portable and vast media library solution. +// 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. diff --git a/src/Kyoo.Abstractions/Models/Attributes/Serializer/DeserializeIgnoreAttribute.cs b/src/Kyoo.Abstractions/Models/Attributes/Serializer/DeserializeIgnoreAttribute.cs index e1ab8bcc..8b10ee87 100644 --- a/src/Kyoo.Abstractions/Models/Attributes/Serializer/DeserializeIgnoreAttribute.cs +++ b/src/Kyoo.Abstractions/Models/Attributes/Serializer/DeserializeIgnoreAttribute.cs @@ -1,4 +1,4 @@ -// Kyoo - A portable and vast media library solution. +// 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. diff --git a/src/Kyoo.Abstractions/Models/Attributes/Serializer/SerializeIgnoreAttribute.cs b/src/Kyoo.Abstractions/Models/Attributes/Serializer/SerializeIgnoreAttribute.cs index 9fc37750..4a87d3f9 100644 --- a/src/Kyoo.Abstractions/Models/Attributes/Serializer/SerializeIgnoreAttribute.cs +++ b/src/Kyoo.Abstractions/Models/Attributes/Serializer/SerializeIgnoreAttribute.cs @@ -1,4 +1,4 @@ -// Kyoo - A portable and vast media library solution. +// 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. diff --git a/src/Kyoo.Abstractions/Models/Exceptions/HealthException.cs b/src/Kyoo.Abstractions/Models/Exceptions/HealthException.cs new file mode 100644 index 00000000..6b4d86e2 --- /dev/null +++ b/src/Kyoo.Abstractions/Models/Exceptions/HealthException.cs @@ -0,0 +1,47 @@ +// 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 . + +using System; +using System.Runtime.Serialization; + +namespace Kyoo.Abstractions.Models.Exceptions +{ + /// + /// An exception thrown when a part of the app has a fatal issue. + /// + [Serializable] + public class HealthException : Exception + { + /// + /// Create a new with a custom message. + /// + /// The message to use. + public HealthException(string message) + : base(message) + { } + + /// + /// The serialization constructor + /// + /// Serialization infos + /// The serialization context + protected HealthException(SerializationInfo info, StreamingContext context) + : base(info, context) + { } + } +} diff --git a/src/Kyoo.Abstractions/Models/Font.cs b/src/Kyoo.Abstractions/Models/Font.cs new file mode 100644 index 00000000..eafb3bdd --- /dev/null +++ b/src/Kyoo.Abstractions/Models/Font.cs @@ -0,0 +1,67 @@ +// 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 . + +using Kyoo.Abstractions.Models.Attributes; +using Kyoo.Utils; +using PathIO = System.IO.Path; + +namespace Kyoo.Abstractions.Models +{ + /// + /// A font of an . + /// + public class Font + { + /// + /// A human-readable identifier, used in the URL. + /// + public string Slug { get; set; } + + /// + /// The name of the font file (with the extension). + /// + public string File { get; set; } + + /// + /// The format of this font (the extension). + /// + public string Format { get; set; } + + /// + /// The path of the font. + /// + [SerializeIgnore] public string Path { get; set; } + + /// + /// Create a new empty . + /// + public Font() { } + + /// + /// Create a new from a path. + /// + /// The path of the font. + public Font(string path) + { + Slug = Utility.ToSlug(PathIO.GetFileNameWithoutExtension(path)); + Path = path; + File = PathIO.GetFileName(path); + Format = PathIO.GetExtension(path).Replace(".", string.Empty); + } + } +} diff --git a/src/Kyoo.Abstractions/Models/Resources/Episode.cs b/src/Kyoo.Abstractions/Models/Resources/Episode.cs index 42e9ecee..10d5f5b6 100644 --- a/src/Kyoo.Abstractions/Models/Resources/Episode.cs +++ b/src/Kyoo.Abstractions/Models/Resources/Episode.cs @@ -34,7 +34,8 @@ namespace Kyoo.Abstractions.Models public int ID { get; set; } /// - [Computed] public string Slug + [Computed] + public string Slug { get { @@ -45,7 +46,9 @@ namespace Kyoo.Abstractions.Models : null; } - [UsedImplicitly] [NotNull] private set + [UsedImplicitly] + [NotNull] + private set { if (value == null) throw new ArgumentNullException(nameof(value)); diff --git a/src/Kyoo.Abstractions/Models/Resources/Season.cs b/src/Kyoo.Abstractions/Models/Resources/Season.cs index 29d22d44..f07055e8 100644 --- a/src/Kyoo.Abstractions/Models/Resources/Season.cs +++ b/src/Kyoo.Abstractions/Models/Resources/Season.cs @@ -34,7 +34,8 @@ namespace Kyoo.Abstractions.Models public int ID { get; set; } /// - [Computed] public string Slug + [Computed] + public string Slug { get { @@ -43,7 +44,9 @@ namespace Kyoo.Abstractions.Models return $"{ShowSlug ?? Show?.Slug}-s{SeasonNumber}"; } - [UsedImplicitly] [NotNull] private set + [UsedImplicitly] + [NotNull] + private set { Match match = Regex.Match(value ?? string.Empty, @"(?.+)-s(?\d+)"); diff --git a/src/Kyoo.Abstractions/Models/Resources/Track.cs b/src/Kyoo.Abstractions/Models/Resources/Track.cs index 8e41c7ee..59b3746f 100644 --- a/src/Kyoo.Abstractions/Models/Resources/Track.cs +++ b/src/Kyoo.Abstractions/Models/Resources/Track.cs @@ -50,12 +50,6 @@ namespace Kyoo.Abstractions.Models /// The stream is a subtitle. /// Subtitle = 3, - - /// - /// 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. - /// - Attachment = 4 } /// @@ -67,7 +61,8 @@ namespace Kyoo.Abstractions.Models public int ID { get; set; } /// - [Computed] public string Slug + [Computed] + public string Slug { get { @@ -77,7 +72,8 @@ namespace Kyoo.Abstractions.Models return $"{episode}.{Language ?? "und"}{index}{(IsForced ? ".forced" : string.Empty)}.{type}"; } - [UsedImplicitly] private set + [UsedImplicitly] + private set { if (value == null) throw new ArgumentNullException(nameof(value)); @@ -86,8 +82,10 @@ namespace Kyoo.Abstractions.Models if (!match.Success) { - throw new ArgumentException("Invalid track slug. " + - "Format: {episodeSlug}.{language}[-{index}][.forced].{type}[.{extension}]"); + throw new ArgumentException( + "Invalid track slug. " + + "Format: {episodeSlug}.{language}[-{index}][.forced].{type}[.{extension}]" + ); } _episodeSlug = match.Groups["ep"].Value; @@ -148,7 +146,8 @@ namespace Kyoo.Abstractions.Models /// /// The episode that uses this track. /// - [LoadableRelation(nameof(EpisodeID))] public Episode Episode + [LoadableRelation(nameof(EpisodeID))] + public Episode Episode { get => _episode; set diff --git a/src/Kyoo.Abstractions/Models/Resources/WatchedEpisode.cs b/src/Kyoo.Abstractions/Models/Resources/WatchedEpisode.cs index 631c417f..66a11a3c 100644 --- a/src/Kyoo.Abstractions/Models/Resources/WatchedEpisode.cs +++ b/src/Kyoo.Abstractions/Models/Resources/WatchedEpisode.cs @@ -1,4 +1,4 @@ -// Kyoo - A portable and vast media library solution. +// 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. diff --git a/src/Kyoo.Abstractions/Models/Utils/Pagination.cs b/src/Kyoo.Abstractions/Models/Utils/Pagination.cs index e52bbf63..233b317d 100644 --- a/src/Kyoo.Abstractions/Models/Utils/Pagination.cs +++ b/src/Kyoo.Abstractions/Models/Utils/Pagination.cs @@ -1,4 +1,4 @@ -// Kyoo - A portable and vast media library solution. +// 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. diff --git a/src/Kyoo.Abstractions/Models/Utils/Sort.cs b/src/Kyoo.Abstractions/Models/Utils/Sort.cs index e68c8a8e..1aa88673 100644 --- a/src/Kyoo.Abstractions/Models/Utils/Sort.cs +++ b/src/Kyoo.Abstractions/Models/Utils/Sort.cs @@ -1,4 +1,4 @@ -// Kyoo - A portable and vast media library solution. +// 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. diff --git a/src/Kyoo.Abstractions/Models/WatchItem.cs b/src/Kyoo.Abstractions/Models/WatchItem.cs index bedd5df5..7c7afc3b 100644 --- a/src/Kyoo.Abstractions/Models/WatchItem.cs +++ b/src/Kyoo.Abstractions/Models/WatchItem.cs @@ -200,7 +200,7 @@ namespace Kyoo.Abstractions.Models ReleaseDate = ep.ReleaseDate, Path = ep.Path, Images = ep.Show.Images, - Container = PathIO.GetExtension(ep.Path)![1..], + 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(), diff --git a/src/Kyoo.Abstractions/Utility/Merger.cs b/src/Kyoo.Abstractions/Utility/Merger.cs index f8bb8286..237eff06 100644 --- a/src/Kyoo.Abstractions/Utility/Merger.cs +++ b/src/Kyoo.Abstractions/Utility/Merger.cs @@ -170,7 +170,7 @@ namespace Kyoo.Utils Type type = typeof(T); IEnumerable properties = type.GetProperties() .Where(x => x.CanRead && x.CanWrite - && Attribute.GetCustomAttribute(x, typeof(NotMergeableAttribute)) == null); + && Attribute.GetCustomAttribute(x, typeof(NotMergeableAttribute)) == null); foreach (PropertyInfo property in properties) { @@ -221,7 +221,7 @@ namespace Kyoo.Utils Type type = typeof(T); IEnumerable properties = type.GetProperties() .Where(x => x.CanRead && x.CanWrite - && Attribute.GetCustomAttribute(x, typeof(NotMergeableAttribute)) == null); + && Attribute.GetCustomAttribute(x, typeof(NotMergeableAttribute)) == null); if (where != null) properties = properties.Where(where); @@ -293,7 +293,7 @@ namespace Kyoo.Utils Type type = typeof(T); IEnumerable properties = type.GetProperties() .Where(x => x.CanRead && x.CanWrite - && Attribute.GetCustomAttribute(x, typeof(NotMergeableAttribute)) == null); + && Attribute.GetCustomAttribute(x, typeof(NotMergeableAttribute)) == null); if (where != null) properties = properties.Where(where); @@ -325,7 +325,7 @@ namespace Kyoo.Utils property.SetValue(first, newDictionary); } else if (typeof(IEnumerable).IsAssignableFrom(property.PropertyType) - && property.PropertyType != typeof(string)) + && property.PropertyType != typeof(string)) { Type enumerableType = Utility.GetGenericDefinition(property.PropertyType, typeof(IEnumerable<>)) .GenericTypeArguments diff --git a/src/Kyoo.Abstractions/Utility/Utility.cs b/src/Kyoo.Abstractions/Utility/Utility.cs index 04a1f948..bce40f4e 100644 --- a/src/Kyoo.Abstractions/Utility/Utility.cs +++ b/src/Kyoo.Abstractions/Utility/Utility.cs @@ -43,8 +43,8 @@ namespace Kyoo.Utils { if (ex == null) return false; - return ex.Body is MemberExpression || - (ex.Body.NodeType == ExpressionType.Convert && ((UnaryExpression)ex.Body).Operand is MemberExpression); + return ex.Body is MemberExpression + || (ex.Body.NodeType == ExpressionType.Convert && ((UnaryExpression)ex.Body).Operand is MemberExpression); } /// @@ -242,9 +242,12 @@ namespace Kyoo.Utils .Where(x => x.Name == name) .Where(x => x.GetGenericArguments().Length == generics.Length) .Where(x => x.GetParameters().Length == args.Length) - .IfEmpty(() => throw new ArgumentException($"A method named {name} with " + - $"{args.Length} arguments and {generics.Length} generic " + - $"types could not be found on {type.Name}.")) + .IfEmpty(() => + { + throw new ArgumentException($"A method named {name} with " + + $"{args.Length} arguments and {generics.Length} generic " + + $"types could not be found on {type.Name}."); + }) // TODO this won't work but I don't know why. // .Where(x => // { diff --git a/src/Kyoo.Authentication/Controllers/PermissionValidator.cs b/src/Kyoo.Authentication/Controllers/PermissionValidator.cs index 490c7649..4af4d963 100644 --- a/src/Kyoo.Authentication/Controllers/PermissionValidator.cs +++ b/src/Kyoo.Authentication/Controllers/PermissionValidator.cs @@ -154,12 +154,12 @@ namespace Kyoo.Authentication return; default: throw new ArgumentException("Multiple non-matching partial permission attribute " + - "are not supported."); + "are not supported."); } if (permission == null || kind == null) { throw new ArgumentException("The permission type or kind is still missing after two partial " + - "permission attributes, this is unsupported."); + "permission attributes, this is unsupported."); } } diff --git a/src/Kyoo.Core/Controllers/ConfigurationManager.cs b/src/Kyoo.Core/Controllers/ConfigurationManager.cs index 10e0843b..ba3372ad 100644 --- a/src/Kyoo.Core/Controllers/ConfigurationManager.cs +++ b/src/Kyoo.Core/Controllers/ConfigurationManager.cs @@ -163,7 +163,7 @@ namespace Kyoo.Core.Controllers if (typeof(T).IsAssignableFrom(type)) { throw new InvalidCastException($"The type {typeof(T).Name} is not valid for " + - $"a resource of type {type.Name}."); + $"a resource of type {type.Name}."); } return (T)GetValue(path); } diff --git a/src/Kyoo.Core/Controllers/FileSystems/LocalFileSystem.cs b/src/Kyoo.Core/Controllers/FileSystems/LocalFileSystem.cs index 44fe5154..42d77f92 100644 --- a/src/Kyoo.Core/Controllers/FileSystems/LocalFileSystem.cs +++ b/src/Kyoo.Core/Controllers/FileSystems/LocalFileSystem.cs @@ -153,16 +153,12 @@ namespace Kyoo.Core.Controllers /// public Task GetExtraDirectory(T resource) { - if (!_options.CurrentValue.MetadataInShow) - return Task.FromResult(null); - return Task.FromResult(resource switch + string path = resource switch { - Show show => Combine(show.Path, "Extra"), - Season season => Combine(season.Show.Path, "Extra"), - Episode episode => Combine(episode.Show.Path, "Extra"), - Track track => Combine(track.Episode.Show.Path, "Extra"), - _ => null - }); + IResource res => Combine(_options.CurrentValue.MetadataPath, typeof(T).Name.ToLower(), res.Slug), + _ => Combine(_options.CurrentValue.MetadataPath, typeof(T).Name.ToLower()) + }; + return CreateDirectory(path); } /// diff --git a/src/Kyoo.Core/Controllers/RegexIdentifier.cs b/src/Kyoo.Core/Controllers/RegexIdentifier.cs index f4931338..ba9bc19a 100644 --- a/src/Kyoo.Core/Controllers/RegexIdentifier.cs +++ b/src/Kyoo.Core/Controllers/RegexIdentifier.cs @@ -25,8 +25,8 @@ using System.Threading.Tasks; using Kyoo.Abstractions.Controllers; using Kyoo.Abstractions.Models; using Kyoo.Abstractions.Models.Exceptions; +using Kyoo.Core.Models; using Kyoo.Core.Models.Options; -using Kyoo.Core.Models.Watch; using Kyoo.Utils; using Microsoft.Extensions.Options; diff --git a/src/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs b/src/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs index d54d251f..4a139a33 100644 --- a/src/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs +++ b/src/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs @@ -73,16 +73,16 @@ namespace Kyoo.Core.Controllers public Task GetOrDefault(int showID, int seasonNumber, int episodeNumber) { return _database.Episodes.FirstOrDefaultAsync(x => x.ShowID == showID - && x.SeasonNumber == seasonNumber - && x.EpisodeNumber == episodeNumber); + && x.SeasonNumber == seasonNumber + && x.EpisodeNumber == episodeNumber); } /// public Task GetOrDefault(string showSlug, int seasonNumber, int episodeNumber) { return _database.Episodes.FirstOrDefaultAsync(x => x.Show.Slug == showSlug - && x.SeasonNumber == seasonNumber - && x.EpisodeNumber == episodeNumber); + && x.SeasonNumber == seasonNumber + && x.EpisodeNumber == episodeNumber); } /// @@ -107,14 +107,14 @@ namespace Kyoo.Core.Controllers public Task GetAbsolute(int showID, int absoluteNumber) { return _database.Episodes.FirstOrDefaultAsync(x => x.ShowID == showID - && x.AbsoluteNumber == absoluteNumber); + && x.AbsoluteNumber == absoluteNumber); } /// public Task GetAbsolute(string showSlug, int absoluteNumber) { return _database.Episodes.FirstOrDefaultAsync(x => x.Show.Slug == showSlug - && x.AbsoluteNumber == absoluteNumber); + && x.AbsoluteNumber == absoluteNumber); } /// diff --git a/src/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs b/src/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs index 3a85e98a..db36be70 100644 --- a/src/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs +++ b/src/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs @@ -82,14 +82,14 @@ namespace Kyoo.Core.Controllers public Task GetOrDefault(int showID, int seasonNumber) { return _database.Seasons.FirstOrDefaultAsync(x => x.ShowID == showID - && x.SeasonNumber == seasonNumber); + && x.SeasonNumber == seasonNumber); } /// public Task GetOrDefault(string showSlug, int seasonNumber) { return _database.Seasons.FirstOrDefaultAsync(x => x.Show.Slug == showSlug - && x.SeasonNumber == seasonNumber); + && x.SeasonNumber == seasonNumber); } /// diff --git a/src/Kyoo.Core/Controllers/ThumbnailsManager.cs b/src/Kyoo.Core/Controllers/ThumbnailsManager.cs index 0d302ac5..cdde9452 100644 --- a/src/Kyoo.Core/Controllers/ThumbnailsManager.cs +++ b/src/Kyoo.Core/Controllers/ThumbnailsManager.cs @@ -129,18 +129,6 @@ namespace Kyoo.Core.Controllers Images.Trailer => "trailer", _ => $"{imageID}" }; - - switch (item) - { - case Season season: - imageName = $"season-{season.SeasonNumber}-{imageName}"; - break; - case Episode episode: - directory = await _files.CreateDirectory(_files.Combine(directory, "Thumbnails")); - imageName = $"{Path.GetFileNameWithoutExtension(episode.Path)}-{imageName}"; - break; - } - return _files.Combine(directory, imageName); } diff --git a/src/Kyoo.Core/Controllers/Transcoder.cs b/src/Kyoo.Core/Controllers/Transcoder.cs index e23896d9..bff0ddd1 100644 --- a/src/Kyoo.Core/Controllers/Transcoder.cs +++ b/src/Kyoo.Core/Controllers/Transcoder.cs @@ -19,11 +19,15 @@ 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; @@ -48,16 +52,16 @@ namespace Kyoo.Core.Controllers private const string TranscoderPath = "transcoder"; /// - /// Initialize the C library, setup the logger and return the size of a . + /// Initialize the C library, setup the logger and return the size of a . /// - /// The size of a + /// The size of a [DllImport(TranscoderPath, CallingConvention = CallingConvention.Cdecl)] private static extern int init(); /// - /// Initialize the C library, setup the logger and return the size of a . + /// Initialize the C library, setup the logger and return the size of a . /// - /// The size of a + /// The size of a public static int Init() => init(); /// @@ -99,7 +103,7 @@ namespace Kyoo.Core.Controllers /// The size of the returned array. /// The number of tracks in the returned array. /// Should the cache be invalidated and information re-extracted or not? - /// A pointer to an array of + /// A pointer to an array of [DllImport(TranscoderPath, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true)] private static extern IntPtr extract_infos(string path, @@ -109,7 +113,7 @@ namespace Kyoo.Core.Controllers bool reExtract); /// - /// An helper method to free an array of . + /// An helper method to free an array of . /// /// A pointer to the first element of the array /// The number of items in the array. @@ -128,7 +132,7 @@ namespace Kyoo.Core.Controllers path = path.Replace('\\', '/'); outPath = outPath.Replace('\\', '/'); - int size = Marshal.SizeOf(); + int size = Marshal.SizeOf(); IntPtr ptr = extract_infos(path, outPath, out uint arrayLength, out uint trackCount, reExtract); IntPtr streamsPtr = ptr; Track[] tracks; @@ -140,8 +144,8 @@ namespace Kyoo.Core.Controllers int j = 0; for (int i = 0; i < arrayLength; i++) { - Models.Watch.Stream stream = Marshal.PtrToStructure(streamsPtr); - if (stream!.Type != StreamType.Unknown) + FTrack stream = Marshal.PtrToStructure(streamsPtr); + if (stream!.Type != FTrackType.Unknown && stream.Type != FTrackType.Attachment) { tracks[j] = stream.ToTrack(); j++; @@ -174,6 +178,11 @@ namespace Kyoo.Core.Controllers /// private readonly ILogger _logger; + /// + /// if the C library has been checked, otherwise. + /// + private bool _initialized; + /// /// Create a new . /// @@ -187,14 +196,29 @@ namespace Kyoo.Core.Controllers _files = files; _options = options; _logger = logger; + } - if (TranscoderAPI.Init() != Marshal.SizeOf()) - _logger.LogCritical("The transcoder library could not be initialized correctly"); + /// + /// Check if the C library can be used or if there is an issue with it. + /// + /// Should the healthcheck be abborted if the transcoder was already initialized? + /// If the transcoder is corrupted, this exception in thrown. + public void CheckHealth(bool fastStop = false) + { + if (fastStop && _initialized) + return; + if (TranscoderAPI.Init() == Marshal.SizeOf()) + return; + _initialized = true; + _logger.LogCritical("The transcoder library could not be initialized correctly"); + throw new HealthException("The transcoder library is corrupted or invalid."); } /// public async Task> ExtractInfos(Episode episode, bool reExtract) { + CheckHealth(true); + string dir = await _files.GetExtraDirectory(episode); if (dir == null) throw new ArgumentException("Invalid path."); @@ -204,9 +228,31 @@ namespace Kyoo.Core.Controllers ); } + /// + public async Task> ListFonts(Episode episode) + { + string path = _files.Combine(await _files.GetExtraDirectory(episode), "Attachments"); + return (await _files.ListFiles(path)) + .Select(x => new Font(x)) + .ToArray(); + } + + /// + public async Task 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); + } + /// 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")); @@ -260,7 +306,7 @@ namespace Kyoo.Core.Controllers _logger = logger; } -// We use threads so tasks are not always awaited. + // We use threads so tasks are not always awaited. #pragma warning disable 4014 /// @@ -293,27 +339,4 @@ namespace Kyoo.Core.Controllers #pragma warning restore 4014 } } - - /// - /// The transcoder used by the . This is on a different interface than the file system - /// to offset the work. - /// - public interface ITranscoder - { - /// - /// Retrieve tracks for a specific episode. - /// Subtitles, chapters and fonts should also be extracted and cached when calling this method. - /// - /// The episode to retrieve tracks for. - /// Should the cache be invalidated and subtitles and others be re-extracted? - /// The list of tracks available for this episode. - Task> ExtractInfos(Episode episode, bool reExtract); - - /// - /// Transmux the selected episode to hls. - /// - /// The episode to transmux. - /// The master file (m3u8) of the transmuxed hls file. - IActionResult Transmux(Episode episode); - } } diff --git a/src/Kyoo.Core/Models/Stream.cs b/src/Kyoo.Core/Models/FTrack.cs similarity index 67% rename from src/Kyoo.Core/Models/Stream.cs rename to src/Kyoo.Core/Models/FTrack.cs index ff6979d6..cb779e23 100644 --- a/src/Kyoo.Core/Models/Stream.cs +++ b/src/Kyoo.Core/Models/FTrack.cs @@ -21,11 +21,45 @@ using Kyoo.Abstractions.Models; namespace Kyoo.Core.Models.Watch { + /// + /// The list of available stream types. + /// Attachments are only used temporarily by the transcoder but are not stored in a database. + /// This is another enum used internally to communicate with ffmpeg. + /// + public enum FTrackType + { + /// + /// The type of the stream is not known. + /// + Unknown = StreamType.Unknown, + + /// + /// The stream is a video. + /// + Video = StreamType.Video, + + /// + /// The stream is an audio. + /// + Audio = StreamType.Audio, + + /// + /// The stream is a subtitle. + /// + Subtitle = StreamType.Subtitle, + + /// + /// 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. + /// + Attachment + } + /// /// The unmanaged stream that the transcoder will return. /// [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] - public struct Stream + public struct FTrack { /// /// The title of the stream. @@ -60,7 +94,7 @@ namespace Kyoo.Core.Models.Watch /// /// The type of this stream. /// - public StreamType Type; + public FTrackType Type; /// /// Create a track from this stream. @@ -76,7 +110,7 @@ namespace Kyoo.Core.Models.Watch IsDefault = IsDefault, IsForced = IsForced, Path = Path, - Type = Type, + Type = Type < FTrackType.Attachment ? (StreamType)Type : StreamType.Unknown, IsExternal = false }; } diff --git a/src/Kyoo.Core/Models/FileExtensions.cs b/src/Kyoo.Core/Models/FileExtensions.cs index e28c9007..5db0b582 100644 --- a/src/Kyoo.Core/Models/FileExtensions.cs +++ b/src/Kyoo.Core/Models/FileExtensions.cs @@ -20,7 +20,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.IO; -namespace Kyoo.Core.Models.Watch +namespace Kyoo.Core.Models { /// /// A static class allowing one to identify files extensions. diff --git a/src/Kyoo.Core/Models/Options/BasicOptions.cs b/src/Kyoo.Core/Models/Options/BasicOptions.cs index 2fa00249..18633b4a 100644 --- a/src/Kyoo.Core/Models/Options/BasicOptions.cs +++ b/src/Kyoo.Core/Models/Options/BasicOptions.cs @@ -17,7 +17,6 @@ // along with Kyoo. If not, see . using System; -using Kyoo.Abstractions.Models; namespace Kyoo.Core.Models.Options { @@ -57,19 +56,7 @@ namespace Kyoo.Core.Models.Options public string TranscodePath { get; set; } = "cached/transcode"; /// - /// true if the metadata of a show/season/episode should be stored in the same directory as video files, - /// false to save them in a kyoo specific directory. - /// - /// - /// Some file systems might discard this option to store them somewhere else. - /// For example, readonly file systems will probably store them in a kyoo specific directory. - /// - public bool MetadataInShow { get; set; } = true; - - /// - /// The path for metadata if they are not stored near show (see ). - /// Some resources can't be stored near a show and they are stored in this directory - /// (like ). + /// The path where metadata is stored. /// public string MetadataPath { get; set; } = "metadata/"; } diff --git a/src/Kyoo.Core/Tasks/Crawler.cs b/src/Kyoo.Core/Tasks/Crawler.cs index 1a5d6e9e..791c280f 100644 --- a/src/Kyoo.Core/Tasks/Crawler.cs +++ b/src/Kyoo.Core/Tasks/Crawler.cs @@ -25,7 +25,7 @@ using System.Threading.Tasks; using Kyoo.Abstractions.Controllers; using Kyoo.Abstractions.Models; using Kyoo.Abstractions.Models.Attributes; -using Kyoo.Core.Models.Watch; +using Kyoo.Core.Models; using Microsoft.Extensions.Logging; namespace Kyoo.Core.Tasks diff --git a/src/Kyoo.Core/Tasks/RegisterEpisode.cs b/src/Kyoo.Core/Tasks/RegisterEpisode.cs index 0a4d6085..7a9641c8 100644 --- a/src/Kyoo.Core/Tasks/RegisterEpisode.cs +++ b/src/Kyoo.Core/Tasks/RegisterEpisode.cs @@ -150,9 +150,7 @@ namespace Kyoo.Core.Tasks if (!show.IsMovie) episode = await _metadataProvider.Get(episode); progress.Report(70); - episode.Tracks = (await _transcoder.ExtractInfos(episode, false)) - .Where(x => x.Type != StreamType.Attachment) - .ToArray(); + episode.Tracks = await _transcoder.ExtractInfos(episode, false); await _thumbnailsManager.DownloadImages(episode); progress.Report(90); diff --git a/src/Kyoo.Core/Views/Helper/Serializers/JsonSerializerContract.cs b/src/Kyoo.Core/Views/Helper/Serializers/JsonSerializerContract.cs index b1e58c1b..6ed67ac8 100644 --- a/src/Kyoo.Core/Views/Helper/Serializers/JsonSerializerContract.cs +++ b/src/Kyoo.Core/Views/Helper/Serializers/JsonSerializerContract.cs @@ -1,4 +1,4 @@ -// Kyoo - A portable and vast media library solution. +// 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. diff --git a/src/Kyoo.Core/Views/Resources/EpisodeApi.cs b/src/Kyoo.Core/Views/Resources/EpisodeApi.cs index ba11597a..cd568e91 100644 --- a/src/Kyoo.Core/Views/Resources/EpisodeApi.cs +++ b/src/Kyoo.Core/Views/Resources/EpisodeApi.cs @@ -47,20 +47,34 @@ namespace Kyoo.Core.Api /// private readonly ILibraryManager _libraryManager; + /// + /// The transcoder used to retrive fonts. + /// + private readonly ITranscoder _transcoder; + + /// + /// The file system used to send fonts. + /// + private readonly IFileSystem _files; + /// /// Create a new . /// /// /// The library manager used to modify or retrieve information in the data store. /// + /// The transcoder used to retrive fonts /// The file manager used to send images. /// The thumbnail manager used to retrieve images paths. public EpisodeApi(ILibraryManager libraryManager, + ITranscoder transcoder, IFileSystem files, IThumbnailsManager thumbnails) : base(libraryManager.EpisodeRepository, files, thumbnails) { _libraryManager = libraryManager; + _transcoder = transcoder; + _files = files; } /// @@ -158,5 +172,60 @@ namespace Kyoo.Core.Api return BadRequest(new RequestError(ex.Message)); } } + + /// + /// List fonts + /// + /// + /// List available fonts for this episode. + /// + /// The ID or slug of the . + /// An object containing the name of the font followed by the url to retrieve it. + [HttpGet("{identifier:id}/fonts")] + [HttpGet("{identifier:id}/font", Order = AlternativeRoute)] + [PartialPermission(Kind.Read)] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task>> GetFonts(Identifier identifier) + { + Episode episode = await identifier.Match( + id => _libraryManager.GetOrDefault(id), + slug => _libraryManager.GetOrDefault(slug) + ); + if (episode == null) + return NotFound(); + return Ok(await _transcoder.ListFonts(episode)); + } + + /// + /// Get font + /// + /// + /// Get a font file that is used in subtitles of this episode. + /// + /// The ID or slug of the . + /// The slug of the font to retrieve. + /// A page of collections. + /// No show with the given ID/slug could be found or the font does not exist. + [HttpGet("{identifier:id}/fonts/{slug}")] + [HttpGet("{identifier:id}/font/{slug}", Order = AlternativeRoute)] + [PartialPermission(Kind.Read)] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task GetFont(Identifier identifier, string slug) + { + Episode episode = await identifier.Match( + id => _libraryManager.GetOrDefault(id), + slug => _libraryManager.GetOrDefault(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); + } } } diff --git a/src/Kyoo.Core/Views/Resources/ShowApi.cs b/src/Kyoo.Core/Views/Resources/ShowApi.cs index ccbf00e5..23261724 100644 --- a/src/Kyoo.Core/Views/Resources/ShowApi.cs +++ b/src/Kyoo.Core/Views/Resources/ShowApi.cs @@ -18,7 +18,6 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Linq.Expressions; using System.Threading.Tasks; @@ -28,10 +27,8 @@ using Kyoo.Abstractions.Models.Attributes; using Kyoo.Abstractions.Models.Exceptions; using Kyoo.Abstractions.Models.Permissions; using Kyoo.Abstractions.Models.Utils; -using Kyoo.Core.Models.Options; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Options; using static Kyoo.Abstractions.Models.Utils.Constants; namespace Kyoo.Core.Api @@ -53,17 +50,6 @@ namespace Kyoo.Core.Api /// private readonly ILibraryManager _libraryManager; - /// - /// The file manager used to send images and fonts. - /// - private readonly IFileSystem _files; - - /// - /// The base URL of Kyoo. This will be used to create links for images and - /// . - /// - private readonly Uri _baseURL; - /// /// Create a new . /// @@ -72,18 +58,12 @@ namespace Kyoo.Core.Api /// /// The file manager used to send images and fonts. /// The thumbnail manager used to retrieve images paths. - /// - /// Options used to retrieve the base URL of Kyoo. - /// public ShowApi(ILibraryManager libraryManager, IFileSystem files, - IThumbnailsManager thumbs, - IOptions options) + IThumbnailsManager thumbs) : base(libraryManager.ShowRepository, files, thumbs) { _libraryManager = libraryManager; - _files = files; - _baseURL = options.Value.PublicUrl; } /// @@ -375,65 +355,5 @@ namespace Kyoo.Core.Api return BadRequest(new RequestError(ex.Message)); } } - - /// - /// List fonts - /// - /// - /// List available fonts for this show. - /// - /// The ID or slug of the . - /// An object containing the name of the font followed by the url to retrieve it. - [HttpGet("{identifier:id}/fonts")] - [HttpGet("{identifier:id}/font", Order = AlternativeRoute)] - [PartialPermission(Kind.Read)] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task>> GetFonts(Identifier identifier) - { - Show show = await identifier.Match( - id => _libraryManager.GetOrDefault(id), - slug => _libraryManager.GetOrDefault(slug) - ); - if (show == null) - return NotFound(); - string path = _files.Combine(await _files.GetExtraDirectory(show), "Attachments"); - return (await _files.ListFiles(path)) - .ToDictionary( - Path.GetFileNameWithoutExtension, - x => $"{_baseURL}api/shows/{identifier}/fonts/{Path.GetFileName(x)}" - ); - } - - /// - /// Get font - /// - /// - /// Get a font file that is used in subtitles of this show. - /// - /// The ID or slug of the . - /// The name of the font to retrieve (with it's file extension). - /// A page of collections. - /// The font name is invalid. - /// No show with the given ID/slug could be found or the font does not exist. - [HttpGet("{identifier:id}/fonts/{font}")] - [HttpGet("{identifier:id}/font/{font}", Order = AlternativeRoute)] - [PartialPermission(Kind.Read)] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task GetFont(Identifier identifier, string font) - { - if (font.Contains('/') || font.Contains('\\')) - return BadRequest(new RequestError("Invalid font name.")); - Show show = await identifier.Match( - id => _libraryManager.GetOrDefault(id), - slug => _libraryManager.GetOrDefault(slug) - ); - if (show == null) - return NotFound(); - string path = _files.Combine(await _files.GetExtraDirectory(show), "Attachments", font); - return _files.FileResult(path); - } } } diff --git a/src/Kyoo.Host.Generic/Contollers/FileSystemComposite.cs b/src/Kyoo.Host.Generic/Contollers/FileSystemComposite.cs index 0a9eec86..d2445d23 100644 --- a/src/Kyoo.Host.Generic/Contollers/FileSystemComposite.cs +++ b/src/Kyoo.Host.Generic/Contollers/FileSystemComposite.cs @@ -44,12 +44,6 @@ namespace Kyoo.Host.Generic.Controllers /// private readonly ICollection, FileSystemMetadataAttribute>> _fileSystems; - /// - /// The library manager used to load shows to retrieve their path - /// (only if the option is set to metadata in show) - /// - private readonly ILibraryManager _libraryManager; - /// /// Options to check if the metadata should be kept in the show directory or in a kyoo's directory. /// @@ -60,14 +54,11 @@ namespace Kyoo.Host.Generic.Controllers /// metadata. /// /// The list of filesystem mapped to their metadata. - /// The library manager used to load shows to retrieve their path. /// The options to use. public FileSystemComposite(ICollection, FileSystemMetadataAttribute>> fileSystems, - ILibraryManager libraryManager, IOptionsMonitor options) { _fileSystems = fileSystems; - _libraryManager = libraryManager; _options = options; } @@ -178,41 +169,10 @@ namespace Kyoo.Host.Generic.Controllers } /// - public async Task GetExtraDirectory(T resource) + public Task GetExtraDirectory(T resource) { - switch (resource) - { - case Season season: - await _libraryManager.Load(season, x => x.Show); - break; - case Episode episode: - await _libraryManager.Load(episode, x => x.Show); - break; - case Track track: - await _libraryManager.Load(track, x => x.Episode); - await _libraryManager.Load(track.Episode, x => x.Show); - break; - } - - IFileSystem fs = resource switch - { - Show show => _GetFileSystemForPath(show.Path, out string _), - Season season => _GetFileSystemForPath(season.Show.Path, out string _), - Episode episode => _GetFileSystemForPath(episode.Show.Path, out string _), - Track track => _GetFileSystemForPath(track.Episode.Show.Path, out string _), - _ => _GetFileSystemForPath(_options.CurrentValue.MetadataPath, out string _) - }; - string path = await fs.GetExtraDirectory(resource) - ?? resource switch - { - IResource res => Combine( - _options.CurrentValue.MetadataPath, - typeof(T).Name.ToLower(), - res.Slug - ), - _ => Combine(_options.CurrentValue.MetadataPath, typeof(T).Name.ToLower()) - }; - return await CreateDirectory(path); + IFileSystem fs = _GetFileSystemForPath(_options.CurrentValue.MetadataPath, out string path); + return fs.GetExtraDirectory(resource); } /// diff --git a/src/Kyoo.Host.Generic/settings.json b/src/Kyoo.Host.Generic/settings.json index 6c472a6e..bf5e6a4e 100644 --- a/src/Kyoo.Host.Generic/settings.json +++ b/src/Kyoo.Host.Generic/settings.json @@ -76,7 +76,7 @@ "tvdb": { "apiKey": "" }, - "the-moviedb": { + "themoviedb": { "apiKey": "" } } diff --git a/src/Kyoo.SqLite/SqLiteContext.cs b/src/Kyoo.SqLite/SqLiteContext.cs index a19151bf..3a0cf6b9 100644 --- a/src/Kyoo.SqLite/SqLiteContext.cs +++ b/src/Kyoo.SqLite/SqLiteContext.cs @@ -184,8 +184,9 @@ namespace Kyoo.SqLite /// protected override bool IsDuplicateException(Exception ex) { - return ex.InnerException is SqliteException { SqliteExtendedErrorCode: 2067 /* SQLITE_CONSTRAINT_UNIQUE */ } - or SqliteException { SqliteExtendedErrorCode: 1555 /* SQLITE_CONSTRAINT_PRIMARYKEY */ }; + return ex.InnerException + is SqliteException { SqliteExtendedErrorCode: 2067 /* SQLITE_CONSTRAINT_UNIQUE */ } + or SqliteException { SqliteExtendedErrorCode: 1555 /* SQLITE_CONSTRAINT_PRIMARYKEY */ }; } /// diff --git a/src/Kyoo.Swagger/SwaggerModule.cs b/src/Kyoo.Swagger/SwaggerModule.cs index d5b51e71..fafb1281 100644 --- a/src/Kyoo.Swagger/SwaggerModule.cs +++ b/src/Kyoo.Swagger/SwaggerModule.cs @@ -1,4 +1,4 @@ -// Kyoo - A portable and vast media library solution. +// 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. diff --git a/src/Kyoo.TheMovieDb/PluginTmdb.cs b/src/Kyoo.TheMovieDb/PluginTmdb.cs index 72d16f19..e20149b5 100644 --- a/src/Kyoo.TheMovieDb/PluginTmdb.cs +++ b/src/Kyoo.TheMovieDb/PluginTmdb.cs @@ -48,7 +48,7 @@ namespace Kyoo.TheMovieDb if (!Enabled) { logger.LogWarning("No API key configured for TheMovieDB provider. " + - "To enable TheMovieDB, specify one in the setting the-moviedb:APIKEY "); + "To enable TheMovieDB, specify one in the setting themoviedb:APIKEY"); } } @@ -62,7 +62,7 @@ namespace Kyoo.TheMovieDb public string Description => "A metadata provider for TheMovieDB."; /// - public bool Enabled => !string.IsNullOrEmpty(_configuration.GetValue("the-moviedb:apikey")); + public bool Enabled => !string.IsNullOrEmpty(_configuration.GetValue("themoviedb:apikey")); /// public Dictionary Configuration => new() diff --git a/src/Kyoo.TheMovieDb/TheMovieDbOptions.cs b/src/Kyoo.TheMovieDb/TheMovieDbOptions.cs index 7167c902..a4ddcc2c 100644 --- a/src/Kyoo.TheMovieDb/TheMovieDbOptions.cs +++ b/src/Kyoo.TheMovieDb/TheMovieDbOptions.cs @@ -26,7 +26,7 @@ namespace Kyoo.TheMovieDb.Models /// /// The path to get this option from the root configuration. /// - public const string Path = "the-moviedb"; + public const string Path = "themoviedb"; /// /// The api key of TheMovieDb. diff --git a/src/Kyoo.Transcoder b/src/Kyoo.Transcoder index 7bae8def..913c8e98 160000 --- a/src/Kyoo.Transcoder +++ b/src/Kyoo.Transcoder @@ -1 +1 @@ -Subproject commit 7bae8def39ace7bab481efea4825c4802e9e1f31 +Subproject commit 913c8e986e220ea48749b815593cdd10b2acb8de diff --git a/tests/Kyoo.Tests/Database/TestSample.cs b/tests/Kyoo.Tests/Database/TestSample.cs index a7327753..634c104a 100644 --- a/tests/Kyoo.Tests/Database/TestSample.cs +++ b/tests/Kyoo.Tests/Database/TestSample.cs @@ -180,8 +180,8 @@ namespace Kyoo.Tests "We Still Don't Know the Name of the Flower We Saw That Day." }, Overview = "When Yadomi Jinta was a child, he was a central piece in a group of close friends. " + - "In time, however, these childhood friends drifted apart, and when they became high " + - "school students, they had long ceased to think of each other as friends.", + "In time, however, these childhood friends drifted apart, and when they became high " + + "school students, they had long ceased to think of each other as friends.", Status = Status.Finished, StudioID = 1, StartAir = new DateTime(2011, 1, 1), @@ -243,7 +243,7 @@ namespace Kyoo.Tests typeof(Track), () => { - Track ret = new() + Track ret = new() { ID = 1, EpisodeID = 1, diff --git a/tests/Kyoo.Tests/Transcoder/TranscoderTests.cs b/tests/Kyoo.Tests/Transcoder/TranscoderTests.cs new file mode 100644 index 00000000..3d0a38d3 --- /dev/null +++ b/tests/Kyoo.Tests/Transcoder/TranscoderTests.cs @@ -0,0 +1,107 @@ +// 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 . + +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Kyoo.Abstractions.Controllers; +using Kyoo.Abstractions.Models; +using Kyoo.Core.Models.Options; +using Microsoft.Extensions.Options; +using Moq; +using Xunit; +using Xunit.Abstractions; + +using KTranscoder = Kyoo.Core.Controllers.Transcoder; + +namespace Kyoo.Tests.Transcoder +{ + public class TranscoderTests + { + private readonly Mock _files; + private readonly ITranscoder _transcoder; + + public TranscoderTests(ITestOutputHelper output) + { + _files = new Mock(); + _transcoder = new KTranscoder( + _files.Object, + Options.Create(new BasicOptions()), + output.BuildLoggerFor() + ); + } + + [Fact] + public async Task ListFontsTest() + { + Episode episode = TestSample.Get(); + _files.Setup(x => x.ListFiles(It.IsAny(), System.IO.SearchOption.TopDirectoryOnly)) + .ReturnsAsync(new[] { "font.ttf", "font.TTF", "toto.ttf" }); + ICollection fonts = await _transcoder.ListFonts(episode); + List fontsFiles = fonts.Select(x => x.File).ToList(); + Assert.Equal(3, fonts.Count); + Assert.Contains("font.TTF", fontsFiles); + Assert.Contains("font.ttf", fontsFiles); + Assert.Contains("toto.ttf", fontsFiles); + } + + [Fact] + public async Task GetNoFontTest() + { + Episode episode = TestSample.Get(); + _files.Setup(x => x.GetExtraDirectory(It.IsAny())) + .ReturnsAsync("/path"); + _files.Setup(x => x.ListFiles(It.IsAny(), System.IO.SearchOption.TopDirectoryOnly)) + .ReturnsAsync(new[] { "font.ttf", "font.TTF", "toto.ttf" }); + Font font = await _transcoder.GetFont(episode, "toto.ttf"); + Assert.Null(font); + } + + [Fact] + public async Task GetFontTest() + { + Episode episode = TestSample.Get(); + _files.Setup(x => x.GetExtraDirectory(It.IsAny())) + .ReturnsAsync("/path"); + _files.Setup(x => x.ListFiles(It.IsAny(), System.IO.SearchOption.TopDirectoryOnly)) + .ReturnsAsync(new[] { "/path/font.ttf", "/path/font.TTF", "/path/toto.ttf" }); + Font font = await _transcoder.GetFont(episode, "toto"); + Assert.NotNull(font); + Assert.Equal("toto.ttf", font.File); + Assert.Equal("toto", font.Slug); + Assert.Equal("ttf", font.Format); + Assert.Equal("/path/toto.ttf", font.Path); + } + + [Fact] + public async Task GetFontNoExtensionTest() + { + Episode episode = TestSample.Get(); + _files.Setup(x => x.GetExtraDirectory(It.IsAny())) + .ReturnsAsync("/path"); + _files.Setup(x => x.ListFiles(It.IsAny(), System.IO.SearchOption.TopDirectoryOnly)) + .ReturnsAsync(new[] { "/path/font", "/path/toto.ttf" }); + Font font = await _transcoder.GetFont(episode, "font"); + Assert.NotNull(font); + Assert.Equal("font", font.File); + Assert.Equal("font", font.Slug); + Assert.Equal("", font.Format); + Assert.Equal("/path/font", font.Path); + } + } +}