Cleanup video info type

This commit is contained in:
Zoe Roux 2025-07-19 19:47:49 +02:00
parent 430843bb15
commit 24a170a859
No known key found for this signature in database
4 changed files with 109 additions and 202 deletions

View File

@ -12,3 +12,4 @@ export * from "./utils/genre";
export * from "./utils/images";
export * from "./utils/page";
export * from "./video";
export * from "./video-info";

View File

@ -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>;

View File

@ -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>;

View 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]
);
};