diff --git a/client/src/app/shared/shared-video-miniature/download/video-generate-download.component.html b/client/src/app/shared/shared-video-miniature/download/video-generate-download.component.html
index fe8dcc12f..f00bba8be 100644
--- a/client/src/app/shared/shared-video-miniature/download/video-generate-download.component.html
+++ b/client/src/app/shared/shared-video-miniature/download/video-generate-download.component.html
@@ -17,9 +17,15 @@
}
diff --git a/client/src/app/shared/shared-video-miniature/download/video-generate-download.component.ts b/client/src/app/shared/shared-video-miniature/download/video-generate-download.component.ts
index 9d89b0a77..52bb04552 100644
--- a/client/src/app/shared/shared-video-miniature/download/video-generate-download.component.ts
+++ b/client/src/app/shared/shared-video-miniature/download/video-generate-download.component.ts
@@ -6,7 +6,7 @@ import { VideoService } from '@app/shared/shared-main/video/video.service'
import {
NgbTooltip
} from '@ng-bootstrap/ng-bootstrap'
-import { maxBy } from '@peertube/peertube-core-utils'
+import { getResolutionAndFPSLabel, maxBy } from '@peertube/peertube-core-utils'
import { VideoFile, VideoResolution, VideoSource } from '@peertube/peertube-models'
import { videoRequiresFileToken } from '@root-helpers/video'
import { GlobalIconComponent } from '../../shared-icons/global-icon.component'
@@ -52,6 +52,10 @@ export class VideoGenerateDownloadComponent implements OnInit {
this.videoFileChosen = 'file-' + maxBy(this.videoFiles, 'resolution').id
}
+ getLabel (file: VideoFile) {
+ return getResolutionAndFPSLabel(file.resolution.label, file.fps)
+ }
+
getFileSize (file: VideoFile) {
if (file.hasAudio && file.hasVideo) return file.size
if (file.hasAudio) return file.size
diff --git a/client/src/assets/player/shared/p2p-media-loader/hls-plugin.ts b/client/src/assets/player/shared/p2p-media-loader/hls-plugin.ts
index a93fff7fb..ba9a8d627 100644
--- a/client/src/assets/player/shared/p2p-media-loader/hls-plugin.ts
+++ b/client/src/assets/player/shared/p2p-media-loader/hls-plugin.ts
@@ -303,15 +303,7 @@ export class Html5Hlsjs {
// ---------------------------------------------------------------------------
private buildLevelLabel (level: Level) {
- if (this.player.srOptions_.levelLabelHandler) {
- return this.player.srOptions_.levelLabelHandler(level, this.player)
- }
-
- if (level.height) return level.height + 'p'
- if (level.width) return Math.round(level.width * 9 / 16) + 'p'
- if (level.bitrate) return (level.bitrate / 1000) + 'kbps'
-
- return this.player.localize('Audio only')
+ return this.player.srOptions_.levelLabelHandler(level, this.player)
}
private _removeQuality (index: number) {
diff --git a/client/src/assets/player/shared/player-options-builder/hls-options-builder.ts b/client/src/assets/player/shared/player-options-builder/hls-options-builder.ts
index b4a3a4d6e..7ba4faaee 100644
--- a/client/src/assets/player/shared/player-options-builder/hls-options-builder.ts
+++ b/client/src/assets/player/shared/player-options-builder/hls-options-builder.ts
@@ -1,8 +1,10 @@
import { HybridLoaderSettings } from '@peertube/p2p-media-loader-core'
import { Engine, HlsJsEngineSettings } from '@peertube/p2p-media-loader-hlsjs'
+import { getResolutionAndFPSLabel, getResolutionLabel } from '@peertube/peertube-core-utils'
import { LiveVideoLatencyMode } from '@peertube/peertube-models'
import { logger } from '@root-helpers/logger'
import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
+import { Level } from 'hls.js'
import { getAverageBandwidthInStore } from '../../peertube-player-local-storage'
import {
HLSLoaderClass,
@@ -70,21 +72,17 @@ export class HLSOptionsBuilder {
const hlsjs = {
hlsjsConfig: this.getHLSJSOptions(loaderBuilder),
- levelLabelHandler: (level: { height: number, width: number }, player: videojs.VideoJsPlayer) => {
+ levelLabelHandler: (level: Level, player: videojs.VideoJsPlayer) => {
const resolution = Math.min(level.height || 0, level.width || 0)
-
const file = this.options.hls.videoFiles.find(f => f.resolution.id === resolution)
- // We don't have files for live videos
- if (!file) {
- if (resolution === 0) return player.localize('Audio only')
- return level.height + 'p'
- }
+ const resolutionLabel = getResolutionLabel({
+ resolution,
+ height: file?.height ?? level.height,
+ width: file?.width ?? level.width
+ })
- let label = file.resolution.label
- if (file.fps >= 50) label += file.fps
-
- return label
+ return player.localize(getResolutionAndFPSLabel(resolutionLabel, file?.fps ?? level.frameRate))
}
}
diff --git a/client/src/assets/player/shared/web-video/web-video-plugin.ts b/client/src/assets/player/shared/web-video/web-video-plugin.ts
index a214fd6ae..e4544f87e 100644
--- a/client/src/assets/player/shared/web-video/web-video-plugin.ts
+++ b/client/src/assets/player/shared/web-video/web-video-plugin.ts
@@ -1,8 +1,8 @@
+import { addQueryParams, getResolutionAndFPSLabel } from '@peertube/peertube-core-utils'
+import { VideoFile } from '@peertube/peertube-models'
+import { logger } from '@root-helpers/logger'
import debug from 'debug'
import videojs from 'video.js'
-import { logger } from '@root-helpers/logger'
-import { addQueryParams } from '@peertube/peertube-core-utils'
-import { VideoFile } from '@peertube/peertube-models'
import { PeerTubeResolution, PlayerNetworkInfo, WebVideoPluginOptions } from '../../types'
const debugLogger = debug('peertube:player:web-video-plugin')
@@ -155,7 +155,7 @@ class WebVideoPlugin extends Plugin {
private buildQualities () {
const resolutions: PeerTubeResolution[] = this.videoFiles.map(videoFile => ({
id: videoFile.resolution.id,
- label: this.buildQualityLabel(videoFile),
+ label: this.player.localize(getResolutionAndFPSLabel(videoFile.resolution.label, videoFile.fps)),
height: videoFile.resolution.id,
selected: videoFile.id === this.currentVideoFile?.id,
selectCallback: () => this.updateVideoFile({ videoFile, isUserResolutionChange: true })
@@ -164,16 +164,6 @@ class WebVideoPlugin extends Plugin {
this.player.peertubeResolutions().add(resolutions)
}
- private buildQualityLabel (file: VideoFile) {
- let label = file.resolution.label
-
- if (file.fps && file.fps >= 50) {
- label += file.fps
- }
-
- return label
- }
-
private setupNetworkInfoInterval () {
this.networkInfoInterval = setInterval(() => {
return this.player.trigger('network-info', {
diff --git a/packages/core-utils/src/videos/common.ts b/packages/core-utils/src/videos/common.ts
index 64e66094c..60c1c1eac 100644
--- a/packages/core-utils/src/videos/common.ts
+++ b/packages/core-utils/src/videos/common.ts
@@ -1,4 +1,4 @@
-import { VideoDetails, VideoPrivacy, VideoStreamingPlaylistType } from '@peertube/peertube-models'
+import { VideoDetails, VideoPrivacy, VideoResolution, VideoStreamingPlaylistType } from '@peertube/peertube-models'
export function getAllPrivacies () {
return [ VideoPrivacy.PUBLIC, VideoPrivacy.INTERNAL, VideoPrivacy.PRIVATE, VideoPrivacy.UNLISTED, VideoPrivacy.PASSWORD_PROTECTED ]
@@ -23,3 +23,54 @@ export function buildAspectRatio (options: { width: number, height: number }) {
return Math.round((width / height) * 10000) / 10000 // 4 decimals precision
}
+
+const classicResolutions = new Set([
+ VideoResolution.H_NOVIDEO,
+ VideoResolution.H_144P,
+ VideoResolution.H_240P,
+ VideoResolution.H_360P,
+ VideoResolution.H_480P,
+ VideoResolution.H_720P,
+ VideoResolution.H_1080P,
+ VideoResolution.H_1440P,
+ VideoResolution.H_4K
+])
+
+const resolutionConverter = {
+ 3840: VideoResolution.H_4K,
+ 1920: VideoResolution.H_1080P,
+ 1280: VideoResolution.H_720P,
+ 854: VideoResolution.H_480P,
+ 640: VideoResolution.H_360P,
+ 426: VideoResolution.H_240P,
+ 256: VideoResolution.H_144P
+}
+
+export function getResolutionLabel (options: {
+ resolution: number
+ height: number
+ width: number
+}) {
+ const { height, width } = options
+
+ if (options.resolution === 0) return 'Audio only'
+
+ let resolution = options.resolution
+
+ // Try to find a better resolution label
+ // For example with a video 1920x816 we prefer to display "1080p"
+ if (!classicResolutions.has(resolution) && typeof height === 'number' && typeof width === 'number') {
+ const max = Math.max(height, width)
+
+ const alternativeLabel = resolutionConverter[max]
+ if (alternativeLabel) resolution = resolutionConverter[max]
+ }
+
+ return `${resolution}p`
+}
+
+export function getResolutionAndFPSLabel (resolutionLabel: string, fps: number) {
+ if (fps && fps >= 50) return resolutionLabel + fps
+
+ return resolutionLabel
+}
diff --git a/server/core/models/video/formatter/video-api-format.ts b/server/core/models/video/formatter/video-api-format.ts
index 11d15fabc..0c627a13e 100644
--- a/server/core/models/video/formatter/video-api-format.ts
+++ b/server/core/models/video/formatter/video-api-format.ts
@@ -25,6 +25,7 @@ import {
import { MServer, MStreamingPlaylistRedundanciesOpt, MVideoFormattable, MVideoFormattableDetails } from '../../../types/models/index.js'
import { MVideoFileRedundanciesOpt } from '../../../types/models/video/video-file.js'
import { sortByResolutionDesc } from './shared/index.js'
+import { getResolutionLabel } from '@peertube/peertube-core-utils'
export type VideoFormattingJSONOptions = {
completeDescription?: boolean
@@ -230,7 +231,12 @@ export function videoFilesModelToFormattedJSON (
resolution: {
id: videoFile.resolution,
- label: getResolutionLabel(videoFile.resolution)
+
+ label: getResolutionLabel({
+ resolution: videoFile.resolution,
+ height: videoFile.height,
+ width: videoFile.width
+ })
},
width: videoFile.width,
@@ -279,12 +285,6 @@ export function getStateLabel (id: number) {
return VIDEO_STATES[id] || 'Unknown'
}
-export function getResolutionLabel (resolution: number) {
- if (resolution === 0) return 'Audio'
-
- return `${resolution}p`
-}
-
// ---------------------------------------------------------------------------
// Private
// ---------------------------------------------------------------------------
diff --git a/server/core/models/video/video-source.ts b/server/core/models/video/video-source.ts
index 9c80ae148..de809322f 100644
--- a/server/core/models/video/video-source.ts
+++ b/server/core/models/video/video-source.ts
@@ -1,3 +1,4 @@
+import { getResolutionLabel } from '@peertube/peertube-core-utils'
import { ActivityVideoUrlObject, type FileStorageType, type VideoSource } from '@peertube/peertube-models'
import { DOWNLOAD_PATHS, WEBSERVER } from '@server/initializers/constants.js'
import { getVideoFileMimeType } from '@server/lib/video-file.js'
@@ -6,7 +7,6 @@ import { extname, join } from 'path'
import { Transaction } from 'sequelize'
import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Table, UpdatedAt } from 'sequelize-typescript'
import { SequelizeModel, doesExist, getSort } from '../shared/index.js'
-import { getResolutionLabel } from './formatter/video-api-format.js'
import { VideoModel } from './video.js'
@Table({
@@ -148,7 +148,7 @@ export class VideoSourceModel extends SequelizeModel {
resolution: {
id: this.resolution,
label: this.resolution !== null
- ? getResolutionLabel(this.resolution)
+ ? getResolutionLabel({ resolution: this.resolution, height: this.height, width: this.width })
: null
},
size: this.size,