Add setting to force transcoding above a max resolution (#1955)
## Description Adds a new setting to pick a maximum resolution (by height). Any playback that has a larger resolution will be transcoded down to the maximum resolution. Playback equal or less than the maximum resolution will not be affected and will direct play if otherwise able. This setting is also independent of the maximum bitrate (ie if max resolution is 4K and max bitrate is 2mbps, playing 4K will transcode). This setting is pretty much equivalent to the Web UI setting "Maximum Allowed Video Transcoding Resolution" with "Limit maximum supported video resolution" enabled. Note: this setting does not affect which version is chosen by default ### Related issues Closes #756 Closes #1594 ### Testing Manual emulator testing ## Screenshots N/A ## AI or LLM usage None
This commit is contained in:
parent
b0d270380d
commit
559abe2458
6 changed files with 105 additions and 45 deletions
|
|
@ -389,6 +389,19 @@ sealed interface AppPreference<Pref, T> {
|
|||
},
|
||||
)
|
||||
|
||||
val MaxResolutionPref =
|
||||
AppChoicePreference<AppPreferences, MaxResolution>(
|
||||
title = R.string.max_resolution,
|
||||
defaultValue = MaxResolution.NO_LIMIT,
|
||||
getter = { MaxResolution.fromValue(it.playbackPreferences.overrides.maxResolution) },
|
||||
setter = { prefs, value ->
|
||||
prefs.updatePlaybackOverrides { maxResolution = value.value }
|
||||
},
|
||||
displayValues = R.array.max_resolution_options,
|
||||
indexToValue = { MaxResolution.entries.getOrNull(it) ?: MaxResolution.NO_LIMIT },
|
||||
valueToIndex = { it.ordinal },
|
||||
)
|
||||
|
||||
val Ac3Supported =
|
||||
AppSwitchPreference<AppPreferences>(
|
||||
title = R.string.ac3_supported,
|
||||
|
|
@ -1242,6 +1255,7 @@ val advancedPreferences =
|
|||
AppPreference.GlobalContentScale,
|
||||
AppPreference.SkipSegments,
|
||||
AppPreference.DpadSeekModePref,
|
||||
AppPreference.MaxResolutionPref,
|
||||
AppPreference.MaxBitrate,
|
||||
AppPreference.RefreshRateSwitching,
|
||||
AppPreference.ResolutionSwitching,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
package com.github.damontecres.wholphin.preferences
|
||||
|
||||
enum class MaxResolution(
|
||||
val value: Int,
|
||||
) {
|
||||
NO_LIMIT(0),
|
||||
ULTRA_HD_8K(4320),
|
||||
ULTRA_HD_4K(2160),
|
||||
FULL_HD(1080),
|
||||
HD(720),
|
||||
PAL_SD(576),
|
||||
NTSC_SD(480),
|
||||
LOW_DEF(360),
|
||||
;
|
||||
|
||||
companion object {
|
||||
fun fromValue(value: Int): MaxResolution = entries.firstOrNull { it.value == value } ?: NO_LIMIT
|
||||
}
|
||||
}
|
||||
|
|
@ -65,6 +65,7 @@ class DeviceProfileService
|
|||
decodeAv1 = prefs.overrides.decodeAv1,
|
||||
preferAc3ForSurround = appPrefs.experimentalPreferences.enabled { preferAc3Surround },
|
||||
jellyfinTenEleven = newConfig.jellyfinTenEleven,
|
||||
maxResolution = newConfig.overrides.maxResolution,
|
||||
)
|
||||
}
|
||||
this@DeviceProfileService.deviceProfile!!
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.github.damontecres.wholphin.util.profile
|
|||
// Adapted from https://github.com/jellyfin/jellyfin-androidtv/blob/v0.19.4/app/src/main/java/org/jellyfin/androidtv/util/profile/deviceProfile.kt
|
||||
|
||||
import android.media.MediaCodecInfo
|
||||
import android.util.Size
|
||||
import androidx.media3.common.MimeTypes
|
||||
import com.github.damontecres.wholphin.util.profile.KnownDefects.supportsHi10P52
|
||||
import org.jellyfin.sdk.model.api.CodecType
|
||||
|
|
@ -61,6 +62,8 @@ val supportedAudioCodecs =
|
|||
// )
|
||||
// }
|
||||
|
||||
private fun Size.min(min: Int) = if (min > 0 && height > min) Size(width, min) else this
|
||||
|
||||
fun createDeviceProfile(
|
||||
mediaTest: MediaCodecCapabilitiesTest,
|
||||
maxBitrate: Int,
|
||||
|
|
@ -72,6 +75,7 @@ fun createDeviceProfile(
|
|||
decodeAv1: Boolean,
|
||||
jellyfinTenEleven: Boolean,
|
||||
preferAc3ForSurround: Boolean,
|
||||
maxResolution: Int,
|
||||
) = buildDeviceProfile {
|
||||
val allowedAudioCodecs =
|
||||
when {
|
||||
|
|
@ -106,10 +110,10 @@ fun createDeviceProfile(
|
|||
val supportsAV1 = mediaTest.supportsAV1()
|
||||
val supportsAV1Main10 = mediaTest.supportsAV1Main10()
|
||||
val supportsVC1 = mediaTest.supportsVc1()
|
||||
val maxResolutionAVC = mediaTest.getMaxResolution(MimeTypes.VIDEO_H264)
|
||||
val maxResolutionHevc = mediaTest.getMaxResolution(MimeTypes.VIDEO_H265)
|
||||
val maxResolutionAV1 = mediaTest.getMaxResolution(MimeTypes.VIDEO_AV1)
|
||||
val maxResolutionVC1 = mediaTest.getMaxResolution(MimeTypes.VIDEO_VC1)
|
||||
val maxResolutionAVC = mediaTest.getMaxResolution(MimeTypes.VIDEO_H264).min(maxResolution)
|
||||
val maxResolutionHevc = mediaTest.getMaxResolution(MimeTypes.VIDEO_H265).min(maxResolution)
|
||||
val maxResolutionAV1 = mediaTest.getMaxResolution(MimeTypes.VIDEO_AV1).min(maxResolution)
|
||||
val maxResolutionVC1 = mediaTest.getMaxResolution(MimeTypes.VIDEO_VC1).min(maxResolution)
|
||||
|
||||
// / HDR capabilities
|
||||
|
||||
|
|
@ -133,59 +137,43 @@ fun createDeviceProfile(
|
|||
|
||||
// / Transcoding profiles
|
||||
// Video
|
||||
if (preferAc3ForSurround) {
|
||||
transcodingProfile {
|
||||
type = DlnaProfileType.VIDEO
|
||||
context = EncodingContext.STREAMING
|
||||
transcodingProfile {
|
||||
type = DlnaProfileType.VIDEO
|
||||
context = EncodingContext.STREAMING
|
||||
|
||||
container = Codec.Container.TS
|
||||
protocol = MediaStreamProtocol.HLS
|
||||
container = Codec.Container.TS
|
||||
protocol = MediaStreamProtocol.HLS
|
||||
|
||||
if (supportsHevc) videoCodec(Codec.Video.HEVC)
|
||||
videoCodec(Codec.Video.H264)
|
||||
|
||||
audioCodec(Codec.Audio.AC3)
|
||||
|
||||
copyTimestamps = false
|
||||
enableSubtitlesInManifest = true
|
||||
}
|
||||
} else {
|
||||
transcodingProfile {
|
||||
type = DlnaProfileType.VIDEO
|
||||
context = EncodingContext.STREAMING
|
||||
|
||||
container = Codec.Container.TS
|
||||
protocol = MediaStreamProtocol.HLS
|
||||
|
||||
if (supportsHevc) videoCodec(Codec.Video.HEVC)
|
||||
videoCodec(Codec.Video.H264)
|
||||
if (supportsHevc) videoCodec(Codec.Video.HEVC)
|
||||
videoCodec(Codec.Video.H264)
|
||||
|
||||
if (preferAc3ForSurround) {
|
||||
audioCodec(*allowedAudioCodecs)
|
||||
} else {
|
||||
audioCodec(Codec.Audio.AC3)
|
||||
}
|
||||
|
||||
copyTimestamps = false
|
||||
enableSubtitlesInManifest = true
|
||||
copyTimestamps = false
|
||||
enableSubtitlesInManifest = true
|
||||
|
||||
if (maxResolution > 0) {
|
||||
conditions {
|
||||
ProfileConditionValue.HEIGHT lowerThanOrEquals maxResolution
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Audio
|
||||
if (preferAc3ForSurround) {
|
||||
transcodingProfile {
|
||||
type = DlnaProfileType.AUDIO
|
||||
context = EncodingContext.STREAMING
|
||||
transcodingProfile {
|
||||
type = DlnaProfileType.AUDIO
|
||||
context = EncodingContext.STREAMING
|
||||
|
||||
container = Codec.Container.TS
|
||||
protocol = MediaStreamProtocol.HLS
|
||||
container = Codec.Container.TS
|
||||
protocol = MediaStreamProtocol.HLS
|
||||
|
||||
if (preferAc3ForSurround) {
|
||||
audioCodec(Codec.Audio.AC3)
|
||||
}
|
||||
} else {
|
||||
transcodingProfile {
|
||||
type = DlnaProfileType.AUDIO
|
||||
context = EncodingContext.STREAMING
|
||||
|
||||
container = Codec.Container.TS
|
||||
protocol = MediaStreamProtocol.HLS
|
||||
|
||||
} else {
|
||||
audioCodec(Codec.Audio.AAC)
|
||||
}
|
||||
}
|
||||
|
|
@ -459,6 +447,24 @@ fun createDeviceProfile(
|
|||
}
|
||||
}
|
||||
|
||||
if (maxResolution > 0) {
|
||||
listOf(
|
||||
Codec.Video.MPEG,
|
||||
Codec.Video.MPEG2VIDEO,
|
||||
Codec.Video.VP8,
|
||||
Codec.Video.VP9,
|
||||
).forEach { codecName ->
|
||||
codecProfile {
|
||||
type = CodecType.VIDEO
|
||||
codec = codecName
|
||||
|
||||
conditions {
|
||||
ProfileConditionValue.HEIGHT lowerThanOrEquals maxResolution
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// / HDR exclude list
|
||||
|
||||
// TODO Use VideoRangeType enum with Jellyfin 10.11 based SDK
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ message PlaybackOverrides{
|
|||
bool direct_play_dolby_vision_e_l = 6;
|
||||
bool decode_av1 = 7;
|
||||
AssPlaybackMode ass_playback_mode = 8;
|
||||
int32 max_resolution = 9;
|
||||
}
|
||||
|
||||
message PlaybackPreferences {
|
||||
|
|
|
|||
|
|
@ -927,6 +927,25 @@
|
|||
<string name="remove_from_playlist">Remove from playlist</string>
|
||||
<string name="login_credentials_expired">Credentials expired, please login in again</string>
|
||||
<string name="login_not_authorized">You are not authorized to access the server at this time</string>
|
||||
<string name="max_resolution">Max resolution before transcoding</string>
|
||||
|
||||
<string name="resolution_8k">8K</string>
|
||||
<string name="resolution_4k">4K</string>
|
||||
<string name="resolution_1080">1080</string>
|
||||
<string name="resolution_720">720</string>
|
||||
<string name="resolution_576">576</string>
|
||||
<string name="resolution_480">480</string>
|
||||
<string name="resolution_360">360</string>
|
||||
<string-array name="max_resolution_options">
|
||||
<item>@string/no_limit</item>
|
||||
<item>@string/resolution_8k</item>
|
||||
<item>@string/resolution_4k</item>
|
||||
<item>@string/resolution_1080</item>
|
||||
<item>@string/resolution_720</item>
|
||||
<item>@string/resolution_576</item>
|
||||
<item>@string/resolution_480</item>
|
||||
<item>@string/resolution_360</item>
|
||||
</string-array>
|
||||
<plurals name="episodes_count">
|
||||
<item quantity="zero">%1$d Episodes</item>
|
||||
<item quantity="one">%1$d Episode</item>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue