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/page";
|
||||
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