// Playback profile generation is adapted from Wholphin's DeviceProfileUtils.kt, // itself derived from Jellyfin Android TV. // // Wholphin: https://github.com/damontecres/Wholphin // Jellyfin Android TV: https://github.com/jellyfin/jellyfin-androidtv // // Modifications Copyright (C) 2026 Memby contributors // SPDX-License-Identifier: GPL-2.0-only package emby import "strconv" // PlaybackCapabilities is the Android decoder evidence captured for one TV session. // Zero values deliberately describe the legacy H.264-safe profile. type PlaybackCapabilities struct { H264Profiles []string H264Level int H264High10Level int H264MaxWidth int H264MaxHeight int HEVC bool HEVCMain bool HEVCMain10 bool HEVCMainLevel int HEVCMain10Level int HEVCMaxWidth int HEVCMaxHeight int HEVCHDR10 bool HEVCHDR10Plus bool HEVCDolbyVision bool } func androidTVDeviceProfile(capabilities PlaybackCapabilities) map[string]any { videoCodecs := directPlayVideoCodecs(capabilities.HEVC) return map[string]any{ "Name": "Memby Android TV", "SupportedMediaTypes": "Video", "DirectPlayProfiles": []map[string]string{ { "Container": "mkv,mp4,m4v,mov,ts,mpegts", "VideoCodec": videoCodecs, "AudioCodec": "aac,mp3", "Type": "Video", }, }, "TranscodingProfiles": []map[string]string{ { // AllowVideoStreamCopy lets Emby keep a supported H.264/HEVC track and // convert only incompatible audio or subtitles into an HLS stream. "Container": "ts", "VideoCodec": videoCodecs, "AudioCodec": "aac", "Protocol": "hls", "Type": "Video", "Context": "Streaming", }, }, "CodecProfiles": videoCodecProfiles(capabilities), "SubtitleProfiles": androidTVSubtitleProfiles(), } } func androidTVSubtitleProfiles() []map[string]string { return []map[string]string{ {"Format": "srt", "Method": "External"}, {"Format": "subrip", "Method": "External"}, {"Format": "ass", "Method": "External"}, {"Format": "ssa", "Method": "External"}, {"Format": "vtt", "Method": "External"}, {"Format": "webvtt", "Method": "External"}, {"Format": "mov_text", "Method": "External"}, {"Format": "tx3g", "Method": "External"}, {"Format": "pgs", "Method": "Encode"}, {"Format": "pgssub", "Method": "Encode"}, {"Format": "sup", "Method": "Encode"}, {"Format": "vobsub", "Method": "Encode"}, {"Format": "dvdsub", "Method": "Encode"}, } } func videoCodecProfiles(capabilities PlaybackCapabilities) []map[string]any { profiles := []map[string]any{} if len(capabilities.H264Profiles) > 0 { profiles = append(profiles, codecProfile("h264", []map[string]any{profileCondition("EqualsAny", "VideoProfile", joinProfiles(capabilities.H264Profiles))}, nil)) } profiles = appendLevelProfile( profiles, "h264", capabilities.H264Level, "baseline|constrained baseline|main|high", ) profiles = appendLevelProfile(profiles, "h264", capabilities.H264High10Level, "high 10") profiles = appendResolutionProfile( profiles, "h264", capabilities.H264MaxWidth, capabilities.H264MaxHeight, ) if !capabilities.HEVC { return profiles } hevcProfiles := []string{} if capabilities.HEVCMain { hevcProfiles = append(hevcProfiles, "main") } if capabilities.HEVCMain10 { hevcProfiles = append(hevcProfiles, "main 10") } if len(hevcProfiles) > 0 { profiles = append(profiles, codecProfile("hevc", []map[string]any{profileCondition("EqualsAny", "VideoProfile", joinProfiles(hevcProfiles))}, nil)) } profiles = appendLevelProfile(profiles, "hevc", capabilities.HEVCMainLevel, "main") profiles = appendLevelProfile(profiles, "hevc", capabilities.HEVCMain10Level, "main 10") profiles = appendResolutionProfile( profiles, "hevc", capabilities.HEVCMaxWidth, capabilities.HEVCMaxHeight, ) return profiles } func appendLevelProfile(profiles []map[string]any, codec string, level int, applyTo string) []map[string]any { if level <= 0 { return profiles } condition := "Equals" if containsPipe(applyTo) { condition = "EqualsAny" } return append(profiles, codecProfile(codec, []map[string]any{profileCondition("LessThanEqual", "VideoLevel", strconv.Itoa(level))}, []map[string]any{profileCondition(condition, "VideoProfile", applyTo)})) } func appendResolutionProfile(profiles []map[string]any, codec string, width, height int) []map[string]any { if width <= 0 || height <= 0 { return profiles } return append(profiles, codecProfile(codec, []map[string]any{ profileCondition("LessThanEqual", "Width", strconv.Itoa(width)), profileCondition("LessThanEqual", "Height", strconv.Itoa(height)), }, nil)) } func codecProfile(codec string, conditions, applyConditions []map[string]any) map[string]any { profile := map[string]any{ "Type": "Video", "Codec": codec, "Conditions": conditions, } if len(applyConditions) > 0 { profile["ApplyConditions"] = applyConditions } return profile } func profileCondition(condition, property, value string) map[string]any { return map[string]any{ "Condition": condition, "Property": property, "Value": value, "IsRequired": false, } } func joinProfiles(values []string) string { result := "" for _, value := range values { if result != "" { result += "|" } result += value } return result } func containsPipe(value string) bool { for _, char := range value { if char == '|' { return true } } return false }