257 lines
12 KiB
Swift
257 lines
12 KiB
Swift
//
|
|
// Swiftfin is subject to the terms of the Mozilla Public
|
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
|
//
|
|
// Copyright (c) 2024 Jellyfin & Jellyfin Contributors
|
|
//
|
|
|
|
import JellyfinAPI
|
|
|
|
enum DeviceProfileBuilder {
|
|
|
|
static func buildProfile(for type: VideoPlayerType, maxBitrate: Int? = nil) -> DeviceProfile {
|
|
let maxStreamingBitrate = maxBitrate
|
|
let maxStaticBitrate = maxBitrate
|
|
let musicStreamingTranscodingBitrate = maxBitrate
|
|
var directPlayProfiles: [DirectPlayProfile] = []
|
|
var transcodingProfiles: [TranscodingProfile] = []
|
|
var codecProfiles: [CodecProfile] = []
|
|
var subtitleProfiles: [SubtitleProfile] = []
|
|
|
|
switch type {
|
|
|
|
case .swiftfin:
|
|
func buildProfileSwiftfin() {
|
|
// Build direct play profiles
|
|
directPlayProfiles = [
|
|
// Just make one profile because if VLCKit can't decode it in a certain container, ffmpeg probably can't decode it for
|
|
// transcode either
|
|
DirectPlayProfile(
|
|
// No need to list containers or videocodecs since if jellyfin server can detect it/ffmpeg can decode it, so can
|
|
// VLCKit
|
|
// However, list audiocodecs because ffmpeg can decode TrueHD/mlp but VLCKit cannot
|
|
audioCodec: "flac,alac,aac,eac3,ac3,dts,opus,vorbis,mp3,mp2,mp1,pcm_s24be,pcm_s24le,pcm_s16be,pcm_s16le,pcm_u8,pcm_alaw,pcm_mulaw,pcm_bluray,pcm_dvd,wavpack,wmav2,wmav1,wmapro,wmalossless,nellymoser,speex,amr_nb,amr_wb",
|
|
type: .video
|
|
),
|
|
]
|
|
|
|
// Build transcoding profiles
|
|
// The only cases where transcoding should occur:
|
|
// 1) TrueHD/mlp audio
|
|
// 2) When server forces transcode for bitrate reasons
|
|
transcodingProfiles = [TranscodingProfile(
|
|
audioCodec: "flac,alac,aac,eac3,ac3,dts,opus,vorbis,mp3,mp2,mp1",
|
|
// no PCM,wavpack,wmav2,wmav1,wmapro,wmalossless,nellymoser,speex,amr_nb,amr_wb in mp4
|
|
isBreakOnNonKeyFrames: true,
|
|
container: "mp4",
|
|
context: .streaming,
|
|
maxAudioChannels: "8",
|
|
minSegments: 2,
|
|
protocol: "hls",
|
|
type: .video,
|
|
videoCodec: "hevc,h264,av1,vp9,vc1,mpeg4,h263,mpeg2video,mpeg1video,mjpeg" // vp8,msmpeg4v3,msmpeg4v2,msmpeg4v1,theora,ffv1,flv1,wmv3,wmv2,wmv1
|
|
// not supported in mp4
|
|
)]
|
|
|
|
// Create subtitle profiles
|
|
subtitleProfiles = [
|
|
SubtitleProfile(format: "pgssub", method: .embed), // *pgs* normalized to pgssub; includes sup
|
|
SubtitleProfile(format: "dvdsub", method: .embed),
|
|
// *dvd* normalized to dvdsub; includes sub/idx I think; microdvd case?
|
|
SubtitleProfile(format: "subrip", method: .embed), // srt
|
|
SubtitleProfile(format: "ass", method: .embed),
|
|
SubtitleProfile(format: "ssa", method: .embed),
|
|
SubtitleProfile(format: "vtt", method: .embed), // webvtt
|
|
SubtitleProfile(format: "mov_text", method: .embed), // MPEG-4 Timed Text
|
|
SubtitleProfile(format: "ttml", method: .embed),
|
|
SubtitleProfile(format: "text", method: .embed), // txt
|
|
SubtitleProfile(format: "dvbsub", method: .embed),
|
|
// dvb_subtitle normalized to dvbsub; burned in during transcode regardless?
|
|
SubtitleProfile(format: "libzvbi_teletextdec", method: .embed), // dvb_teletext
|
|
SubtitleProfile(format: "xsub", method: .embed),
|
|
SubtitleProfile(format: "vplayer", method: .embed),
|
|
SubtitleProfile(format: "subviewer", method: .embed),
|
|
SubtitleProfile(format: "subviewer1", method: .embed),
|
|
SubtitleProfile(format: "sami", method: .embed), // SMI
|
|
SubtitleProfile(format: "realtext", method: .embed),
|
|
SubtitleProfile(format: "pjs", method: .embed), // Phoenix Subtitle
|
|
SubtitleProfile(format: "mpl2", method: .embed),
|
|
SubtitleProfile(format: "jacosub", method: .embed),
|
|
SubtitleProfile(format: "cc_dec", method: .embed), // eia_608
|
|
// Can be passed as external files; ones that jellyfin can encode to must come first
|
|
SubtitleProfile(format: "subrip", method: .external), // srt
|
|
SubtitleProfile(format: "ttml", method: .external),
|
|
SubtitleProfile(format: "vtt", method: .external), // webvtt
|
|
SubtitleProfile(format: "ass", method: .external),
|
|
SubtitleProfile(format: "ssa", method: .external),
|
|
SubtitleProfile(format: "pgssub", method: .external),
|
|
SubtitleProfile(format: "text", method: .external), // txt
|
|
SubtitleProfile(format: "dvbsub", method: .external), // dvb_subtitle normalized to dvbsub
|
|
SubtitleProfile(format: "libzvbi_teletextdec", method: .external), // dvb_teletext
|
|
SubtitleProfile(format: "dvdsub", method: .external),
|
|
// *dvd* normalized to dvdsub; includes sub/idx I think; microdvd case?
|
|
SubtitleProfile(format: "xsub", method: .external),
|
|
SubtitleProfile(format: "vplayer", method: .external),
|
|
SubtitleProfile(format: "subviewer", method: .external),
|
|
SubtitleProfile(format: "subviewer1", method: .external),
|
|
SubtitleProfile(format: "sami", method: .external), // SMI
|
|
SubtitleProfile(format: "realtext", method: .external),
|
|
SubtitleProfile(format: "pjs", method: .external), // Phoenix Subtitle
|
|
SubtitleProfile(format: "mpl2", method: .external),
|
|
SubtitleProfile(format: "jacosub", method: .external),
|
|
]
|
|
}
|
|
buildProfileSwiftfin()
|
|
|
|
case .native:
|
|
func buildProfileNative() {
|
|
// Build direct play profiles
|
|
directPlayProfiles = [
|
|
// Apple limitation: no mp3 in mp4; avi only supports mjpeg with pcm
|
|
// Right now, mp4 restrictions can't be enforced because mp4, m4v, mov, 3gp,3g2 treated the same
|
|
DirectPlayProfile(
|
|
audioCodec: "flac,alac,aac,eac3,ac3,opus",
|
|
container: "mp4",
|
|
type: .video,
|
|
videoCodec: "hevc,h264,mpeg4"
|
|
),
|
|
DirectPlayProfile(
|
|
audioCodec: "alac,aac,ac3",
|
|
container: "m4v",
|
|
type: .video,
|
|
videoCodec: "h264,mpeg4"
|
|
),
|
|
DirectPlayProfile(
|
|
audioCodec: "alac,aac,eac3,ac3,mp3,pcm_s24be,pcm_s24le,pcm_s16be,pcm_s16le",
|
|
container: "mov",
|
|
type: .video,
|
|
videoCodec: "hevc,h264,mpeg4,mjpeg"
|
|
),
|
|
DirectPlayProfile(
|
|
audioCodec: "aac,eac3,ac3,mp3",
|
|
container: "mpegts",
|
|
type: .video,
|
|
videoCodec: "h264"
|
|
),
|
|
DirectPlayProfile(
|
|
audioCodec: "aac,amr_nb",
|
|
container: "3gp,3g2",
|
|
type: .video,
|
|
videoCodec: "h264,mpeg4"
|
|
),
|
|
DirectPlayProfile(
|
|
audioCodec: "pcm_s16le,pcm_mulaw",
|
|
container: "avi",
|
|
type: .video,
|
|
videoCodec: "mjpeg"
|
|
),
|
|
]
|
|
|
|
// Build transcoding profiles
|
|
transcodingProfiles = [
|
|
TranscodingProfile(
|
|
audioCodec: "flac,alac,aac,eac3,ac3,opus",
|
|
isBreakOnNonKeyFrames: true,
|
|
container: "mp4",
|
|
context: .streaming,
|
|
maxAudioChannels: "8",
|
|
minSegments: 2,
|
|
protocol: "hls",
|
|
type: .video,
|
|
videoCodec: "hevc,h264,mpeg4"
|
|
),
|
|
]
|
|
|
|
// Create subtitle profiles
|
|
subtitleProfiles = [
|
|
// FFmpeg can only convert bitmap to bitmap and text to text; burn in bitmap subs
|
|
SubtitleProfile(format: "pgssub", method: .encode),
|
|
SubtitleProfile(format: "dvdsub", method: .encode),
|
|
SubtitleProfile(format: "dvbsub", method: .encode),
|
|
SubtitleProfile(format: "xsub", method: .encode),
|
|
// According to Apple HLS authoring specs, WebVTT must be in a text file delivered via HLS
|
|
SubtitleProfile(format: "vtt", method: .hls), // webvtt
|
|
// Apple HLS authoring spec has closed captions in video segments and TTML in fmp4
|
|
SubtitleProfile(format: "ttml", method: .embed),
|
|
SubtitleProfile(format: "cc_dec", method: .embed),
|
|
]
|
|
}
|
|
buildProfileNative()
|
|
}
|
|
|
|
// For now, assume native and VLCKit support same codec conditions:
|
|
let h264CodecConditions: [ProfileCondition] = [
|
|
ProfileCondition(
|
|
condition: .notEquals,
|
|
isRequired: false,
|
|
property: .isAnamorphic,
|
|
value: "true"
|
|
),
|
|
ProfileCondition(
|
|
condition: .equalsAny,
|
|
isRequired: false,
|
|
property: .videoProfile,
|
|
value: "high|main|baseline|constrained baseline"
|
|
),
|
|
ProfileCondition(
|
|
condition: .lessThanEqual,
|
|
isRequired: false,
|
|
property: .videoLevel,
|
|
value: "80"
|
|
),
|
|
ProfileCondition(
|
|
condition: .notEquals,
|
|
isRequired: false,
|
|
property: .isInterlaced,
|
|
value: "true"
|
|
),
|
|
]
|
|
|
|
codecProfiles.append(CodecProfile(applyConditions: h264CodecConditions, codec: "h264", type: .video))
|
|
|
|
let hevcCodecConditions: [ProfileCondition] = [
|
|
ProfileCondition(
|
|
condition: .notEquals,
|
|
isRequired: false,
|
|
property: .isAnamorphic,
|
|
value: "true"
|
|
),
|
|
ProfileCondition(
|
|
condition: .equalsAny,
|
|
isRequired: false,
|
|
property: .videoProfile,
|
|
value: "high|main|main 10"
|
|
),
|
|
ProfileCondition(
|
|
condition: .lessThanEqual,
|
|
isRequired: false,
|
|
property: .videoLevel,
|
|
value: "175"
|
|
),
|
|
ProfileCondition(
|
|
condition: .notEquals,
|
|
isRequired: false,
|
|
property: .isInterlaced,
|
|
value: "true"
|
|
),
|
|
]
|
|
|
|
codecProfiles.append(CodecProfile(applyConditions: hevcCodecConditions, codec: "hevc", type: .video))
|
|
|
|
let responseProfiles: [ResponseProfile] = [ResponseProfile(container: "m4v", mimeType: "video/mp4", type: .video)]
|
|
|
|
return .init(
|
|
codecProfiles: codecProfiles,
|
|
containerProfiles: [],
|
|
directPlayProfiles: directPlayProfiles,
|
|
maxStaticBitrate: maxStaticBitrate,
|
|
maxStreamingBitrate: maxStreamingBitrate,
|
|
musicStreamingTranscodingBitrate: musicStreamingTranscodingBitrate,
|
|
responseProfiles: responseProfiles,
|
|
subtitleProfiles: subtitleProfiles,
|
|
transcodingProfiles: transcodingProfiles
|
|
)
|
|
}
|
|
}
|