From 106392b9cba9d73e3a0a3f6d0d0887584ed71e91 Mon Sep 17 00:00:00 2001 From: grafixeyehero Date: Thu, 22 Aug 2024 01:04:18 +0300 Subject: [PATCH] Add Media Info Stats --- src/components/mediainfo/MediaInfoItem.tsx | 4 +- src/components/mediainfo/MediaInfoStats.tsx | 49 ++++ src/components/mediainfo/type.ts | 9 + .../mediainfo/useMediaInfoStats.tsx | 229 ++++++++++++++++++ src/types/mediaInfoItem.ts | 1 + 5 files changed, 290 insertions(+), 2 deletions(-) create mode 100644 src/components/mediainfo/MediaInfoStats.tsx create mode 100644 src/components/mediainfo/useMediaInfoStats.tsx diff --git a/src/components/mediainfo/MediaInfoItem.tsx b/src/components/mediainfo/MediaInfoItem.tsx index 545414c37a..35159ddfc6 100644 --- a/src/components/mediainfo/MediaInfoItem.tsx +++ b/src/components/mediainfo/MediaInfoItem.tsx @@ -11,7 +11,7 @@ interface MediaInfoItemProps { } const MediaInfoItem: FC = ({ className, miscInfo }) => { - const { text, textAction, cssClass } = miscInfo; + const { text, textAction, cssClass, type } = miscInfo; const renderText = () => { if (textAction) { @@ -31,7 +31,7 @@ const MediaInfoItem: FC = ({ className, miscInfo }) => { }; return ( - + {renderText()} ); diff --git a/src/components/mediainfo/MediaInfoStats.tsx b/src/components/mediainfo/MediaInfoStats.tsx new file mode 100644 index 0000000000..08984770b1 --- /dev/null +++ b/src/components/mediainfo/MediaInfoStats.tsx @@ -0,0 +1,49 @@ +import React, { type FC } from 'react'; +import classNames from 'classnames'; +import Box from '@mui/material/Box'; +import useMediaInfoStats from './useMediaInfoStats'; + +import MediaInfoItem from './MediaInfoItem'; +import type { ItemDto } from 'types/base/models/item-dto'; +import type { MiscInfo } from 'types/mediaInfoItem'; +import type { MediaInfoStatsOpts } from './type'; + +interface MediaInfoStatsProps extends MediaInfoStatsOpts { + className?: string; + infoclass?: string; + item: ItemDto; +} + +const MediaInfoStats: FC = ({ + className, + infoclass, + item, + showResolutionInfo, + showVideoStreamCodecInfo, + showAudoChannelInfo, + showAudioStreamCodecInfo, + showDateAddedInfo +}) => { + const mediaInfoStats = useMediaInfoStats({ + item, + showResolutionInfo, + showVideoStreamCodecInfo, + showAudoChannelInfo, + showAudioStreamCodecInfo, + showDateAddedInfo + }); + + const cssClass = classNames(className); + + const renderMediaInfo = (info: MiscInfo, index: number) => ( + + ); + + return ( + + {mediaInfoStats.map((info, index) => renderMediaInfo(info, index))} + + ); +}; + +export default MediaInfoStats; diff --git a/src/components/mediainfo/type.ts b/src/components/mediainfo/type.ts index ab5bf19422..2a5a51edec 100644 --- a/src/components/mediainfo/type.ts +++ b/src/components/mediainfo/type.ts @@ -23,3 +23,12 @@ export interface SecondaryInfoOpts { showChannelInfo?: boolean; channelInteractive?: boolean; } + +export interface MediaInfoStatsOpts { + showVideoTypeInfo?: boolean; + showResolutionInfo?: boolean; + showVideoStreamCodecInfo?: boolean; + showAudoChannelInfo?: boolean; + showAudioStreamCodecInfo?: boolean; + showDateAddedInfo?: boolean; +} diff --git a/src/components/mediainfo/useMediaInfoStats.tsx b/src/components/mediainfo/useMediaInfoStats.tsx new file mode 100644 index 0000000000..593d17788f --- /dev/null +++ b/src/components/mediainfo/useMediaInfoStats.tsx @@ -0,0 +1,229 @@ +import { MediaStreamType } from '@jellyfin/sdk/lib/generated-client/models/media-stream-type'; +import { VideoType } from '@jellyfin/sdk/lib/generated-client/models/video-type'; +import type { MediaStream } from '@jellyfin/sdk/lib/generated-client/models/media-stream'; +import itemHelper from 'components/itemHelper'; +import datetime from 'scripts/datetime'; +import globalize from 'lib/globalize'; + +import type { ItemDto } from 'types/base/models/item-dto'; +import type { MiscInfo } from 'types/mediaInfoItem'; +import type { NullableString } from 'types/base/common/shared/types'; +import type { MediaInfoStatsOpts } from './type'; + +const getResolution = (label: string, isInterlaced?: boolean) => + isInterlaced ? `${label}i` : label; + +const getResolutionText = ( + showResolutionInfo: boolean, + stream: MediaStream +) => { + const { Width, Height, IsInterlaced } = stream; + + if (showResolutionInfo && Width && Height) { + switch (true) { + case Width >= 3800 || Height >= 2000: + return '4K'; + case Width >= 2500 || Height >= 1400: + return getResolution('1440p', IsInterlaced); + case Width >= 1800 || Height >= 1000: + return getResolution('1080p', IsInterlaced); + case Width >= 1200 || Height >= 700: + return getResolution('720p', IsInterlaced); + case Width >= 700 || Height >= 400: + return getResolution('480p', IsInterlaced); + default: + return null; + } + } + + return null; +}; + +const getAudoChannelText = ( + showAudoChannelInfo: boolean, + stream: MediaStream +) => { + const { Channels } = stream; + + if (showAudoChannelInfo && Channels) { + switch (true) { + case Channels === 8: + return '7.1'; + case Channels === 7: + return '6.1'; + case Channels === 6: + return '5.1'; + case Channels === 2: + return '2.0'; + default: + return null; + } + } + + return null; +}; + +function getAudioStreamForDisplay(item: ItemDto) { + const mediaSource = (item.MediaSources || [])[0] || {}; + + return ( + (mediaSource.MediaStreams || []).filter((i) => { + return ( + i.Type === MediaStreamType.Audio + && (i.Index === mediaSource.DefaultAudioStreamIndex + || mediaSource.DefaultAudioStreamIndex == null) + ); + })[0] || {} + ); +} + +function getVideoStreamForDisplay(item: ItemDto) { + const mediaSource = (item.MediaSources || [])[0] || {}; + + return ( + (mediaSource.MediaStreams || []).filter((i) => { + return i.Type === MediaStreamType.Video; + })[0] || {} + ); +} + +function addVideoType( + showVideoTypeInfo: boolean, + itemVideoType: VideoType | undefined, + addMiscInfo: (val: MiscInfo) => void +): void { + if (showVideoTypeInfo) { + if (itemVideoType === VideoType.Dvd) { + addMiscInfo({ type: 'mediainfo', text: 'Dvd' }); + } + + if (itemVideoType === VideoType.BluRay) { + addMiscInfo({ type: 'mediainfo', text: 'BluRay' }); + } + } +} + +function addResolution( + showResolutionInfo: boolean, + videoStream: MediaStream, + addMiscInfo: (val: MiscInfo) => void +): void { + const resolutionText = getResolutionText(showResolutionInfo, videoStream); + + if (resolutionText) { + addMiscInfo({ type: 'mediainfo', text: resolutionText }); + } +} + +function addVideoStreamCodec( + showVideoCodecInfo: boolean, + videoStreamCodec: NullableString, + addMiscInfo: (val: MiscInfo) => void +): void { + if (showVideoCodecInfo && videoStreamCodec) { + addMiscInfo({ type: 'mediainfo', text: videoStreamCodec }); + } +} + +function addAudoChannel( + showAudoChannelInfo: boolean, + audioStream: MediaStream, + addMiscInfo: (val: MiscInfo) => void +): void { + const audioChannelText = getAudoChannelText( + showAudoChannelInfo, + audioStream + ); + + if (audioChannelText) { + addMiscInfo({ type: 'mediainfo', text: audioChannelText }); + } +} + +function addAudioStreamCodec( + showAudioStreamCodecInfo: boolean, + audioStream: MediaStream, + addMiscInfo: (val: MiscInfo) => void +): void { + const audioCodec = (audioStream.Codec || '').toLowerCase(); + + if (showAudioStreamCodecInfo) { + if ( + (audioCodec === 'dca' || audioCodec === 'dts') + && audioStream?.Profile + ) { + addMiscInfo({ type: 'mediainfo', text: audioStream.Profile }); + } else if (audioStream?.Codec) { + addMiscInfo({ type: 'mediainfo', text: audioStream.Codec }); + } + } +} + +function addDateAdded( + showDateAddedInfo: boolean, + item: ItemDto, + addMiscInfo: (val: MiscInfo) => void +): void { + if ( + showDateAddedInfo + && item.DateCreated + && itemHelper.enableDateAddedDisplay(item) + ) { + const dateCreated = datetime.parseISO8601Date(item.DateCreated); + addMiscInfo({ + type: 'added', + text: globalize.translate( + 'AddedOnValue', + `${datetime.toLocaleDateString( + dateCreated + )} ${datetime.getDisplayTime(dateCreated)}` + ) + }); + } +} + +interface UseMediaInfoStatsProps extends MediaInfoStatsOpts { + item: ItemDto; +} + +function useMediaInfoStats({ + item, + showVideoTypeInfo = false, + showResolutionInfo = false, + showVideoStreamCodecInfo = false, + showAudoChannelInfo = false, + showAudioStreamCodecInfo = false, + showDateAddedInfo = false +}: UseMediaInfoStatsProps) { + const miscInfo: MiscInfo[] = []; + + const addMiscInfo = (val: MiscInfo) => { + if (val) { + miscInfo.push(val); + } + }; + + const videoStream = getVideoStreamForDisplay(item); + + const audioStream = getAudioStreamForDisplay(item); + + addVideoType(showVideoTypeInfo, item.VideoType, addMiscInfo); + + addResolution(showResolutionInfo, videoStream, addMiscInfo); + + addVideoStreamCodec( + showVideoStreamCodecInfo, + videoStream.Codec, + addMiscInfo + ); + + addAudoChannel(showAudoChannelInfo, audioStream, addMiscInfo); + + addAudioStreamCodec(showAudioStreamCodecInfo, audioStream, addMiscInfo); + + addDateAdded(showDateAddedInfo, item, addMiscInfo); + + return miscInfo; +} + +export default useMediaInfoStats; diff --git a/src/types/mediaInfoItem.ts b/src/types/mediaInfoItem.ts index e8c3dc168d..faa6ebc374 100644 --- a/src/types/mediaInfoItem.ts +++ b/src/types/mediaInfoItem.ts @@ -5,6 +5,7 @@ interface TextAction { } export interface MiscInfo { text?: string | number; + type?: string; textAction?: TextAction; cssClass?: string; }