Add liks in the API for videos, fonts and subtitle

This commit is contained in:
Zoe Roux 2022-10-08 14:44:44 +09:00
parent 0652ffef68
commit edc33f3c37
8 changed files with 67 additions and 23 deletions

View File

@ -25,7 +25,7 @@ namespace Kyoo.Abstractions.Models
/// <summary>
/// A font of an <see cref="Episode"/>.
/// </summary>
public class Font
public class Font : ILink
{
/// <summary>
/// A human-readable identifier, used in the URL.
@ -47,6 +47,9 @@ namespace Kyoo.Abstractions.Models
/// </summary>
[SerializeIgnore] public string Path { get; set; }
/// <inheritdoc/>
public object Link { get; init; }
/// <summary>
/// Create a new empty <see cref="Font"/>.
/// </summary>
@ -56,12 +59,14 @@ namespace Kyoo.Abstractions.Models
/// Create a new <see cref="Font"/> from a path.
/// </summary>
/// <param name="path">The path of the font.</param>
public Font(string path)
/// <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}";
}
}
}

View File

@ -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 <https://www.gnu.org/licenses/>.
namespace Kyoo.Abstractions.Models
{
/// <summary>
/// An interface to represent resources that should have a link field in their return values (like videos).
/// </summary>
public interface ILink
{
/// <summary>
/// The link to return, in most cases this should be a string.
/// </summary>
public object Link { get; }
}
}

View File

@ -55,7 +55,7 @@ namespace Kyoo.Abstractions.Models
/// <summary>
/// A video, audio or subtitle track for an episode.
/// </summary>
public class Track : IResource
public class Track : IResource, ILink
{
/// <inheritdoc />
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<StreamType>(match.Groups["type"].Value, true);
}
@ -198,6 +198,9 @@ namespace Kyoo.Abstractions.Models
/// </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)
{

View File

@ -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 <see cref="Episode"/> with another form.
/// </summary>
public class WatchItem : CustomTypeDescriptor, IThumbnails
public class WatchItem : CustomTypeDescriptor, IThumbnails, ILink
{
/// <summary>
/// The ID of the episode associated with this item.
@ -136,6 +136,13 @@ namespace Kyoo.Abstractions.Models
/// </summary>
public ICollection<Chapter> Chapters { get; set; }
/// <inheritdoc/>
public object Link => new
{
Direct = $"/video/direct/{Slug}",
Transmux = $"/video/transmux/{Slug}/master.m3u8",
};
/// <summary>
/// Create a <see cref="WatchItem"/> from an <see cref="Episode"/>.
/// </summary>

View File

@ -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);
}
/// <inheritdoc />

View File

@ -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<typeof TrackP>;
@ -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<typeof FontP>;
@ -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),
}),
}),
);

View File

@ -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({
/**

View File

@ -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<Track | null, { track: Tr
track.kind = "subtitles";
track.label = value.track.displayName;
if (value.track.language) track.srclang = value.track.language;
track.src = `subtitle/${value.track.slug}.vtt`;
track.src = value.track.link!;
track.className = "subtitle_container";
track.default = true;
track.onload = () => {
@ -184,7 +184,7 @@ export const [_subtitleAtom, subtitleAtom] = bakedAtom<Track | null, { track: Tr
suboctoAtom,
new SubtitleOctopus({
video: player.current,
subUrl: `/api/subtitle/${value.track.slug}`,
subUrl: value.track.link!,
workerUrl: "/_next/static/chunks/subtitles-octopus-worker.js",
legacyWorkerUrl: "/_next/static/chunks/subtitles-octopus-worker-legacy.js",
fonts: value.fonts?.map((x) => x.link),