mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
Add Media Info Stats
This commit is contained in:
parent
82d70763bb
commit
106392b9cb
5 changed files with 290 additions and 2 deletions
|
@ -11,7 +11,7 @@ interface MediaInfoItemProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
const MediaInfoItem: FC<MediaInfoItemProps> = ({ className, miscInfo }) => {
|
const MediaInfoItem: FC<MediaInfoItemProps> = ({ className, miscInfo }) => {
|
||||||
const { text, textAction, cssClass } = miscInfo;
|
const { text, textAction, cssClass, type } = miscInfo;
|
||||||
|
|
||||||
const renderText = () => {
|
const renderText = () => {
|
||||||
if (textAction) {
|
if (textAction) {
|
||||||
|
@ -31,7 +31,7 @@ const MediaInfoItem: FC<MediaInfoItemProps> = ({ className, miscInfo }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box className={classNames('mediaInfoItem', cssClass, className)}>
|
<Box className={classNames('mediaInfoItem', cssClass, type, className)}>
|
||||||
{renderText()}
|
{renderText()}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|
49
src/components/mediainfo/MediaInfoStats.tsx
Normal file
49
src/components/mediainfo/MediaInfoStats.tsx
Normal file
|
@ -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<MediaInfoStatsProps> = ({
|
||||||
|
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) => (
|
||||||
|
<MediaInfoItem key={index} className={infoclass} miscInfo={info} />
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box className={cssClass}>
|
||||||
|
{mediaInfoStats.map((info, index) => renderMediaInfo(info, index))}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MediaInfoStats;
|
|
@ -23,3 +23,12 @@ export interface SecondaryInfoOpts {
|
||||||
showChannelInfo?: boolean;
|
showChannelInfo?: boolean;
|
||||||
channelInteractive?: boolean;
|
channelInteractive?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MediaInfoStatsOpts {
|
||||||
|
showVideoTypeInfo?: boolean;
|
||||||
|
showResolutionInfo?: boolean;
|
||||||
|
showVideoStreamCodecInfo?: boolean;
|
||||||
|
showAudoChannelInfo?: boolean;
|
||||||
|
showAudioStreamCodecInfo?: boolean;
|
||||||
|
showDateAddedInfo?: boolean;
|
||||||
|
}
|
||||||
|
|
229
src/components/mediainfo/useMediaInfoStats.tsx
Normal file
229
src/components/mediainfo/useMediaInfoStats.tsx
Normal file
|
@ -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;
|
|
@ -5,6 +5,7 @@ interface TextAction {
|
||||||
}
|
}
|
||||||
export interface MiscInfo {
|
export interface MiscInfo {
|
||||||
text?: string | number;
|
text?: string | number;
|
||||||
|
type?: string;
|
||||||
textAction?: TextAction;
|
textAction?: TextAction;
|
||||||
cssClass?: string;
|
cssClass?: string;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue