mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-08-07 09:01:29 -04:00
Cleanup video info type
This commit is contained in:
parent
430843bb15
commit
24a170a859
@ -12,3 +12,4 @@ export * from "./utils/genre";
|
|||||||
export * from "./utils/images";
|
export * from "./utils/images";
|
||||||
export * from "./utils/page";
|
export * from "./utils/page";
|
||||||
export * from "./video";
|
export * from "./video";
|
||||||
|
export * from "./video-info";
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
export const QualityP = z
|
|
||||||
.union([
|
|
||||||
z.literal("original"),
|
|
||||||
z.literal("8k"),
|
|
||||||
z.literal("4k"),
|
|
||||||
z.literal("1440p"),
|
|
||||||
z.literal("1080p"),
|
|
||||||
z.literal("720p"),
|
|
||||||
z.literal("480p"),
|
|
||||||
z.literal("360p"),
|
|
||||||
z.literal("240p"),
|
|
||||||
])
|
|
||||||
.default("original");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A Video Quality Enum.
|
|
||||||
*/
|
|
||||||
export type Quality = z.infer<typeof QualityP>;
|
|
@ -1,182 +0,0 @@
|
|||||||
import { z } from "zod";
|
|
||||||
import { QualityP } from "./quality";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A audio or subtitle track.
|
|
||||||
*/
|
|
||||||
export const TrackP = z.object({
|
|
||||||
/**
|
|
||||||
* The index of this track on the episode.
|
|
||||||
* NOTE: external subtitles can have a null index
|
|
||||||
*/
|
|
||||||
index: z.number().nullable(),
|
|
||||||
/**
|
|
||||||
* The title of the stream.
|
|
||||||
*/
|
|
||||||
title: z.string().nullable(),
|
|
||||||
/**
|
|
||||||
* The language of this stream (as a ISO-639-2 language code)
|
|
||||||
*/
|
|
||||||
language: z.string().nullable(),
|
|
||||||
/**
|
|
||||||
* The codec of this stream.
|
|
||||||
*/
|
|
||||||
codec: z.string(),
|
|
||||||
/**
|
|
||||||
* Is this stream the default one of it's type?
|
|
||||||
*/
|
|
||||||
isDefault: z.boolean(),
|
|
||||||
/**
|
|
||||||
* Is this stream tagged as forced?
|
|
||||||
* NOTE: not available for videos
|
|
||||||
*/
|
|
||||||
isForced: z.boolean().optional(),
|
|
||||||
});
|
|
||||||
export type Track = z.infer<typeof TrackP>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A Video track
|
|
||||||
*/
|
|
||||||
export const VideoP = TrackP.extend({
|
|
||||||
/**
|
|
||||||
* The Quality of the Video
|
|
||||||
* E.g. "1080p"
|
|
||||||
*/
|
|
||||||
quality: QualityP,
|
|
||||||
/**
|
|
||||||
* The Width of the Video Frame
|
|
||||||
* E.g. 1424
|
|
||||||
*/
|
|
||||||
width: z.number(),
|
|
||||||
/**
|
|
||||||
* The Height of the Video Frame
|
|
||||||
* E.g. 1072
|
|
||||||
*/
|
|
||||||
height: z.number(),
|
|
||||||
/**
|
|
||||||
* The Bitrate (in bits/seconds) of the video track
|
|
||||||
* E.g. 2693245
|
|
||||||
*/
|
|
||||||
bitrate: z.number(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export type Video = z.infer<typeof VideoP>;
|
|
||||||
|
|
||||||
export const AudioP = TrackP;
|
|
||||||
export type Audio = z.infer<typeof AudioP>;
|
|
||||||
|
|
||||||
export const SubtitleP = TrackP.extend({
|
|
||||||
/*
|
|
||||||
* The url of this track (only if this is a subtitle)..
|
|
||||||
*/
|
|
||||||
link: z.string().nullable(),
|
|
||||||
/*
|
|
||||||
* Is this an external subtitle (as in stored in a different file)
|
|
||||||
*/
|
|
||||||
isExternal: z.boolean(),
|
|
||||||
/**
|
|
||||||
* Is this a hearing impaired subtitle?
|
|
||||||
*/
|
|
||||||
isHearingImpaired: z.boolean(),
|
|
||||||
});
|
|
||||||
export type Subtitle = z.infer<typeof SubtitleP>;
|
|
||||||
|
|
||||||
export const ChapterP = z.object({
|
|
||||||
/**
|
|
||||||
* The start time of the chapter (in second from the start of the episode).
|
|
||||||
*/
|
|
||||||
startTime: z.number(),
|
|
||||||
/**
|
|
||||||
* The end time of the chapter (in second from the start of the episode).
|
|
||||||
*/
|
|
||||||
endTime: z.number(),
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
name: z.string(),
|
|
||||||
});
|
|
||||||
export type Chapter = z.infer<typeof ChapterP>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The transcoder's info for this item. This include subtitles, fonts, chapters...
|
|
||||||
*/
|
|
||||||
export const WatchInfoP = z
|
|
||||||
.object({
|
|
||||||
/**
|
|
||||||
* The sha1 of the video file.
|
|
||||||
*/
|
|
||||||
sha: z.string(),
|
|
||||||
/**
|
|
||||||
* The internal path of the video file.
|
|
||||||
*/
|
|
||||||
path: z.string(),
|
|
||||||
/**
|
|
||||||
* The extension used to store this video file.
|
|
||||||
*/
|
|
||||||
extension: z.string(),
|
|
||||||
/**
|
|
||||||
* The whole mimetype (defined as the RFC 6381).
|
|
||||||
* ex: `video/mp4; codecs="avc1.640028, mp4a.40.2"`
|
|
||||||
*/
|
|
||||||
mimeCodec: z.string(),
|
|
||||||
/**
|
|
||||||
* The file size of the video file.
|
|
||||||
*/
|
|
||||||
size: z.number(),
|
|
||||||
/**
|
|
||||||
* The duration of the video (in seconds).
|
|
||||||
*/
|
|
||||||
duration: z.number(),
|
|
||||||
/**
|
|
||||||
* The container of the video file of this episode. Common containers are mp4, mkv, avi and so on.
|
|
||||||
*/
|
|
||||||
container: z.string().nullable(),
|
|
||||||
/**
|
|
||||||
* The video track.
|
|
||||||
*/
|
|
||||||
videos: z.array(VideoP),
|
|
||||||
/**
|
|
||||||
* The list of audio tracks.
|
|
||||||
*/
|
|
||||||
audios: z.array(AudioP),
|
|
||||||
/**
|
|
||||||
* 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()),
|
|
||||||
/**
|
|
||||||
* The list of chapters. See Chapter for more information.
|
|
||||||
*/
|
|
||||||
chapters: z.array(ChapterP),
|
|
||||||
})
|
|
||||||
.transform((x) => {
|
|
||||||
const hour = Math.floor(x.duration / 3600);
|
|
||||||
const minutes = Math.ceil((x.duration % 3600) / 60);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...x,
|
|
||||||
duration: `${hour ? `${hour}h` : ""}${minutes}m`,
|
|
||||||
durationSeconds: x.duration,
|
|
||||||
size: humanFileSize(x.size),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// from https://stackoverflow.com/questions/10420352/converting-file-size-in-bytes-to-human-readable-string
|
|
||||||
const humanFileSize = (size: number): string => {
|
|
||||||
const i = size === 0 ? 0 : Math.floor(Math.log(size) / Math.log(1024));
|
|
||||||
return (
|
|
||||||
// @ts-ignore I'm not gonna fix stackoverflow's working code.
|
|
||||||
// biome-ignore lint/style/useTemplate: same as above
|
|
||||||
(size / 1024 ** i).toFixed(2) * 1 + " " + ["B", "kB", "MB", "GB", "TB"][i]
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A watch info for a video
|
|
||||||
*/
|
|
||||||
export type WatchInfo = z.infer<typeof WatchInfoP>;
|
|
108
front/src/models/video-info.ts
Normal file
108
front/src/models/video-info.ts
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
import { z } from "zod/v4";
|
||||||
|
|
||||||
|
export const Quality = z
|
||||||
|
.enum([
|
||||||
|
"original",
|
||||||
|
"8k",
|
||||||
|
"4k",
|
||||||
|
"1440p",
|
||||||
|
"1080p",
|
||||||
|
"720p",
|
||||||
|
"480p",
|
||||||
|
"360p",
|
||||||
|
"240p",
|
||||||
|
])
|
||||||
|
.default("original");
|
||||||
|
export type Quality = z.infer<typeof Quality>;
|
||||||
|
|
||||||
|
export const Video = z.object({
|
||||||
|
index: z.number(),
|
||||||
|
title: z.string().nullable(),
|
||||||
|
language: z.string().nullable(),
|
||||||
|
codec: z.string(),
|
||||||
|
mimeCodec: z.string(),
|
||||||
|
width: z.number(),
|
||||||
|
height: z.number(),
|
||||||
|
bitrate: z.number(),
|
||||||
|
isDefault: z.boolean(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type Video = z.infer<typeof Video>;
|
||||||
|
|
||||||
|
export const Audio = z.object({
|
||||||
|
index: z.number(),
|
||||||
|
title: z.string().nullable(),
|
||||||
|
language: z.string().nullable(),
|
||||||
|
codec: z.string(),
|
||||||
|
mimeCodec: z.string(),
|
||||||
|
bitrate: z.number(),
|
||||||
|
isDefault: z.boolean(),
|
||||||
|
});
|
||||||
|
export type Audio = z.infer<typeof Audio>;
|
||||||
|
|
||||||
|
export const Subtitle = z.object({
|
||||||
|
// external subtitles don't have indexes
|
||||||
|
index: z.number().nullable(),
|
||||||
|
title: z.string().nullable(),
|
||||||
|
language: z.string().nullable(),
|
||||||
|
codec: z.string(),
|
||||||
|
mimeCodec: z.string(),
|
||||||
|
extension: z.string(),
|
||||||
|
isDefault: z.boolean(),
|
||||||
|
isForced: z.boolean(),
|
||||||
|
isHearingImpaired: z.boolean(),
|
||||||
|
isExternal: z.boolean(),
|
||||||
|
// only non-null when `isExternal` is true
|
||||||
|
path: z.string().nullable(),
|
||||||
|
link: z.string().nullable(),
|
||||||
|
});
|
||||||
|
export type Subtitle = z.infer<typeof Subtitle>;
|
||||||
|
|
||||||
|
export const Chapter = z.object({
|
||||||
|
// in seconds
|
||||||
|
startTime: z.number(),
|
||||||
|
// in seconds
|
||||||
|
endTime: z.number(),
|
||||||
|
name: z.string(),
|
||||||
|
type: z.enum(["content", "recap", "intro", "credits", "preview"]),
|
||||||
|
});
|
||||||
|
export type Chapter = z.infer<typeof Chapter>;
|
||||||
|
|
||||||
|
export const VideoInfo = z
|
||||||
|
.object({
|
||||||
|
sha: z.string(),
|
||||||
|
path: z.string(),
|
||||||
|
extension: z.string(),
|
||||||
|
mimeCodec: z.string(),
|
||||||
|
size: z.number(),
|
||||||
|
// in seconds
|
||||||
|
duration: z.number(),
|
||||||
|
container: z.string().nullable(),
|
||||||
|
videos: z.array(Video),
|
||||||
|
audios: z.array(Audio),
|
||||||
|
subtitles: z.array(Subtitle),
|
||||||
|
fonts: z.array(z.string()),
|
||||||
|
chapters: z.array(Chapter),
|
||||||
|
})
|
||||||
|
.transform((x) => {
|
||||||
|
const hour = Math.floor(x.duration / 3600);
|
||||||
|
const minutes = Math.ceil((x.duration % 3600) / 60);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...x,
|
||||||
|
duration: `${hour ? `${hour}h` : ""}${minutes}m`,
|
||||||
|
durationSeconds: x.duration,
|
||||||
|
size: humanFileSize(x.size),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
export type VideoInfo = z.infer<typeof VideoInfo>;
|
||||||
|
|
||||||
|
// from https://stackoverflow.com/questions/10420352/converting-file-size-in-bytes-to-human-readable-string
|
||||||
|
const humanFileSize = (size: number): string => {
|
||||||
|
const i = size === 0 ? 0 : Math.floor(Math.log(size) / Math.log(1024));
|
||||||
|
return (
|
||||||
|
// @ts-ignore I'm not gonna fix stackoverflow's working code.
|
||||||
|
// biome-ignore lint/style/useTemplate: same as above
|
||||||
|
(size / 1024 ** i).toFixed(2) * 1 + " " + ["B", "kB", "MB", "GB", "TB"][i]
|
||||||
|
);
|
||||||
|
};
|
Loading…
x
Reference in New Issue
Block a user