Update media info display

This commit is contained in:
Zoe Roux 2024-08-09 00:16:44 +02:00
parent 2618650d27
commit 4e79f38d1e
4 changed files with 74 additions and 53 deletions

View File

@ -23,14 +23,42 @@ import { imageFn } from "../traits";
import { QualityP } from "./quality";
/**
* A Video track
* A audio or subtitle track.
*/
export const VideoP = z.object({
export const TrackP = z.object({
/**
* The Codec of the Video Track.
* E.g. "AVC"
* 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"
@ -55,37 +83,6 @@ export const VideoP = z.object({
export type Video = z.infer<typeof VideoP>;
/**
* A audio or subtitle track.
*/
export const TrackP = z.object({
/**
* The index of this track on the episode.
*/
index: z.number(),
/**
* 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?
*/
isForced: z.boolean(),
});
export type Track = z.infer<typeof TrackP>;
export const AudioP = TrackP;
export type Audio = z.infer<typeof AudioP>;
@ -94,6 +91,10 @@ export const SubtitleP = TrackP.extend({
* The url of this track (only if this is a subtitle)..
*/
link: z.string().transform(imageFn).nullable(),
/*
* Is this an external subtitle (as in stored in a different file)
*/
isExternal: z.boolean(),
});
export type Subtitle = z.infer<typeof SubtitleP>;
@ -152,7 +153,7 @@ export const WatchInfoP = z
/**
* The video track.
*/
video: VideoP.nullable(),
videos: z.array(VideoP),
/**
* The list of audio tracks.
*/

View File

@ -19,6 +19,7 @@
*/
import {
type Video,
type Audio,
type QueryIdentifier,
type Subtitle,
@ -33,8 +34,10 @@ import { useYoshiki } from "yoshiki/native";
import { Fetch } from "../fetch";
import { useDisplayName } from "../utils";
const formatBitrate = (b: number) => `${(b / 1000000).toFixed(2)} Mbps`;
const MediaInfoTable = ({
mediaInfo: { path, video, container, audios, subtitles, duration, size },
mediaInfo: { path, videos, container, audios, subtitles, duration, size },
}: {
mediaInfo: Partial<WatchInfo>;
}) => {
@ -42,22 +45,22 @@ const MediaInfoTable = ({
const { t } = useTranslation();
const { css } = useYoshiki();
const formatBitrate = (b: number) => `${(b / 1000000).toFixed(2)} Mbps`;
const formatTrackTable = (trackTable: (Audio | Subtitle)[], type: "subtitles" | "audio") => {
if (trackTable.length === 0) {
return undefined;
}
const singleTrack = trackTable.length === 1;
return trackTable.reduce(
(collected, audioTrack, index) => {
(collected, track, index) => {
// If there is only one track, we do not need to show an index
collected[singleTrack ? t(`mediainfo.${type}`) : `${t(`mediainfo.${type}`)} ${index + 1}`] =
[
getDisplayName(audioTrack),
getDisplayName(track),
// Only show it if there is more than one track
audioTrack.isDefault && !singleTrack ? t("mediainfo.default") : undefined,
audioTrack.isForced ? t("mediainfo.forced") : undefined,
audioTrack.codec,
track.isDefault && !singleTrack ? t("mediainfo.default") : undefined,
track.isForced ? t("mediainfo.forced") : undefined,
"isExternal" in track && track.isExternal ? t("mediainfo.external") : undefined,
track.codec,
]
.filter((x) => x !== undefined)
.join(" - ");
@ -66,6 +69,29 @@ const MediaInfoTable = ({
{} as Record<string, string | undefined>,
);
};
const formatVideoTable = (trackTable: Video[]) => {
if (trackTable.length === 0) {
return { [t("mediainfo.video")]: t("mediainfo.novideo") };
}
const singleTrack = trackTable.length === 1;
return trackTable.reduce(
(collected, video, index) => {
// If there is only one track, we do not need to show an index
collected[singleTrack ? t("mediainfo.video") : `${t("mediainfo.video")} ${index + 1}`] = [
getDisplayName(video),
`${video.width}x${video.height} (${video.quality})`,
formatBitrate(video.bitrate),
// Only show it if there is more than one track
video.isDefault && !singleTrack ? t("mediainfo.default") : undefined,
video.codec,
]
.filter((x) => x !== undefined)
.join(" - ");
return collected;
},
{} as Record<string, string | undefined>,
);
};
const table = (
[
{
@ -74,15 +100,7 @@ const MediaInfoTable = ({
[t("mediainfo.duration")]: duration,
[t("mediainfo.size")]: size,
},
{
[t("mediainfo.video")]: video
? `${video.width}x${video.height} (${video.quality}) - ${formatBitrate(
video.bitrate,
)} - ${video.codec}`
: video === null
? t("mediainfo.novideo")
: undefined,
},
videos === undefined ? { [t("mediainfo.video")]: undefined } : formatVideoTable(videos),
audios === undefined
? { [t("mediainfo.audio")]: undefined }
: formatTrackTable(audios, "audio"),

View File

@ -14,6 +14,7 @@ export const useDisplayName = () => {
if (lng && sub.title && sub.title !== lng) return `${lng} - ${sub.title}`;
if (lng) return lng;
if (sub.title) return sub.title;
return `Unknown (${sub.index})`;
if (sub.index !== null) return `Unknown (${sub.index})`;
return "Unknown";
};
};

View File

@ -260,6 +260,7 @@
"subtitles": "Subtitles",
"forced": "Forced",
"default": "Default",
"external": "External",
"duration": "Duration",
"size": "Size",
"novideo": "No video",