diff --git a/back/src/Kyoo.Abstractions/Models/Font.cs b/back/src/Kyoo.Abstractions/Models/Font.cs index eafb3bdd..ca9c4680 100644 --- a/back/src/Kyoo.Abstractions/Models/Font.cs +++ b/back/src/Kyoo.Abstractions/Models/Font.cs @@ -25,7 +25,7 @@ namespace Kyoo.Abstractions.Models /// /// A font of an . /// - public class Font + public class Font : ILink { /// /// A human-readable identifier, used in the URL. @@ -47,6 +47,9 @@ namespace Kyoo.Abstractions.Models /// [SerializeIgnore] public string Path { get; set; } + /// + public object Link { get; init; } + /// /// Create a new empty . /// @@ -56,12 +59,14 @@ namespace Kyoo.Abstractions.Models /// Create a new from a path. /// /// The path of the font. - public Font(string path) + /// The slug of the episode that contains this font. + 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}"; } } } diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/ILink.cs b/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/ILink.cs new file mode 100644 index 00000000..5c146786 --- /dev/null +++ b/back/src/Kyoo.Abstractions/Models/Resources/Interfaces/ILink.cs @@ -0,0 +1,31 @@ +// 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 . + +namespace Kyoo.Abstractions.Models +{ + /// + /// An interface to represent resources that should have a link field in their return values (like videos). + /// + public interface ILink + { + /// + /// The link to return, in most cases this should be a string. + /// + public object Link { get; } + } +} diff --git a/back/src/Kyoo.Abstractions/Models/Resources/Track.cs b/back/src/Kyoo.Abstractions/Models/Resources/Track.cs index 59b3746f..d243c0eb 100644 --- a/back/src/Kyoo.Abstractions/Models/Resources/Track.cs +++ b/back/src/Kyoo.Abstractions/Models/Resources/Track.cs @@ -55,7 +55,7 @@ namespace Kyoo.Abstractions.Models /// /// A video, audio or subtitle track for an episode. /// - public class Track : IResource + public class Track : IResource, ILink { /// public int ID { get; set; } @@ -66,9 +66,9 @@ namespace Kyoo.Abstractions.Models { get { - string type = Type.ToString().ToLower(); + string type = Type.ToString().ToLowerInvariant(); string index = TrackIndex != 0 ? $"-{TrackIndex}" : string.Empty; - string episode = _episodeSlug ?? Episode?.Slug ?? EpisodeID.ToString(); + string episode = _episodeSlug ?? Episode?.Slug ?? EpisodeID.ToString(CultureInfo.InvariantCulture); return $"{episode}.{Language ?? "und"}{index}{(IsForced ? ".forced" : string.Empty)}.{type}"; } @@ -92,7 +92,7 @@ namespace Kyoo.Abstractions.Models Language = match.Groups["lang"].Value; if (Language == "und") Language = null; - TrackIndex = match.Groups["index"].Success ? int.Parse(match.Groups["index"].Value) : 0; + TrackIndex = match.Groups["index"].Success ? int.Parse(match.Groups["index"].Value, CultureInfo.InvariantCulture) : 0; IsForced = match.Groups["forced"].Success; Type = Enum.Parse(match.Groups["type"].Value, true); } @@ -198,6 +198,9 @@ namespace Kyoo.Abstractions.Models /// [SerializeIgnore] private Episode _episode; + /// + public object Link => Type == StreamType.Subtitle ? $"/subtitle/{Slug}" : null; + // Converting mkv track language to c# system language tag. private static string _GetLanguage(string mkvLanguage) { diff --git a/back/src/Kyoo.Abstractions/Models/WatchItem.cs b/back/src/Kyoo.Abstractions/Models/WatchItem.cs index 70e61836..e737aa74 100644 --- a/back/src/Kyoo.Abstractions/Models/WatchItem.cs +++ b/back/src/Kyoo.Abstractions/Models/WatchItem.cs @@ -33,7 +33,7 @@ namespace Kyoo.Abstractions.Models /// Information about tracks and display information that could be used by the player. /// This contains mostly data from an with another form. /// - public class WatchItem : CustomTypeDescriptor, IThumbnails + public class WatchItem : CustomTypeDescriptor, IThumbnails, ILink { /// /// The ID of the episode associated with this item. @@ -136,6 +136,13 @@ namespace Kyoo.Abstractions.Models /// public ICollection Chapters { get; set; } + /// + public object Link => new + { + Direct = $"/video/direct/{Slug}", + Transmux = $"/video/transmux/{Slug}/master.m3u8", + }; + /// /// Create a from an . /// diff --git a/back/src/Kyoo.Core/Controllers/Transcoder.cs b/back/src/Kyoo.Core/Controllers/Transcoder.cs index b684271d..04bb3414 100644 --- a/back/src/Kyoo.Core/Controllers/Transcoder.cs +++ b/back/src/Kyoo.Core/Controllers/Transcoder.cs @@ -233,7 +233,7 @@ namespace Kyoo.Core.Controllers { string path = _files.Combine(await _files.GetExtraDirectory(episode), "Attachments"); return (await _files.ListFiles(path)) - .Select(x => new Font(x)) + .Select(x => new Font(x, episode.Slug)) .ToArray(); } @@ -245,7 +245,7 @@ namespace Kyoo.Core.Controllers .FirstOrDefault(x => Utility.ToSlug(Path.GetFileNameWithoutExtension(x)) == slug); if (font == null) return null; - return new Font(font); + return new Font(font, episode.Slug); } /// diff --git a/front/src/models/resources/watch-item.ts b/front/src/models/resources/watch-item.ts index 023353d1..8131a47d 100644 --- a/front/src/models/resources/watch-item.ts +++ b/front/src/models/resources/watch-item.ts @@ -20,7 +20,7 @@ import { z } from "zod"; import { zdate } from "~/utils/zod"; -import { ResourceP, ImagesP } from "../traits"; +import { ResourceP, ImagesP, imageFn } from "../traits"; import { EpisodeP } from "./episode"; /** @@ -59,6 +59,10 @@ export const TrackP = ResourceP.extend({ * A user-friendly name for this track. It does not include the track type. */ displayName: z.string(), + /* + * The url of this track (only if this is a subtitle).. + */ + link: z.string().transform(imageFn).nullable(), }); export type Track = z.infer; @@ -78,7 +82,7 @@ export const FontP = z.object({ /* * The url of the font. */ - link: z.string(), + link: z.string().transform(imageFn), }); export type Font = z.infer; @@ -105,13 +109,6 @@ const WatchMovieP = z.preprocess( if (!x) return x; x.name = x.title; - x.link = { - direct: `/api/video/${x.slug}`, - }; - x.fonts = x.fonts?.map((y: Font) => { - y.link = `/api/watch/${x.slug}/font/${y.slug}.${y.format}`; - return y; - }) return x; }, ImagesP.extend({ @@ -155,7 +152,8 @@ const WatchMovieP = z.preprocess( * The links to the videos of this watch item. */ link: z.object({ - direct: z.string(), + direct: z.string().transform(imageFn), + transmux: z.string().transform(imageFn), }), }), ); diff --git a/front/src/models/traits/images.ts b/front/src/models/traits/images.ts index de3e0a25..a1273237 100644 --- a/front/src/models/traits/images.ts +++ b/front/src/models/traits/images.ts @@ -20,7 +20,7 @@ import { z } from "zod"; -const imageFn = (url: string) => (url.startsWith("/api") ? url : `/api${url}`); +export const imageFn = (url: string) => (url.startsWith("/api") ? url : `/api${url}`); export const ImagesP = z.object({ /** diff --git a/front/src/player/state.tsx b/front/src/player/state.tsx index 9db11d02..99e634e9 100644 --- a/front/src/player/state.tsx +++ b/front/src/player/state.tsx @@ -21,7 +21,7 @@ import { BoxProps } from "@mui/material"; import { atom, useSetAtom } from "jotai"; import { useRouter } from "next/router"; -import { RefObject, useCallback, useEffect, useRef, useState } from "react"; +import { RefObject, useEffect, useRef } from "react"; import { Font, Track } from "~/models/resources/watch-item"; import { bakedAtom } from "~/utils/jotai-utils"; // @ts-ignore @@ -169,7 +169,7 @@ export const [_subtitleAtom, subtitleAtom] = bakedAtom { @@ -184,7 +184,7 @@ export const [_subtitleAtom, subtitleAtom] = bakedAtom x.link),