mirror of
				https://github.com/zoriya/Kyoo.git
				synced 2025-10-31 10:37:13 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			109 lines
		
	
	
		
			2.7 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			109 lines
		
	
	
		
			2.7 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| 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 VideoTrack = 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 VideoTrack = z.infer<typeof VideoTrack>;
 | |
| 
 | |
| export const AudioTrack = 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 AudioTrack = z.infer<typeof AudioTrack>;
 | |
| 
 | |
| 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().nullable(),
 | |
| 	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(VideoTrack),
 | |
| 		audios: z.array(AudioTrack),
 | |
| 		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]
 | |
| 	);
 | |
| };
 |