diff --git a/front/packages/models/src/resources/watch-info.ts b/front/packages/models/src/resources/watch-info.ts index a0a10d86..134da343 100644 --- a/front/packages/models/src/resources/watch-info.ts +++ b/front/packages/models/src/resources/watch-info.ts @@ -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; + +/** + * 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; -/** - * 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; - export const AudioP = TrackP; export type Audio = z.infer; @@ -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; @@ -152,7 +153,7 @@ export const WatchInfoP = z /** * The video track. */ - video: VideoP.nullable(), + videos: z.array(VideoP), /** * The list of audio tracks. */ diff --git a/front/packages/ui/src/components/media-info.tsx b/front/packages/ui/src/components/media-info.tsx index 77b2dc65..9db7ab45 100644 --- a/front/packages/ui/src/components/media-info.tsx +++ b/front/packages/ui/src/components/media-info.tsx @@ -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; }) => { @@ -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, ); }; + 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, + ); + }; 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"), diff --git a/front/packages/ui/src/utils.ts b/front/packages/ui/src/utils.ts index 89320206..db12439a 100644 --- a/front/packages/ui/src/utils.ts +++ b/front/packages/ui/src/utils.ts @@ -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"; }; }; diff --git a/front/translations/en.json b/front/translations/en.json index f59ffaa3..949b5346 100644 --- a/front/translations/en.json +++ b/front/translations/en.json @@ -260,6 +260,7 @@ "subtitles": "Subtitles", "forced": "Forced", "default": "Default", + "external": "External", "duration": "Duration", "size": "Size", "novideo": "No video",