diff --git a/frontend/src/components/VideoListItemTable.tsx b/frontend/src/components/VideoListItemTable.tsx index dc09c585..b21ae246 100644 --- a/frontend/src/components/VideoListItemTable.tsx +++ b/frontend/src/components/VideoListItemTable.tsx @@ -3,12 +3,12 @@ import Routes from '../configuration/routes/RouteList'; import { VideoType } from '../pages/Home'; import { ViewStylesType } from '../configuration/constants/ViewStyle'; import humanFileSize from '../functions/humanFileSize'; +import humanBitrate from '../functions/humanBitrate'; import { FileSizeUnits } from '../api/actions/updateUserConfig'; import { useUserConfigStore } from '../stores/UserConfigStore'; import { useVideoSelectionStore } from '../stores/VideoSelectionStore'; import iconChecked from '/img/icon-seen.svg'; import iconUnchecked from '/img/icon-unseen.svg'; -import bitsToBytes from '../functions/bitsToBytes'; const StreamsTypeEmun = { Video: 'video', @@ -98,9 +98,9 @@ const VideoListItemTable = ({ videoList, viewStyle }: VideoListItemProps) => { {`${videoStream?.width || '-'}x${videoStream?.height || '-'}`} {humanFileSize(media_size, useSiUnits)} {videoStream?.codec || '-'} - {humanFileSize(bitsToBytes(videoStream?.bitrate || 0), useSiUnits)} + {humanBitrate(videoStream?.bitrate || 0)} {audioStream?.codec || '-'} - {humanFileSize(bitsToBytes(audioStream?.bitrate || 0), useSiUnits)} + {humanBitrate(audioStream?.bitrate || 0)} ); })} diff --git a/frontend/src/functions/humanBitrate.ts b/frontend/src/functions/humanBitrate.ts new file mode 100644 index 00000000..3f64df19 --- /dev/null +++ b/frontend/src/functions/humanBitrate.ts @@ -0,0 +1,44 @@ +/** + * Format a bitrate (in bits per second) as human-readable text. + * + * Bitrate values from yt-dlp come in bits per second. This helper formats + * them using SI powers of 1000 (kbps, Mbps, Gbps) — the standard for + * bit-rate display, where 1 kbps = 1000 bits per second. + * + * Note: this intentionally does NOT use the IEC "Kib/s" unit, since + * network bitrates (and yt-dlp's own `--format-sort bitrate` values) + * are universally expressed in SI decimal units in the wild. + * + * @param bitsPerSecond Bitrate in bits per second. + * @param dp Number of decimal places to display. Default 1. + * + * @return Formatted string ending in the unit (e.g. "1.5 Mbps"). + */ +function humanBitrate(bitsPerSecond: number, dp = 1): string { + if (!isFinite(bitsPerSecond) || bitsPerSecond < 0) { + return '-'; + } + if (bitsPerSecond === 0) { + return '0 bps'; + } + + const thresh = 1000; + const units = ['bps', 'kbps', 'Mbps', 'Gbps', 'Tbps']; + + if (Math.abs(bitsPerSecond) < thresh) { + return bitsPerSecond + ' bps'; + } + + let value = bitsPerSecond; + let u = 0; + const r = 10 ** dp; + + do { + value /= thresh; + ++u; + } while (Math.round(Math.abs(value) * r) / r >= thresh && u < units.length - 1); + + return value.toFixed(dp) + ' ' + units[u]; +} + +export default humanBitrate; diff --git a/frontend/src/pages/Video.tsx b/frontend/src/pages/Video.tsx index 2a4e39f7..3d4a3ac1 100644 --- a/frontend/src/pages/Video.tsx +++ b/frontend/src/pages/Video.tsx @@ -16,6 +16,7 @@ import loadSimilarVideosById from '../api/loader/loadSimilarVideosById'; import VideoList from '../components/VideoList'; import updateWatchedState from '../api/actions/updateWatchedState'; import humanFileSize from '../functions/humanFileSize'; +import humanBitrate from '../functions/humanBitrate'; import ScrollToTopOnNavigate from '../components/ScrollToTop'; import ChannelOverview from '../components/ChannelOverview'; import deleteVideo from '../api/actions/deleteVideo'; @@ -45,7 +46,6 @@ import NotFound from './NotFound'; import { ApiResponseType } from '../functions/APIClient'; import VideoThumbnail from '../components/VideoThumbail'; import { ViewStylesEnum, ViewStylesType } from '../configuration/constants/ViewStyle'; -import bitsToBytes from '../functions/bitsToBytes'; const isInPlaylist = (videoId: string, playlist: PlaylistType) => { return playlist.playlist_entries.some(entry => { @@ -461,7 +461,7 @@ const Video = () => { return (

{capitalizeFirstLetter(stream.type)}: {stream.codec}{' '} - {humanFileSize(bitsToBytes(stream.bitrate), useSiUnits)}/s + {humanBitrate(stream.bitrate)} {stream.width && ( <> | {stream.width}x{stream.height}